【Android面试周】快问快答之 Android 篇

更新时间:2018/03/21


  • 多进程有什么好处
  • 跨进程通信方式
  • Activity保活思路
  • Binder通信原理
  • 什么是用户态、内核态
  • 什么是IdleHandler,有什么用?
  • HandlerThread,IntentService有什么用途?
  • Handler里的ThreadLocal是什么,有什么用?
  • Dalvik vs ART vs JVM
  • Handler造成内存泄漏的原因
    ...

多进程有什么好处

  • 稳定性:子进程的崩溃闪退不会影响主进程;
  • 不占用主进程的内存,如一些大图展示;
  • 安全隔离,不会因为子进程的安全漏洞危害主进程;
  • 基于多进程可以做到Activity级别的保活。

跨进程通信方式

  • 同步AIDL方式:创建AIDL接口,自动生成代理类,通过ServiceManager对外部进程提供Service,通过Bindler实现数据通信;缺点是:每次添加一个接口都要修改AIDL文件,如果通信接口很多就比较麻烦;
  • 异步Messager方式:底层依赖AIDL,实现了异步跨进程通信;
  • ContentProvider方式:提供对外进程的数据Query,类似数据库方式,

Activity保活思路
点击后退键时,调用moveTaskToBack而不是finish,再次进入时,如果仍在内存就调用moveTaskToFront。如果二次打开有新的参数,可以用 onNewIntent。

Binder通信原理
Binder主要用于多进程间通信,即函数互相调用和数据传递。涉及到以下几个元素:

  1. ServiceManager;每个进程如果能对外提供函数接口,需要向底层ServiceManager进行注册,告诉它你的接口能力;
  2. Client进程想要调用Server进程某个接口时,需要去请求ServiceManager,这时ServiceManager会生成一个动态代理对象直接返回给Client进程去使用,当Client进程发起函数调用后,SM会去找到Server进程,并调用它的对应函数。

《Binder学习指南》

什么是用户态、内核态
首先要了解“进程隔离”,由于进程使用的是虚拟空间,这让每个进程都认为自己占用了整个系统的所有资源,实际上进程之间的物理空间是互相不可见的。这可以提高进程的安全性,互相隔离。

对于Linux系统而言(Android基于Linux),用户空间就是应用程序进程所能访问到的虚拟空间,而内核空间则是高安全性、受保护的物理空间。用户空间无法直接访问内核空间,除非通过系统对外提供的安全系统调用这个统一入口。

当用户进程在执行系统调用进入内核空间时,处于内核运行态;
当用户进程在执行自己的代码时,处于用户态。

什么是IdleHandler,有什么用?
我们知道,Looper会不断从MessageQueue里拿Message进行处理,当MessageQueue为空时,Looper便进入了Idle状态,这时它就会取出IdleHandler来处理。

比如当用户停住某个界面无手势交互时,主线程进入空闲,其Looper就会去执行IdleHandler,很多优先级不高的事可以在这里去执行,比如,就可以让IdleHandler做一些预加载的事情。

Looper.myQueue().addIdleHandler(new TestIdleHandler()); // 向Looper中添加一个IdleHandler
    class TestIdleHandler implements MessageQueue.IdleHandler{  
        /** 
         *返回值为true,则保持此Idle一直在Handler中,否则,执行一次后就从Handler线程中remove掉。 
         */  
        @Override  
        public boolean queueIdle() {  
            Log.d("android-interview","空闲线程开始执行!");  
            return true;  
        }  
    }  

例子:在LeakCanary里,当Activity OnDestroy后,会把该Activity的WeakReference存起来后面来检测是否已回收。为了不影响主线程运行,LeakCanary选择了IdleHandler,在主线程空闲时去检测Activity的回收情况。

HandlerThread,IntentService有什么用途?
HandlerThread本质是一个线程,不过我们知道线程在运行完特定任务后会自动结束,而HandlerThread 就像一个持续运行的等待任务的线程,随时有任务都可以让他来处理,而且特别适用于跨线程任务。它内部使用了一个Looper来持续遍历MessageQueue,每次有任务了就可以通过Handler向里面发送Message即可。

IntentService本质是一个Service,不过我们知道常规的Service存在两个问题:

  • 默认在主线程执行;
  • 不会自动关闭,需要手动停止。
    由于我们使用Service一般是用来执行一些耗时任务,所以我们希望它在后台自动执行,执行完后自动关闭,这就是 IntentService。它内部维护了一个 HandlerThread,运行在非主线程里,可以持续处理外部消息,当所有任务执行完毕,会自动停止。

Handler里的ThreadLocal是什么,有什么用?
ThreadLocal本身用来包装某个变量,这个变量的值在不同的线程里是不同的。其实了解了原理就很简单,看似ThreadLocal包装的是单个变量,而实际上内部是一个Map,key是某个线程,因此每次在取ThreadLocal的值时,会根据不同的线程取出属于它的值。

在Looper里用到了ThreadLocal,首先我们知道,Looper是每个线程最多只有一个的,(主线程默认会创建一个Looper,而子线程可通过Looper.prepare()来创建),这与ThreadLocal的特性是一致的,通过一个ThreadLocal<Looper>就可以实现为不同线程提供一个对应的Looper对象

apk是如何打包的?

  • 资源文件:通过aapt打包得到一个R.java文件,其实是资源文件索引表,有了这个以后可以在Java运行时查找到某个资源;(aapt: Android Asset Packaging tool)
  • AIDL文件:这些App是对外进程提供的接口,会生成对应的interface文件;
  • Java代码:常规Java编译成 .class 文件 ->通过 dx 工具打包成 Android系统识别的 dex 文件;
  • APK builder:有了dex文件和编译的资源文件,就可以打包成apk了;
  • 签名Jarsigner:为了防止别人跟你打包一个一样的apk,你需要对apk进行签名表明你才是正版;
  • zipalign:对签名后的apk文件进行对齐处理,使apk中所有资源文件距离文件起始偏移为4字节的整数倍,从而在通过内存映射访问apk文件时会更快。

最终得到一个Signed and Aligned .apk

aar与jar有什么区别?
jar是普通java打包生成的,只包括.class和清单文件;
aar除了.class文件,还包括AndroidManifest.xml、资源文件、res文件、R文件等;

Dalvik 垃圾回收机制

  • Dalvik 的 GC 类型:
    • GC_FOR_MALLOC:分配对象时发现内存不够引发GC;
    • GC_CONCURRENT:空闲是可用堆内存小于阈值;
    • GC_BEFORE_OOM:在OOM之前最后的挣扎回收;
    • GC_EXPLICIT:应用程序显示调用System.gc(), Runtime.gc()去触发回收。
  • 回收方式:
    • 非并行回收:在回收时会锁住堆,如果其他线程需要使用堆,会被一直挂起到GC结束,这就是Stop-the-World;不过不访问堆的线程不会受到影响;
top Created with Sketch.