Android 要点学习笔记(一)Handler消息机制
一、什么是进程、什么是线程,有和区别?
进程和线程的基本定义是:进程是分配资源的基本单位,线程是独立运行和独立调度的基本单位;
通俗的来讲一个应用程序一般就是一个进程(进程名:默认就是包名),我们访问数据是以进程为单位的(一般情况下进程之间是不允许直接访问到对方的数据,除非使用跨进程间通信)
而线程是cpu执行的基本,一个进程可以包含一个或多个线程
二、为什么主线程不能执行耗时操作?
我们在打开一个应用程序时,AMS的startProcessLocked()方法中启动进程时,会为进程创建好主线程,也就是下图:
1.png
传递的"android.app.ActivityThread"即为我们通俗意义上的“主线程”
进程启动的时候,会通过创建ActivityThread并调用其main(String[] args)方法开启主线程,因此进程和主线程是一 一对应的;
大家都知道主线程是用来处理UI渲染绘制的,同时主线程也负责处理AMS对四大组件的调度分发处理,最终完成执行;
如果在主线程中执行耗时操作,线程里的loop循环会阻塞,导致事件停止分发,最直观的就是界面卡住了,阻塞时间超过5秒直接ANR了;
三、为什么子线程无法更新UI界面?
由于Android的UI控件不是“线程安全”的,如果多线程并发访问,可能导致UI控件出现未知问题
(线程安全是指,多线程编程中线程安全的代码会通过同步机制,保证各个线程都能正确执行)
那么为什么UI控件不加同步机制,让它本省线程安全呢?
加锁会使UI访问逻辑变得很复杂,加锁会降低UI的访问效率,因为加锁会阻塞某些线程的执行
四、简述Handler机制?Handler机制的4个要素?
Handler是跨线程通信机制,因为主线程不能执行耗时操作,需要在子线程中执行耗时操作,而子线程中不能直接对UI进行操作;所以当有耗时操作后的UI界面更新时,需要使用线程和Handler跨线程通信机制更新主线程UI界面;
四个要素:
Message(消息):需要被传递的消息
MessageQueue(消息队列):负责存储管理消息,单链表维护,插入和删除有优势;
Handler(消息处理器):负责发送、处理消息
Looper(消息池):负责关联线程和消息的分发,Looper创建时会创建一个 MessageQueue;
Looper.loop()后创建循环持续从MessageQueue那新的消息传递给Handler处理;
五、 Handler机制的流程?
1、应用程序创建进程时,主线程ActivityThread也被创建
在主线程的main()函数中创建mainLooper并执行Looper.loop();
2、四大组件里的Handler创建时
会获取主线程的Looper并拿到Looper的MessageQueue
3、Handler通过sendMessage发送消息,最终调用到queue.enqueueMessage
在消息队列中添加该消息
------------------------enqueueMessage()源码及分析如下:------------------------
boolean enqueueMessage(Message msg, long when) {
if (msg.isInUse()) {//消息是否占用
throw new AndroidRuntimeException(msg + " This message is already in use.");
} else if (msg.target == null) {//msg.target即Handler,不能为空
throw new AndroidRuntimeException("Message must have a target.");
} else {
synchronized(this) {
if (this.mQuitting) {//消息队列的持有者Looper已经quit了
RuntimeException e = new RuntimeException(msg.target + " sending message to a Handler on a dead thread");
Log.w("MessageQueue", e.getMessage(), e);
return false;
} else {
msg.when = when;//延迟时间赋值给msg
Message p = this.mMessages;//消息队列
boolean needWake;
if (p != null && when != 0L && when >= p.when) {
//消息队列不为空,延时不为零,延时大于队列第一个消息的延时时长
needWake = this.mBlocked && p.target == null && msg.isAsynchronous();
//while循环依次比较延时时长,时长越长,插入的位置越靠后
while(true) {
Message prev = p;
p = p.next;
if (p == null || when < p.when) {
msg.next = p;
prev.next = msg;
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
} else {
//反之就是:消息队列为空 或 延时为零 或延时时长小于队列第一个消息的延时时长
msg.next = p;
this.mMessages = msg;
needWake = this.mBlocked;
}
if (needWake) {
nativeWake(this.mPtr);
}
return true;
}
}
}
}
4、主线程执行了Looper.loop(),开始消息循环不断轮询调用MessageQueue.next()
取得队列中下一个消息Message,并调用Handler的dispatchMessage(msg)传递给Handler
MessageQueue.next()函数从消息队列取出消息源码分析如下:
Message next() {
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
while(true) {
while(true) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(this.mPtr, nextPollTimeoutMillis);
synchronized(this) {
long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = this.mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while(msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now >= msg.when) {
this.mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
this.mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
nextPollTimeoutMillis = (int)Math.min(msg.when - now, 2147483647L);
} else {
nextPollTimeoutMillis = -1;
}
if (this.mQuitting) {//Looper.quit(),也就是退出程序了
this.dispose();
return null;
}
………………此处省略………………
this.mBlocked = true;//msg为空,且没有退出程序,Blocked在next()循环中
}
}
………此处省略………
}
5、Handler最终调用重写的handleMessage处理消息
六、Looper.loop()是死循环,为什么不会造成应用卡死?
这是一个很有意思的问题,经过Handler消息机制的深入学习,我们已经知道:
1、一个程序能正常运行、组件能正常调度、界面能正常刷新,后面是消息机制不停在起作用;
2、Looper.loop()循环和MessageQueue.next()循环,就是为了不断把消息拿出,让消息机制不会停止;
3、当消息队列为空时,只是界面和组件暂时不需要更新,从MessageQueue.next()代码可以看到,消息队列为空循环不会停止,只有循环不停止等到有新的消息入列时才会及时取出来;
4、也就是说,恰恰是Looper.loop()的死循环,才能保证消息的及时发出,才能保持消息机制的正常运行;
通过上面的描述我们其实已经知道了,即应用卡死(即ANR)和主线程阻塞是没什么关系的;
ANR全称Application Not Responding,即应用程序无响应,指的是界面阻塞,即消息队列阻塞;
七、那么在主线程里做耗时操作,会造成ANR呢?
ANR,Application Not Responding即应用程序无响应,是界面阻塞
1、应用程序的界面更新,是由主线程的消息处理机制完成的
2、主线程的消息处理机制,是由Looper.loop()和MessageQueue.next()的死循环不断取出消息,来维持消息机制不断更新的
3、主线程中新增耗时操作,就会把Looper.loop()的循环暂停住,等新增的耗时操作完成后Looper.loop()的循环才会继续走下去
4、loop循环停止一段时间,相当于消息机制就停止了一段时间,这段时间内无法响应用户操作,界面UI无法更新
5、当这个时间超过5s,即引发ANR
八、一个线程能否有多个Looper,能否有多个Handler,Handler和Looper之间关系?
1、一个线程只能有一个Looper,并且只有一个MessageQueue被Looper持有
2、一个线程可以有多个Handler,Handler发送的Message会被标记上Target
Handler和Looper是多对一的关系,各个Handler发送的Message会被标记上Target,根据延时长短被先后放入到一个MessageQueue中,在Looper.loop()时依次取出msg,根据msg的target发送回给指定的Handler处理
九、在子线程直接new Handler可以么?需要怎么做?
不可以,看源码可以知道
直接new Handler,会报RuntimeException
"Can't create handler inside thread that has not called Looper.prepare()"
就是,无法在没有 Looper.prepare()之前就在线程中创建Handler
public class Handler {
public Handler() {
this((Handler.Callback)null, false);
}
public Handler(Handler.Callback callback, boolean async) {
this.mLooper = Looper.myLooper();
if (this.mLooper == null) {
throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");
} else {
this.mQueue = this.mLooper.mQueue;
this.mCallback = callback;
this.mAsynchronous = async;
}
}
…………
}
在四大组件中可以直接new Handler是因为,主线程ActivityThread的main()函数中会自动创建Looper对象无需我们自己管理;
在子线程中,正确的方法是:
先Looper.prepare()
然后new Handler()
最后Looper.loop()让消息队列运行起来
不过我们不建议子线程中再加Handler
因为Looper.loop()直接子线程就阻塞了,无法再做其他的耗时操作
6.png
十、Message要如何创建?哪种创建方法最好?
有三种方式:
第一种是新建了实例
后两种方式比较好,直接复用了消息池中已有的Message实例
Message msg1 = new Message();
Message msg2 = Message.obtain();
Message msg3 = handler.obtainMessage();
十一、Handler的PostDelay()方法使用后,消息队列是如何处理的?
查看源码:
postDelayed()直接调用的sendMessageDelayed()
并通过getPostMessage(Runnable r)获取Message实例
最后还是调用到MessageQueue.enqueueMessage()函数将消息添加到消息链表
public final boolean postDelayed(Runnable r, long delayMillis) {
return this.sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0L) {
delayMillis = 0L;
}
return this.sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = this.mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
} else {
return this.enqueueMessage(queue, msg, uptimeMillis);
}
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (this.mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
十二、Handler使用时应注意哪些问题?有什么解决方法?
Handler在使用时很容易遇到内存泄漏问题
在发送消息是无论调用的是handler.sendMessage或handler.sendMessageDelayed最终都会调用到
enqueueMessage()将msg添加到MessageQueue中,如下图源码:
发送出去的msg都会持有handler实例
(这是因为消息执行时,也会根据msg持有的handler实例将msg发回给对应的Handler执行)
package android.os;
import android.os.IMessenger.Stub;
import android.util.Log;
import android.util.Printer;
public class Handler {
…………
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//msg将会持有Handler实例
if (this.mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
}
所以Handler在使用时遇到内存泄漏问题的原因是:
handler发送的延时消息会持有handler实例,而handler我们很多时候是使用的匿名内部类,
java的特性匿名内部类会隐式持有外部类对象的引用
也就是说,如果在延时过程中Activity等组件退出了,msg持有handler,handler会持有activity,
这就造成了activity的内存无法回首,造成activity内存泄漏
解决办法:
Handler声明时使用静态内部类,持有外部类的若应用