内容简介:相信Handler对于作为Android开发者的小伙伴们来说并不陌生,某些阶段的面试中Handler甚至是必考题之一。那么Handler到底有什么用呢?为什么要用Handler呢?我们都知道在Android中分有UI线程和非UI线程,其中有一条规定就是只能在UI线程操作UI组件,这是为了防止多个线程并发的操作一个UI组件带来的问题。同时我们也知道不能在UI线程中做耗时操作,那么这个时候就带来了问题,如果我们要在一个耗时操作结束后再去操作某个UI组件,那要怎么做呢?比如我们需要下载一份文件,并且在文件下载完成
- 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层面,而对这一层面博主并不了解,所以没有能提到这部分的东西,希望以后能有时间补充这部分的内容。以上内容若有错误之处欢迎大家指出,大家一起进步。
以上所述就是小编给大家介绍的《再来亿遍 一遍带你搞懂Android Handler机制》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 快速失败机制 & 失败安全机制
- JavaScript线程机制与事件机制
- 区块链是怎样将分布式组网机制、合约机制、共识机制等技术结合并应用
- Java内存机制和GC回收机制-----笔记
- javascript垃圾回收机制 - 标记清除法/引用计数/V8机制
- Android 8.1 源码_机制篇 -- 全面解析 Handler 机制(原理篇)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。