最近打算从源码角度来读一下Handler,MessageQueue,Message,Looper,这四个面试必考项。所以今天先从Handler开始。

一.Handler的作用

源码定义

There are two main uses for a Handler:
- (1) to schedule messages andrunnables to be executed as some point in the future; and
- (2) to enqueuean action to be performed on a different thread than your own.
复制代码
  1. 在未来的某个时刻调用某事件

  2. 线程之间互相通信

这就是Handler设计出来最主要的两种用途

二.Handler的构造方法

1.public Handler()
2.public Handler(Callback callback)
3.public Handler(Looper looper)
4.public Handler(Looper looper, Callback callback)
5.public Handler(boolean async)
6.public Handler(Callback callback, boolean async)
7.public Handler(Looper looper, Callback callback, boolean async)
复制代码

从Handler源码看,Handler构造函数一共7个,不过仔细观察可发现这七个归根到底都是围绕着三个参数而来,即looper,callback,async。

Looper大家应该都知道,就是负责消息进行循环的,每个Handler都必定要对应一个独一无二的Looper。这样你Handler sendMessage以后,你的Message才能进入到对应的Looper去做循环,然后被调用。

Callback,估计大家有点陌生,说实话我之前也没怎么用过。先放上源码

  /*** Callback interface you can use when instantiating a Handler to avoid* having to implement your own subclass of Handler.*/public interface Callback {/*** @param msg A {@link android.os.Message Message} object* @return True if no further handling is desired*/public boolean handleMessage(Message msg);}
复制代码

看源码就是一个简单的接口。

然后再仔细看该接口的调用会明白这是在Handler调用自身handleMessage方法之前做的一次调用,若Callback参数不为Null,则处理消息时会走Callback的handleMessage,而不会走Handler的handleMessage方法。相关源码在接下来的第四模块Handler处理消息的方法中放出。这个具体使用场景欢迎补充。

async,官方文档说明

If true, the handler calls {@link Message#setAsynchronous(boolean)} for each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
复制代码

然后查找哪里使用到了这个参数,发现在消息进入MessageQueue时使用。代码如下

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}
复制代码

相当于所有通过这个Handler来发送消息的Message,若Handler的async为true,则这些Message会被标记为async为true,那么这个Message参数为true或false有什么作用么? 参考这篇文章Android消息处理机制(Handler、Looper、MessageQueue与Message) 以及源码可知,默认的消息该值都为false,若为true,则Message不再受同步障碍影响,即使设置了同步障碍,这些消息也能不间断的被执行,反过来默认的消息若被设了同步障碍,则这个Looper取Message过程则会被同步障碍中断,原因又是一个类似死循环。因为自己没用过,看源码,说在系统View.invalidate代码中有使用,接下来可研究相关使用示例。

(刚看了相关代码感觉挺有意思的下面补充一下)

首先看ViewRootImpl 中scheduleTraversals的代码,刷新开始的地方

 void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}
复制代码

其中

mHandler.getLooper().getQueue().postSyncBarrier();
复制代码

代码往MessageQueue中加入了一个同步屏障(说白了同步屏障是一个特殊的Message)然后由于同步屏障的作用MessageQueue中那些非异步的消息都不进行获取操作了,这么做就是保障刷新的Message能够第一时间得到Looper的调用。 接着看mChoreographer.postCallback的内部代码

   Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);msg.arg1 = callbackType;msg.setAsynchronous(true);mHandler.sendMessageAtTime(msg, dueTime);
复制代码

发现了这个是发送异步Message的代码。由于其他同步Message都无法调用,所以这个异步消息可以第一时间得到调用。 同时绘制代码如下

 void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);复制代码

在绘制代码中通过 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier)这个代码把这个同步屏障给去除了,使整个Looper又恢复正常的调用。

三.Handler发消息的主要方法

Handler发消息的几种方式(之前看某个谈面试心得的时候说过)

1. public final boolean post(Runnable r)
2. public final boolean postDelayed(Runnable r, long delayMillis)
3. public final boolean sendEmptyMessage(int what)
4. public final boolean sendMessage(Message msg)
5. public boolean sendMessageAtTime(Message msg, long uptimeMillis)
6. public final boolean sendMessageDelayed(Message msg, long delayMillis)
复制代码

这些方法最后都是调用的 public boolean sendMessageAtTime(Message msg, long uptimeMillis) 方法。

其中Runnable r被getPostMessage 方法包装成了Message参数

 private static Message getPostMessage(Runnable r) {Message m = Message.obtain();m.callback = r;return m;}
复制代码

四.Handler处理消息的方法

主要为dispatchMessage方法

 public void dispatchMessage(Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}}
复制代码

看代码首先第一步会去取msg中callback字段,该字段在第三部分中(Handler发消息的主要方法)已经提到,是在Handler.post(Runnable r)方法调用中,参数r被getPostMessage方法封装而来。 如果msg中callback为null,则走第二步,调用Handler中参数mCallback中的handlerMessage方法,这个mCallback则是在Handler构造时传入,可参考第二部分。若这个也没有,才会走我们熟悉的第三步调用Handler的复写handleMessage(msg)方法。

其他相关问题:

  1. 由于Looper.loop()方法会进入死循环,那么子线程在Looper.loop()之后的代码是否会被执行?

答:不会

protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_second);initView();new Thread(new Runnable() {public void run() {Looper.prepare();Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg) {Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();}};handler.sendEmptyMessage(1);Looper.loop();Toast.makeText(getApplicationContext(), "after loop", Toast.LENGTH_LONG).show();}}).start();}
复制代码

如上代码进行了测试,after loop 不会被调用,这样就留下了一个坑,会导致子线程一直在循环中不会被回收。所以子线程用了Looper.loop以后一定要调用looper.quit() or looper.quitSafely()进行退出。否则你这个线程就不会被回收,后果就是资源泄漏。

2.The following Handler class should be static or leaks might occur: <classCanonicalName> 内存泄漏

原因:

由于Handler有可能会被Looper#mQueue#mMessages#target引用,而很有可能由于消息还未到达处理的时刻,导致引用会被长期持有,如果Handler是一个非静态内部类,就会持有一个外部类实例的引用,进而导致外部类很有可能出现无法及时gc的问题。

通用解决方法:

直接静态化内部类,这样内部类Handler就不再持有外部类实例的引用,再在Handler的构造函数中以弱引用(当所指实例不存在强引用与软引用后,GC时会自动回弱引用指向的实例)传入外部类供使用即可。

示例代码

public static class WeakUiHandler<T> extends Handler {WeakReference<T> classInstance; public WeakUiHandler(T instance) {classInstance = new WeakReference<T>(instance);}public T getClassInstance() {return classInstance.get();}
}
复制代码

参考资料:

  1. blog.dreamtobe.cn/2016/03/11/… 强烈推荐
  2. www.cnblogs.com/angeldevil/…

从源码角度来读Handler相关推荐

  1. 从源码角度深入理解Toast

    Toast这个东西我们在开发中经常用到,使用也很简单,一行代码就能搞定: 1: Toast.makeText(this, "333", Toast.LENGTH_LONG).sho ...

  2. Mybatis底层原理学习(二):从源码角度分析一次查询操作过程

    在阅读这篇文章之前,建议先阅读一下我之前写的两篇文章,对理解这篇文章很有帮助,特别是Mybatis新手: 写给mybatis小白的入门指南 mybatis底层原理学习(一):SqlSessionFac ...

  3. 【Android 插件化】Hook 插件化框架 ( 从源码角度分析加载资源流程 | Hook 点选择 | 资源冲突解决方案 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  4. 从源码角度解析Android中APK安装过程

    从源码角度解析Android中APK的安装过程 1. Android中APK简介 Android应用Apk的安装有如下四种方式: 1.1 系统应用安装 没有安装界面,在开机时自动完成 1.2 网络下载 ...

  5. 从源码角度解释 fragment 坑(一)

    fragment 自从被Android官方推出以来,就得到了广泛的应用,很多项目中都会使用多个fragment代替Activity进行页面展示,但是由于fragment使用起来相对复杂,如果不是很熟悉 ...

  6. 透析ThreadLocal(以源码角度讲解原理)

    目录 序言 初探ThreadLocal 深入ThreadLocal 拓展ThreadLocal 设置初始值 继承父线程的ThreadLocal 进阶ThreadLocal ThreadLocalMap ...

  7. 从源码角度看CPU相关日志

    简介 (本文原地址在我的博客CheapTalks, 欢迎大家来看看~) 安卓系统中,普通开发者常常遇到的是ANR(Application Not Responding)问题,即应用主线程没有相应.根本 ...

  8. 从源码角度分析MapReduce的map-output流程

    文章目录 前言 流程图 源码分析 1 runNewMapper方法 2.NewOutputCollector方法 2.1 createSortingCollector方法 2.1.1 collecto ...

  9. 从源码角度入手实现RecyclerView的Item点击事件

    转载请注明出处:http://www.cnblogs.com/cnwutianhao/p/6758373.html RecyclerView 作为 ListView 和 GridView 的替代产物, ...

最新文章

  1. Science评论:量子计算目前最大的挑战,在0和1之间
  2. 什么是C#编程语言明明白白学C#
  3. windows下常用命令
  4. 树莓派 ubuntu 安装Python+OpenCV
  5. GAN作用——在我做安全的看来,就是做数据拟合、数据增强
  6. Spring MVC 3 深入总结
  7. print python 如何加锁_深度解密Python单例模式
  8. unix环境高级编程 pdf_UNIX环境高级编程——记录锁
  9. python-copy模块-待优化的功能
  10. vuforia的物体识别能识别大物体吗_衢州sensopart 物体识别检测视觉-灵测信息
  11. 大屏数据可视化源码_数据可视化大屏快速入门
  12. android sudio连接服务器教程,Android Studio连接手机设备教程
  13. 计算机408专业考研真题,2021年计算机考研408历年真题及答案
  14. 机器学习(六)—— 分类
  15. RuntimeError: nms is not compiled with GPU support
  16. 学之思开源考试系统 - 数据库设计文档
  17. 办公软件excel表格_国产表格神器:超脱excel,画表只是基本功能,做软件才是真本事...
  18. edge浏览器整理收藏夹 找不到收藏夹
  19. 【vue】仿淘宝商品详情---实现滚动显隐标签页锚点导航
  20. 利用Flash制作一个可以自由拨动的时钟模具

热门文章

  1. 笨办法学 Python · 续 第五部分:文本解析
  2. IDEA/Git 设置多个push远程仓库或者同时提交多个push仓库
  3. Flex Timer 定时器
  4. 刚装oracle, 熟悉一下命令
  5. CodeForces 86 D Powerful array 莫队
  6. 使用Lucene2.3构建搜索引擎
  7. 鸡年除夕全天微信红包收发量达142亿个增长75.7%
  8. Introduction to random forests
  9. Android第四十一天(3)
  10. Swift - 表格图片加载优化(拖动表格时不加载,停止时只加载当前页图片)