Looper.loop()死循环为啥不卡死
为啥要死循环?
其实是为了保证app能一直存活,简单做法就是可执行代码是能一直执行下去的,死循环便能保证不会被退出。
为啥死循环没卡死进程并且还能处理其他的事务呢?
原因是在创建了新的线程。
也就是说,又这个心开启的线程去处理各种事务,接受android事件的回调、view的touch事件等等。
因此真正出现ANR的是在如这些回调方法onCreate/onStart/onResume,looper.loop本身不会卡死应用。
android源码中是怎样做的?
public static void main(String[] args) {
....
//创建Looper和MessageQueue对象,用于处理主线程的消息
Looper.prepareMainLooper();
//创建ActivityThread对象
ActivityThread thread = new ActivityThread();
//建立Binder通道 (创建新线程)
thread.attach(false);
Looper.loop(); //消息循环运行
throw new RuntimeException("Main thread loop unexpectedly exited");
}
可以看到,通过attach(),会创建一个Binder线程,后面就是这个线程和主线程进行交互。
主线程这样的死循环不是会消耗CPU资源吗?
这里涉及到了Linux pipe/epoll机制。
详解
https://www.jianshu.com/p/7bc2b86c4d89
简单的说法
主线程的MQ在没有消息的时候,便阻塞在MQ的next()方法的nativePollOnce()里。
此时主线程会释放CPU资源进入休眠状态,直到下个消息到达或者有事务发生,通过往pipe管道写端写入数据来唤醒主线程工作。这里采用的epoll机制,是一种IO多路复用机制,可以同时监控多个描述符,当某个描述符就绪(读或写就绪),则立刻通知相应程序进行读或写操作,本质同步I/O,即读写是阻塞的。
activity的生命周期是怎样被触发的?
这个主要就是线程间通信了。
在ActivityThread内部有个H
的handler类,处理线程的通信。当新起的那个线程完成对应的处理,会给H
发送一个消息。
app里大致通信的示意图:
- system_server是系统进程:系统的服务都是运行在这里的,系统服务都实现了IBinder接口
- app进程:app的主线程就是运行在这里的,并且会有其他的线程,至少包括ApplicationThread和ActivityManagerProxy。
activtiy等组件的生命周期的回调就是从ApplicationThread通过handler发送给H的。
为什么用Binder作为进程间通信?
1、Linux下实现进程间IPC的方式
- 管道:
- 消息队列:信息需要复制连词,额外的消耗CPU,不适合频繁大量的信息通信
- 共享内存:无需复制,速度也快,但是进程间同步问题需要解决和安全问题
- 套接字:传输效率比较低,主要用户不同机器或网络的通信
- 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 信号:不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等
2、Binder的优势
- 性能上:Binder数据只需要拷贝一次,主要是采用了mmap的机制。性能上仅次于共享内存。
- 稳定性上:Binder采用C/S的架构,架构清晰,相对独立,稳定性就不叫好;而共享内存则无边界之分,由于同步的问题可能会导致死锁现象。
- 安全性上:上面提到的Linux进程间通信无法知道调用进程的UID/PID,也就无法知道对方的身份。Android为每个安装好的应用程序分配了自己的UID,故进程的UID是鉴别进程身份的重要标志,前面提到C/S架构,Android系统中对外只暴露Client端,Client端将任务发送给Server端,Server端会根据权限控制策略,判断UID/PID是否满足访问权限,目前权限控制很多时候是通过弹出权限询问对话框,让用户选择是否运行。
其他的IPC方式的应用,Android OS中的Zygote进程的IPC采用的是Socket(套接字)机制,Android中的Kill Process采用的signal(信号)机制等等。而Binder更多则用在system_server进程与上层App层的IPC交互。