最新Android面试总结

2018-12-06  本文已影响0人  粥小新

年底互联网寒冬来临,很不幸,本人公司由于资金链断裂,也加入年底找工作大军当中。年底工作不好找,特此勉励所有有相同经历的同学们,保持耐心,互相鼓励。

个人面试了一两个星期,以下是我个人的面试总结,仅供参考,有错恳请提出。

一.网络

1.网络协议 http、tcp、udp、socket
网络分层和协议关系如下:
    物理层--                     
    数据链路层--
    网络层--                       IP协议
    传输层--                       TCP、UDP协议
    会话层--
    表示层--                       HTTP协议、https、ftp
 http协议是基于tcp/ip协议的一种应用。而 socket封装了做tcp/ip开发的网络接口,        
 通过Socket,我们才能使用TCP/IP协议。http是短连接,socket是长连接。
2.tcp三次握手、4次挥手
第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,
客户端进入SYN_SEND状态,等待服务器的确认;
第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,
设置Acknowledgment Number为x+1(Sequence  Number+1);同时,自己自己还要发送SYN请求信息,
将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,
一并发送给客户端,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,
向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,
完成TCP三次握手。

当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的啊。
那对于TCP的断开连接,这里就有了神秘的“四次分手”。

第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment 
Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段Acknowledgment 
Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我“同意”你的关闭请求;
第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;
主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,
则证明Server端已正常关闭,那好,主机1也可以关闭连接了。
3.https对比http
简单来说,HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,要比http协议安全。

HTTPS和HTTP的区别主要如下:

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进        
行加密传输、身份认证的网络协议,比http协议安全。
4.防止抓包
因为抓包软件例如Fiddle都是需要利用wifi代理,所以判断是wifi代理,则不访问数据

if(isWifiProxy()){  //true,使用了wifi代理
    //不做访问操作
 }else{                 //flase,正常用户,未使用wifi代理
    //访问数据
 }

 public static boolean isWifiProxy() {
    final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >=   
    Build.VERSION_CODES.ICE_CREAM_SANDWICH;
    String proxyAddress;
    int proxyPort;
    if (IS_ICS_OR_LATER) {
        proxyAddress = System.getProperty("http.proxyHost");
        String portStr = System.getProperty("http.proxyPort");
        proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
    } else {
        proxyAddress = android.net.Proxy.getHost(context);
        proxyPort = android.net.Proxy.getPort(context);
    }
    return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
}
5.防止多并发请求,比如说一个页面多个请求
Android本来就是要做并发请求,开线程池在里面发网络请求,
如果真要防止并发,那就弄个排队的线程池就行了,参考AsyncTask在高版本的实现,就是排队。
6.跟后台交互网络优化
  1. 比如频繁调用的接口,可以考虑用长连接 
  2. 需要传输数据的接口可以考虑让服务器支持304状态,比如etag和last-modified。当没有变化时,不返回数据,从缓存取 
  3. 让服务端把小接口合并成大接口,减少网络请求的次数
7.为何选择retrofit
  1.解耦:比方说通过注解来配置请求参数,通过工厂来生成CallAdapter,Converter
  2.注解:通过注解方式配置请求,代码简介、使用方便(将一个http请求抽象出java接口,再通过动态代理翻译成http请求)
  3.同步异步:支持同步和异步执行,只要调用enqueue/execute即可完成;
  4.自由:更大自由度地支持我们自定义的业务逻辑,如自定义Converters,或者搭配rxjava使用
8.retrofit生命周期管理

https://www.jianshu.com/p/5f3e1398b3bd

9.http组成
 http请求报文和响应报文都是由以下4部分组成
 1.请求行(状态行)
 2.请求头
     通用报文包含Date、Connection、Cache-Control
     请求报头通知服务器关于客户端求求的信息,典型的请求头有:
      Host、User-Agent:发送请求的浏览器类型、操作系统等信息
      Accept:客户端可识别的内容类型列表,用于指定客户端接收那些类型的信息
      Accept-Encoding:客户端可识别的数据编码
      Accept-Language:表示浏览器所支持的语言类型
      Connection:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是为Keep-Alive则表示保持连接。
      Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。

      用于服务器传递自身信息的响应,常见的响应报头:
      Location:用于重定向接受者到一个新的位置,常用在更换域名的时候
      Server:包含可服务器用来处理请求的系统信息,与User-Agent请求报头是相对应的

      实体报头用来定于被传送资源的信息,请求和响应消息都可以传送一个实体,常见的实体报头为:
      Content-Type:发送给接收者的实体正文的媒体类型
      Content-Lenght:实体正文的长度
      Content-Language:描述资源所用的自然语言,没有设置则该选项则认为实体内容将提供给所有的语言阅读
      Content-Encoding:实体报头被用作媒体类型的修饰符,它的值指示了已经被应用到实体正文的附加内容的编码
      Last-Modified:实体报头用于指示资源的最后修改日期和时间
      Expires:实体报头给出响应过期的日期和时间
 3.空行
 4.消息主体
10.http1.0和http1.1区别
 http1.0 服务端向客服端返回响应消息时,确定客户端收到消息后,便会关闭连接。
 整个过程中,并不保存任何历史信息和状态信息,所以http也被认为是无状态的;
 http1.1后将关闭客户端连接的主动权交还给客户端,增加了持久连接支持
11.为什么选择okhttp
 用于替换httpurlconnection;
 支持SPDY协议,允许连接同一主机所有请求分享一个socket;
 使用连接池减少请求延迟;
 利用响应缓存避免重复请求;
 使用GZIP压缩下载内容
12.https加密过程,ssl核心
一个HTTPS请求实际上包含了两次HTTP传输,可以细分为8步。

第一次HTTP请求:
1.客户端向服务器发起HTTPS请求,连接到服务器的443端口。
2.服务器端有一个密钥对,即公钥和私钥,是用来进行非对称加密使用的,服务器端保存着私钥,不能将其泄露,公钥可以发送给任何人。
3.服务器将自己的公钥发送给客户端。
4.客户端收到服务器端的公钥之后,会对公钥进行检查,验证其合法性(CA认证),如果公钥合格,那么客户端会生成一个随机值,这个随机值就是用于进行对称加密的密钥,
我们将该密钥称之为client key,即客户端密钥,这样在概念上和服务器端的密钥容易进行区分。然后用服务器的公钥对客户端密钥进行非对称加密,这样客户端密钥就变成密文了

第二次HTTP请求:
1.客户端会发起HTTPS中的第二个HTTP请求,将加密之后的客户端密钥发送给服务器。
2.服务器接收到客户端发来的密文之后,会用自己的私钥对其进行非对称解密,解密之后的明文就是客户端密钥,然后用客户端密钥对数据进行对称加密,这样数据就变成了密文。
然后服务器将加密后的密文发送给客户端。
3.客户端收到服务器发送来的密文,用客户端密钥对其进行对称解密,得到服务器发送的数据。这样HTTPS中的第二个HTTP请求结束,整个HTTPS传输完成。

总的来说,对数据进行对称加密,对称加密所要使用的密钥通过非对称加密传输。

https://yq.aliyun.com/articles/666103

13.自登录保存用户信息怎么保证安全性
  方案1.token+https:登录之后由服务端生成token,一般是由用户信息+设备信息+时间组成。token保存在本地,自登录时再由
   服务器判断是有有效和过期。网络协议采用https,传输过程是密文传输,因此不担心窃取
  
  方案2.RSA+随机数(加盐):将登录信息+时间用RSA公钥加密传给服务端,服务端私钥解密判断时间再验证登录,验证通过,
  生成随机salt,以用户id为key,缓存salt值。将用户名+salt用公钥加密,返回给客户端保存。下次登录直接传加密串,验证缓存中salt值是否相等。
  方案2的好处是不需要https加密保护,还可利用清楚salt缓存强制再次登录,在加密串失窃后不必修改密码

二.图片处理

1.像素格式
ALPHA_8 
表示8位Alpha位图,即A=8,一个像素点占用1个字节,它没有颜色,只有透明度 
ARGB_4444 
表示16位ARGB位图,即A=4,R=4,G=4,B=4,一个像素点占4+4+4+4=16位,2个字节 ,不推荐,画质太差
ARGB_8888 
表示32位ARGB位图,即A=8,R=8,G=8,B=8,一个像素点占8+8+8+8=32位,4个字节 
RGB_565 
表示16位RGB位图,即R=5,G=6,B=5,它没有透明度,一个像素点占5+6+5=16位,2个字节
2.图片压缩处理

https://www.jianshu.com/p/a52c31b45d6b

3.为什么选择Glide
  1.链式操作,代码简洁
  2.可以加载gif
  3.默认像素格式使用的是RGB565,加载imageview所需要的精确尺寸,省内存,速度快
4.Glide控制生命周期
  加载图片时取得context,然后再上面追加一个fragment,目的是为了拿到activity的生命周期,
  在destroy的时候取消图片加载任务
5.bitmap定宽不定高压缩
  Matrix matrix = new Matrix();
  matrix.setScale(0.5f, 1f); //只设置宽或者高
  bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),
  bit.getHeight(), matrix, true);

  ps:如果是定宽不定高加载,直接使用imageview的scaletype缩放显示即可
6.缓存策略&Lru算法
  图片缓存策略一般是采取三级缓存,内存缓存、本地缓存和网络缓存
  1).内存缓存LruCache
      初始化,分配内存缓存容量
      int maxMemory = (int) (Runtime.getRuntime().maxMemory()/1024);
      int cacheSizw = maxMemory/8;
      mMemoryCache = new LruCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key,Bitmap bitmap){
                  return bitmap.getRowBytes()*bitmap.getHeight()/1024;
            }
      };

    然后便是使用时的添加和获取方法了
    mMemoryCache.put(key.bitmap);
    mMemoryCache.get(key);
    
  2).本地缓存 DiskLruCache
  初始化可用内存和缓存位置
  private static final long DISK_CACHE_SIZE = 1024*1024*50;//50M
  File diskCacheDisk = getDiskCacheDir(mContext,"bitmap");
  if(!diskCacheDisk.exists())
    diskcacheDisk.mkdirs();
  //参数分别为缓存位置,版本号,单个节点对应个数,缓存大小
  mDiskLruCache = DiskLruCache.open(diskCacheDisk,1,1,DISH_CACHE_SIZE);

  DiskLruCache的使用需要通过Editor
    new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            String imageUrl = "...";
            String key = hashKeyForDisk(imageUrl);//MD5,url有特殊字符,不能直接使用
            DiskLruCache.Editor editor = mDiskLruCache.edit(key);
            if (editor != null) {
                OutputStream outputStream = editor.newOutputStream(0);
                //将图片下载到outputStream
                if (downloadUrlToStream(imageUrl, outputStream)) {
                    editor.commit();
                } else {
                    editor.abort();
                }
            }
            mDiskLruCache.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }).start();

    //获取缓存
    String imageUrl = "...";
    String key = hashKeyForDisk(imageUrl);
    DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
    if (snapShot != null) {
        InputStream is = snapShot.getInputStream(0);
        Bitmap bitmap = BitmapFactory.decodeStream(is);
        mImage.setImageBitmap(bitmap);
    }

    内存缓存和本地缓存都是基于LRU算法,即Least Recently Used最近最少使用算法

LRU底层实现是基于LinkedHaspMap,通过LinkedHaspMap的header的双向链表实现,header的after就是最近最少使用的,删除的都是这个节点
https://cloud.tencent.com/developer/article/1340759

三.其他

1.冲突的时候改写子view
 冲突的时候一般采取外部拦截法,即改写父view。在父view的onIterceptTouchEvent需要拦截的地方
 返回true即可。(一般是ACTION_MOVE里面,ACTION_DOWN不可拦截,拦了的话事件就传不到子view了)
  
 也可采取内部拦截法,改写子view。子view没有onIterceptTouchEvent,在dispatchTouchEvent里面
 配合parent.requestDisallowInterceptTouchEvent()拦截
 
 public boolean dispatchTouchEvent(MotionEvent event){
      switch(event.getAction()){
          case MotionEvent.ACTION_DOWN:{
              getParent().requestallowInterceptTouchEvent(true); //传true表示父view不拦截
              break;
          }
          case MotionEvent.ACTION_MOVE:{
              if(父view需要此点击事件){
                 getParent().requestallowInterceptTouchEvent(false); //传false表示父view拦截  
              }
              break;
          }
      }
      return super.dispatchTouchEvent(event);
}

此外父view也要默认拦截除了ACTION_DOWN以外的其他事件,子view调用requestallowInterceptTouchEvent(false)才能生效
public  boolean onIterceptionTouchEvent(MotionEvent event){
    int action = event.getAction();
    if(action == MotionEvent.ACTION_DOWN)
        return false;
    else
        return true;
}
2.handle为什么会持有activity对象
 在java中非静态内部类和匿名内部类都会持有当前类的外部引用,所以在activity中使用    
 handler,handler就会持有activity的隐式引用。这样activity无法被回收,会导致内存泄漏。
 解决办法:将handler声明为静态内部类,并弱引用activity
 static class MyHandler extends Handler {
    WeakReference<Activity > mActivityReference;

    MyHandler(Activity activity) {
        mActivityReference= new WeakReference<Activity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            tv_text.setText(msg.what.toString());
        }
    }
}
3.Application的content和activity service的content有什么不同
  Application content是整个应用内有效的,activity、service的content是
  组件生命周期内有效。所以单例类不可以持有activity、service的content,要使用context.getApplicationContext(),防止内存泄漏
Android与js的交互
 1.webView的loadUrl("javascript.a()"//javascript定义的方法)
 2.定义javascriptIntereface在本地方法给js文件调用
 public class JsObject {
private final static String TAG = "TAG_JsObject";

@JavascriptInterface
public void onSumResult(int result){
    Log.d(TAG, "result = " + result);
}

}

Rxbus怎么异步通知
 定义RxBus类
 public class RxBus {

    private final Subject<Object, Object> _bus;
    private volatile static RxBus instace;

    private RxBus() {
        //使用PublishSubject把在订阅发生的时间点之后来自原始Observable的数据发射给观察者
        _bus = new SerializedSubject<>(PublishSubject.create());
    }

    public static RxBus getInstace() {
        if (instace == null) {
            synchronized (RxBus.class) {
                if (instace == null)
                    instace = new RxBus();
            }
        }
        return instace;
    }

    public void send(Object o) {
        _bus.onNext(o);
    }

    //配合rxlifecycle使用,管理生命周期
    public Observable<Object> toObserverable(LifecycleTransformer lifecycleTransformer) {
        return _bus.compose(lifecycleTransformer);
    }
}
  
发送通知
 RxBus.getInstace().send(ActivityEventEntity.REFRESH.getValue());

接收通知
 RxBus.getInstace().toObserverable(
            bindUntilEvent(ActivityEvent.DESTROY))  //DESTROY时销毁
            .subscribe(new Action1<Object>() {
                @Override
                public void call(Object event) {
                    if ((int) event == ActivityEventEntity.REFRESH.getValue()) {
                       ...
                    }
                }
            });
系统启动流程
首先会启动引导程序bootloader,接着启动init进程;
init进程启动zygote进程,zygote首先启动systemServer进程,初始化硬件设备和服务;
systemServer会启动ActivityManagerService,WindowManagerService等,AMS的方法resumeTopActivityLocked会打开桌面launcher。
应用启动流程
launcher远程调用ActivityManagerService,要打开一个新的进程;
等确认可以打开新进程后,ActivityManagerService会请求zygote进程为应用fork出一个新进程并实例化Activityhread;
调用ActivityThread.bindApplication绑定应用的application
activity启动过程
 activity启动会来到ActivityManagerService类(Binder对象),通过IPC远程调用ActivityThread的内部类ApplicationThread,
 通过scheduleLaunchActivity方法将启动activity的消息交由Handler H处理,
 H会调用handlerLaunchActivity-performLaunchActivity方法创建和启动Activity对象
卸载后打开一个页面
调用c层jni接口,fork一个新进程,负责监听本应用是否被卸载了。Linux中父进程死了,fork复制出来的子进程不会被杀死。
监听data/data/下应用的缓存文件夹是否还存在,不在则调用android浏览器,打开页面

系统里面用到的设计模式

mvp存在问题
  1.代码增多,增加很多类
  2.耦合度高,界面改动,涉及到的接口也要变化;
  3.P持有V对象,如果view销毁时,P还在做耗时操作,可能会内存泄漏。
    解决:在basePresenter加attach和detach方法,attach获取泛型view,detach释放view和中止网络。activity销毁时调用detach即可(可在baseActivity中实现)

https://www.jianshu.com/p/2fd2d5dac94e

mvvm
m-model,v-view,vm-viewmodel。
mvp模式缺陷:p需要持有view对象,要管理生命周期;
            耦合度高,界面改动,涉及到的接口也要变化;
            p有可能臃肿
而mvvm中vm不需要持有v对象,通过databinding可以做到数据驱动,改变数据即可改变ui,解耦和更新ui方便(不用切换到主线程),复用性高
binder过程
服务端通过service提供binder,客户端通过asInterface方法将binder转为代理proxy,并通过它发起远程请求,然后挂起;
binder写入参数,然后调用transact方法,服务端会由onTransact方法处理,将结果写入reply;
最终服务端返回请求结果,唤醒客户端
serializable和parcelable区别
serializable是java自带的,parcelable是android专用的。
serializable优点实现起来简单,只需要实现serializable接口即可。缺点使用反射实现序列化,开销大效率慢易触发GC
parcelable优点效率快,缺点实现复杂,需要实现parcelable接口,然后复写describeContents和writeToPracel方法,实例化静态化内部变量CREATOR。
handler机制
 当调用handler.sendMessage方法时,会通过enqueueMessage方法将发送的message加入消息队列MessageQueue
  Looper.loop方法会一直去轮询MessageQueue.next
  调用handler.dispatchMessage发送消息,handler收到消息调用handlerMessage方法处理消息
handler postdely延时不准
 public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

handler的sendMessageDelayed方法一开始就计算好了执行的时间点是系统启动至今的时间+延时时间,
在MessageQueue.next方法判断消息是否出列时,是根据当前时间和msg的时间判断的

Message next() {
    if (now < msg.when) {
        // Next message is not ready.  Set a timeout to wake up when it is ready.
        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    } else {
        // Got a message.
        ...
        return msg;
    }
}
所以如果上一个消息有耗时任务,占用了延时任务执行的时机,就会不准确
RxJava流程
首先通过observable.create方法创建被观察者,其他操作符最后也是返回一个observable对象;
然后调用subscribe方法绑定观察者,入参observer,方法会来到subscribeActual方法,在里面会创建CreateEmitter发射器对象;
继续执行observable.subscribe(发射器),接着触发事件源发射事件。
而subscribeOn和observerOn两个切换线程的原理,切换到子线程是通过线程池新开一个runnable,在里面执行订阅或观察。
 切换到主线程则是利用参数mainThread返回的handler进来postDelayed方法切换的
路由
现在开发都建议模块化开发,即是多module。好处如下:
1.可以独立模块编译,提升速度
2.方便单元测试
3.有利于团队并发开发
多module带来不好的地方就是多module要跳转的话就要互相依赖,导致耦合度高。为了解决跳转问题所以需要引入路由框架。
路由可以达到类似web的跳转方式,根据路径跳转。将router配置在common module就可以解耦

使用可以考虑阿里的ARouter框架。只要在activity前注入@Route(path = "/com/Activity1") ,    
使用 ARouter.getInstance().build("/com/Activity1").navigation() 就可以进行跳转
启动优化
启动分为冷启动(首次启动),热启动(任务列表存在该进程)。优化方案:
1.在application和mainactivity的初始化不做耗时操作,可以开线程,也可以延时操作
2.用导航图作为windowBackground,替换掉冷启动系统准备时间的白屏或黑屏,优化体验
3.返回键退出处理为home退出,用热启动替换冷启动moveTaskToBack(true)

java

1.java的封装
 是面向对象的重要原则。是将对象的属性和操作包装成一个独立的实体,
 并隐藏对象的内部实现,向外界提供一些可访问的属性操作。
 比如说实体类提供部分属性set方法和get方法
2.java四种引用及使用场景
强引用:默认调用就是强引用,永远不会被GC回收
软引用:当内存不足时,会被GC回收,用于存储一些内存敏感的缓存
       SoftReference<String> softBean = new SoftReference<String>(new String[]{"a", "b", "c"});
       String a = softBean.get();
弱引用:不管内存足否,只要被发现,都会被GC回收,用于存储一些内存敏感的缓存
       WeakReference<String> weakBean = new WeakReference<String>(new String[]{"a", "b", "c"});
       String a = weakBean .get();
虚引用:和没有任何引用一样,在任何时候都可能被垃圾回收器回收。主要用来跟踪对象被垃圾回收器回收的活动。
      ReferenceQueue<String[]> referenceQueue = new ReferenceQueue<String[]>();
      PhantomReference<String[]> referent = new PhantomReference<>("a", referenceQueue);
3.线程start和run区别
 run是同步方法,运行在主线程
 start才是真正实现了多线程

重载和重写(kotlin的重载)

1.ArrayList和LinkedList区别
 ArrayList是基于动态数组的数据结构,而LinkedList是基于双向链表的数据结构
 随机set和get,ArrayList较快,因为LinkedList要移动指针
 add和remove字段,LinkedList较快,因为ArrayList要移动数据
2.循环arraylist删除方法
方法1:普通倒序循环
   public static void remove(ArrayList<String> list) {
       for (int i = list.size() - 1; i >= 0; i--) {  
          String s = list.get(i);
          if (s.equals("bb")) {
              list.remove(s);
          }
      }
    }
之所以要采取倒序,是因为正序的话,删除了第一个“bb”,下一个元素会往左移动,这次遍历就错过了,
如果也是bb就删除不了重复元素。倒序遍历时即使发生元素删除也不影响后序元素遍历。
 
方法2:迭代器循环删除()
     public static void remove(ArrayList<String> list) {
          Iterator<String> it = list.iterator();
          while (it.hasNext())  {
              String s = it.next();
              if (s.equals("b")) {
                    it.remove();
              }
          }
    }
  多线程中不能保证两个变量修改的一致性,结果具有不确定性,所以不推荐这种方法
  如果是用强循环,并用arraylist的remove方法会报ConcurrentModificationException并发修改异常
4.静态方法和实例方法用sync锁是锁住什么
  synchronized加在静态方法是锁类,加在非静态方法是锁对象。
   1. 如果多线程同时访问同一类的类锁(synchronized 修饰的静态方法)以及对象 
  锁(synchronized 修饰的非静态方法)这两个方法执行是异步的,原因:类锁和对象锁是两种不同的锁。 
   2. 类锁对该类的所有对象都能起作用,而对象锁不能。
5.单例最好写法
1.双重检查+volitile
    public class Singleton {
        private static volatile Singleton singleton; //1
        private Singleton() {}
        public static Singleton getInstance() {
            if (singleton == null) { //2
                synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
          }
          return singleton;
      }
  }   
双重检查避免大开销,避免两个线程同时进入2,在同步方法里面在检查一次,保证只实例化一次
volitile避免线程1刚实例化完,线程2走到1时singleton还不是最新的,又实例化一次。
volitile读写时都会到主线程同步最新的变量

2.静态内部类
  public class Singleton {
      private Singleton() {}
      private static class SingletonInstance {
          private static final Singleton INSTANCE = new Singleton();
      }
      public static Singleton getInstance() {
          return SingletonInstance.INSTANCE;
     }
  }
  类的静态属性只会在第一次加载类的时候初始化,在类进行初始化时,别的线程是无法进入的。
  优点:避免了线程不安全,延迟加载,效率高。


3.枚举法
     public enum Singleton {
          INSTANCE;
          public void whateverMethod() {
          }
     }
    不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象
6.注解和反射

https://www.jianshu.com/p/d4978bbce12a

Android任务栈分配

保活

栈倒序冒泡

Service生命周期启动

a++跟++a
 a++是先赋值,再运算。a=0;x=a++先将a赋予x,后再运算a=a+1,所以x=0
 ++a是先运算,再赋值。a=0;x=++a先是运算a=a+1,然后才是x=a
sleep和wait区别
  sleep是thread的方法,没有释放锁
  wait是object方法,释放了锁

hashcode是否相等,当对象不相等

hashmap底层
 HashMap是基于hashing的原理,我们使用put(key, value)存储对象到HashMap中,
 使用get(key)从HashMap中获取对象。当我们给put()方法传递键和值时,我们先对键调用hashCode()方法,
 返回的hashCode用于找到bucket位置来储存Entry对象

克隆,复制一个对象

idhandler(空闲handler) activity什么时候开始显示的

静态方法为什么可以全局调用(不需要外部引用)

单例为什么要双重检查

线程池到达核心数,到达缓存数,参数之一策略

场景:图片上传外再调评论接口(锁)

7.摘星者

transient关键字

rxjava同步方法,取消订阅关系

Glide底层

广汽

HashMap链表什么时候变红黑树

jvm分区

classloader

为什么用binder

volicate解决什么问题

leakcanary原理

mvvm数据混乱怎么办

路由原理

怎么保证一个线程一个looper

上一篇下一篇

猜你喜欢

热点阅读