目录
- Handler的作用和简单使用
- 从源码看Handler的原理
- 一些知识点的整理
- 总结
Handler的作用和简单使用
相信Handler对于作为Android开发者的小伙伴们来说并不陌生,某些阶段的面试中Handler甚至是必考题之一。那么Handler到底有什么用呢?为什么要用Handler呢?
我们都知道在Android中分有UI线程和非UI线程,其中有一条规定就是只能在UI线程操作UI组件,这是为了防止多个线程并发的操作一个UI组件带来的问题。同时我们也知道不能在UI线程中做耗时操作,那么这个时候就带来了问题,如果我们要在一个耗时操作结束后再去操作某个UI组件,那要怎么做呢?比如我们需要下载一份文件,并且在文件下载完成之后需要让TextView显示下载完成来提醒用户。
这个时候就轮到我们的Handler出场了:
public class MainActivity extends AppCompatActivity {
TextView tv;
private static final int MSG_DOWNLOAD_FINISH=10;
//创建Handler,同时会发现as会提示不建议我们这么写,这个稍后再说
private Handler handler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MSG_DOWNLOAD_FINISH:
tv.setText("下载成功!");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv=findViewById(R.id.tv);
tv.setText("downloading ...");
new Thread(new Runnable() {
@Override
public void run() {
try {
//这里模拟一个耗时操作,可以看到这里是在非UI线程运行的
Thread.sleep(3000);
Message message = handler.obtainMessage(MSG_DOWNLOAD_FINISH);
message.sendToTarget();
//上面部分也可以这么写,效果是一样的
// Message message=new Message();
// message.what=MSG_DOWNLOAD_FINISH;
// handler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
复制代码
运行上面的代码我们可以发现在子线程sleep 3s后,成功的修改了tv的文字为 下载成功。我们成功的满足了Android对UI线程的两条规定,我们凭借Handler成功的在子线程做耗时操作并且在完成之后更新UI界面。
到此我们不然发现Handler可以做线程直接的通信使用,通过Handler发送的一个Message,UI线程就能收到子线程要传达的消息,那么Handler为什么能做到线程间的通信呢?
从源码看Handler的原理
我们先来对Handler的工作流程有一个整体的了解
假设有AB两个线程。在A线程创建一个Handler对象h,并且把h发送给B线程。此时A线程中通过Looper.loop()达到死循环不停的从A线程的MessageQueue中尝试获取一个Message,如果获取到Message就会通过message.target获取到A线程中创建的h,并调用h.dispatchMessage()方法(注意这一部分都是在A线程中进行的)。这时B线程获取到h对象和通过h.sendMessage(msg)将一个msg插入到了A线程的MessageQueue中,这样子一个流程就完成了。
再看源码之前我们先说下Handler的整体结构,话不多说看图。
可以看到主要涉及到三个类
- Handler类,Handler类内部会持有一个MessageQueue对象和一个Looper的实例,这两个实例都是和线程相关的,在调用Handler构造函数并且没传入Looper参数的情况下,默认就是当前线程的Looper和MessageQueue
- Looper类,通过Looper.prepare()和Looper.prepareMainLooper()这两个静态方法构建实例,后者是用来初始化Main线程(也就是UI线程)的Looper的,我们在应用开发中不要使用这个方法,会报错哦。
- MessageQueue类。一个链表实现的消息队列,由Looper负责初始化并被当前Looper对象持有。因为Looper对象是和某个线程绑定的,所以MessageQueue也是和线程绑定的。即一个线程只能有一个Looper和MessageQueue实例,并且不同线程的Looper,MessageQueue不同。
值得注意的是线程不是一开始就拥有和自己绑定的Looper,MessageQueue的,在使用Handler之前需要我们去调用Looper.prepare()方法来初始化当前线程的Looper,MessageQueue对象。我们可以看下这个方法做了什么事情:
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
复制代码
可以看到Looper.prepare方法最终会构建一个Looper对象并放入ThreadLocal中。关于ThreadLocal小伙伴们可以简单理解为不同线程从同一个ThreadLocal中获得的对象是不同的,是独属于自己线程的。
通过上述的代码我们也能够发现同一个线程只能拥有一个Looper对象。 看到这里的小伙伴可能会奇怪了,在我们上面的简单使用中我们并没有调用Looper.prepare(),为什么还能使用Handler呢?
这是应为Android系统在启动当前APP创建出UI线程后就会去执行Looper.prepareMainLooper()方法,系统已经帮我们初始化过了UI线程的Looper,所以我们可以直接使用Handler对象了。
而说了Looper.prepare()就不得不提下Looper.loop()方法了。事实上我们要想在子线程成功的new 出Handler并且顺利使用的话,必须要再调用下Looper.loop()方法。 loop方法是一个是建立一个死循环,不停的尝试从当前的MessageQueue中获取一个Message。
Looper.loop()
public static void loop() {
//myLooper()就是通过ThreadLocal获取当前线程的Looper实例
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
//...省略部分代码
for (;;) {
/**
* 通过MessageQueue获取一个msg。这里小伙伴会问了不是说好了是循环的吗?
* 但是这里如果queue.next()返回为空不就return掉了吗?
* 这里大可放心,queue.next()内部又是一个死循环,只有当以下情况是才会返回空
* 1.调用MessageQueue的quite方法
* 2.Application某些情况下尝试去重启looper(这部分存疑)
* 博主只发现这两个情况,有其他情况欢迎指出
*/
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//....
try {
//msg.target就是发送该msg的Handler对象
/**
* 调用Handler的dispatchMessage分为三种情况
* 1.当msg含有Callback时候会调用msg的callback
* 2.当msg不含有Callback但是Handler有设置Callback时候会调用Handler的callback。如果Handler的callback返回为true的话就不会在执行handleMessage方法
* 3.不满足以上两者时会调用Handler的handleMessage方法,也就是我们例子中实现的方法
*/
msg.target.dispatchMessage(msg);
} finally {
//...
}
//...
//这里可见Message在使用之后会被清空数据并缓存
msg.recycleUnchecked();
}
}
复制代码
调用Handler的dispatchMessage的三种情况
- 1.当msg含有Callback时候会调用msg的callback
- 2.当msg不含有Callback但是Handler有设置Callback时候会调用Handler的callback。如果Handler的callback返回为true的话就不会在执行handleMessage方法
- 3.不满足以上两者时会调用Handler的handleMessage方法,也就是我们例子中实现的方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
//这里的callback是一个Runnable。
//调用handler.post(new Runnable())方法就是生成一个带callback的msg并投入到MessageQueue中
message.callback.run();
}
复制代码
handler.sendMessage(msg)
看完了取出Message并处理的操作,我们看看发送Message部分的逻辑。 sendMessage(msg) ->sendMessageDelayed(msg, 0) ->sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis) ->enqueueMessage(queue, msg, uptimeMillis) ->queue.enqueueMessage(msg, uptimeMillis) 可以看到上面这一串调用链之后最终会调用MessageQueue的enqueueMessage方法。而这上面这一串调主要做了一些常规的检测操作,同时把当前的Handler赋值给msg.target。这部分不多说,我们重点看入队操作
boolean enqueueMessage(Message msg, long when) {
//常规性检测,注意下msg.isInUse判断,代表一个msg只能入队一次
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
//如果调用过stop,此时判断就会为true
if (mQuitting) {
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//接下来这部分就是关于单链表的操作,相信没什么好说的(看不懂的小伙伴要补习下数据结构知识哦)
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
复制代码
看到这里,小伙伴们应该能了解Handler的整套工作流程了。关于Handler中的线程切换,如果有点迷糊的话可以这么想 不管Handler被传递了什么线程,不管是在什么线程发送的消息。最终对该消息的处理都是在最初创建Handler的线程上。
一些知识点的整理
为什么主线程中使用Handler不需要初始化Looper
因为Android系统在启动APP的时候已经调用过Looper.prepareMainLooper();和Looper.loop()了 ActivityThread.java的main方法
public static void main(String[] args){
...
Looper.prepareMainLooper();
//初始化Looper
...
ActivityThread thread = new ActivityThread();
//实例化一个ActivityThread
thread.attach(false);
//建立Binder通道
...
Looper.loop();
//主线程进入无限循环状态,等待接收消息
}
复制代码
为什么主线程中Looper.loop()开启死循环不会造成APP无响应
这部分参考知乎上的一个答案 Android中为什么主线程不会因为Looper.loop()里的死循环卡死?
Handler的内存泄漏
这个问题我在最初的例子中写到了,as不建议我们这么写Handler,这是应为非静态内部类会持有外部内的引用。那么Handler将会持有Activity的引用,我们知道handler是会被msg.target持有的,而msg又在MessageQueue队列中,那么当消息队列中拥有未消费的Message时,会导致Activity即使finish了也无法被GC回收,最终导致内存泄漏。为了避免这个问题我们可以将Handler写成外部内或者静态的内部类,并且传递的Activity引用可以用WeakReference弱引用来持有,同时可以在Activity的onDestory中使用Handler.removeCallbacksAndMessages(null);来清空消息队列
总结
由于Handler还有一部分涉及到native层面,而对这一层面博主并不了解,所以没有能提到这部分的东西,希望以后能有时间补充这部分的内容。以上内容若有错误之处欢迎大家指出,大家一起进步。