Android 线程模型和Looper深入理解

来源:互联网 发布:软件开发咨询 编辑:程序博客网 时间:2024/05/22 20:29

1、Android的单线程模型

当APP启动时,AMS会通过守护进程为APP创建一个独立的进程,在他的静态ActivityThread的静态main方法中启动这主线程(Main Thread)并创建ActivityThread。主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线程。

如果他线程要修改UI时,不能直接修改,需要通过主线程修改,这就是Android的单线程模型。

2、Android 为什么会采用单线程模型

CPU可以分成多个来运转,但是你看到的显示器只有一个,所以 UI 的本质是单线程的。 一般界面 UI 框架向来都是单线程为主,一般常见的程序架构都是一个 UI 线程加若干个非UI 线程/进程。

所以作为一个有显示界面的UI系统,Android必须采用单线程模型。

3、为什么会有looper,循环读取

在Android平台上,主要用到两种通信机制,即Binder机制和消息机制,前者用于跨进程通信,后者用于进程内部通信。

主线程(UI线程)正是利用Looper死循环机制更新UI,但是不会卡死,内部使用了linux的epoll机制。简单一句话是:Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。在线程没有消息处理时,虽然有死循环,但是通过linux I/O阻塞机制让程处于空闲状态,有能力去执行其他操作,所以不会因为looper死循环导致线程卡死,当然主线程的UI也不会卡顿。

所以千万不要再主线程的looper中干坏事,特别是做耗时操作(做耗时的Handler处理),这样会影响UI的更新操作,产生卡顿情况!!

4、保证单线程的方式Message Queue

Android设计出来复杂的message queue + looper模型也是为了其他线程可以通过主线程更新UI。

其他线程也有更新UI的权利,但是必须通过主线程来更新,每个线程都对外提供了Looper对象,主线程也不例外。其他线程把更新操作通过Handler传给主线程的Looper就行了,主线程的Looper会将更新操作放到自己的Message Queue中,排队更新UI

5、Message Queue阻塞

在Message的next方法中的阻塞,nativePollOnce是通过linux层实现的。Native层也有looper和NativeMessageQueue,他们是为消息阻塞服务,NativeMessageQueue负责对外提供nativePollOnce方法并持有Looper,Looper实现具体的I/O阻塞:
int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData)
timeoutMillis参数为超时等待时间。如果值为–1,则表示无限等待,直到有事件发生为止。如果值为0,则无须等待立即返回。

Looper采用的是epoll机制,【epoll机制提供了Linux平台上最高效的I/O复用机制。从调用方法上看,epoll的用法和select/poll非常类似,其主要作用就是I/O复用,即在一个地方等待多个文件句柄的I/O事件】。

有兴趣了解参考nativePollOnce函数分析
以及聊一聊Android的消息机制

Message next() {    int pendingIdleHandlerCount = -1; // -1 only during first iteration    int nextPollTimeoutMillis = 0;    for (;;) {……………………        nativePollOnce(mPtr, nextPollTimeoutMillis);    // 开始阻塞!        …………………………            // 获取next消息,如能得到就返回之。            final long now = SystemClock.uptimeMillis();            Message prevMsg = null;            Message msg = mMessages;  // 先尝试拿消息队列里当前第一个消息          …………………………}

6、同步分割栏和异步Message

这个可以作为兴趣点了解,实际中机会用不到,“同步分割栏”是起什么作用的呢?它就像一个卡子,卡在消息链表中的某个位置,当消息循环不断从消息链表中摘取消息并进行处理时,一旦遇到这种“同步分割栏”,那么即使在分割栏之后还有若干已经到时的普通Message,也不会摘取这些消息了。请注意,此时只是不会摘取“普通Message”了,如果队列中还设置有“异步Message”,那么还是会摘取已到时的“异步Message”的。

在Android的消息机制里,“普通Message”和“异步Message”也就是这点儿区别啦,也就是说,如果消息列表中根本没有设置“同步分割栏”的话,那么“普通Message”和“异步Message”的处理就没什么大的不同了。

也就是说如果有同步分隔栏,并且到达同步分隔栏这里,会直接执行后面的“异步Message”,移除同步分隔栏之后,普通Message仍然会执行。

7、为什么Message不及时Remove会内存泄露

导致内存泄露的根本原因是Message没有被回收,Message队列仍然被静态对象持有。
Context等对象——>Handler——>Message(target)——>Message Queue(链表)——>Looper——>ThreadLocalMap——>Thread.
从引用链可以看到被handler和Message引用的对象最终被Thread持有,如果Message没有被执行,一直在Message Queue中,会一直被Thread持有。如果Thread没有被销毁,会导致其持有的ThreadLocalMap不会被销毁,所以会导致内存泄漏,特别是UI线程,生命周期和APP相同。所以需要在不再使用Message时,要把Message Remove掉,在线程结束时要调用线程looper的quit(boolean isSafe)方法,ThreadLocal有一个Remove的public方法,但是调用不了,因为sThreadLocal不是public的。

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public void remove() {         ThreadLocalMap m = getMap(Thread.currentThread());         if (m != null)             m.remove(this);}

参考:
[1]为什么 Android 的 UI 框架使用单线程模型
[2]为什么说android UI操作不是线程安全的
[3]nativePollOnce函数分析
[4]聊一聊Android的消息机制
[5]Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
https://www.zhihu.com/question/34652589/answer/157834250

阅读全文
0 0
原创粉丝点击