美文网首页
最新Android面试总结

最新Android面试总结

作者: 粥小新 | 来源:发表于2018-12-06 18:32 被阅读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

    相关文章

      网友评论

          本文标题:最新Android面试总结

          本文链接:https://www.haomeiwen.com/subject/opvocqtx.html