教你如何征服面试官系列—–安卓消息处理机制
既然我们要讲消息处理了,就必要知道与消息处理相关的类
- Looper
- Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程:所谓Looper线程就是循环工作的线程
- Handler
- handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息)
- Message
- message又叫task,封装了任务携带的信息和处理该任务的handler
按照思路一点点来 常规的步骤如下
1.创建一个Handler对象: new Handler();
2.发消息 sendMessage();
3.处理消息 handleMessage();
当面试官问我们的时候,我们肯定不能按照上面这样说啊,起码说一下原理吧?
深入解析第一步:创建Handler的时候我们做了什么?
这个时候 我们应该看一下源码了;
在Handler的构造方法中我们发现了如下有价值的代码:
mLooper = Looper.myLooper();//关联当前线程的looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//直接在当前线程关联的looper上插入消息
mCallback = null; //让mCallback为空
通过构造方法呢,我们可以发现:
a:mLooper不能为空,否则会抛异常
b:非常重要的一点!直接把关联looper的MQ作为自己的MQ,因此它的消息将发送到关联looper的MQ上
c:Looper.myLooper();这个可能是不太懂的东西,所以需要研究一下,我为大家讲一下
深入解析第二步:Looper作为一个循环者,是怎样实现整体步骤的?
好吧,继续翻源代码
我们就找到了Loop.myLooper()这个方法,那么发现:
public static final Looper myLooper() {
//在任意线程调用Looper.myLooper()返回的都是那个线程的looper
return (Looper)sThreadLocal.get();
}
是不是发现ThreadLocal又不知道是啥了?解释一下啊:
//其实就是一个成员变量,可以理解为每一个线程中的Looper对象就是一个ThreadLocal
private static final ThreadLocal sThreadLocal = new ThreadLocal();
那我们继续,发现有以下方法:prepare();prepareMainLooper();loop(); private Looper();
这里边翻源码边给大家说明一下。
我们在这个过程中发现了构造方法是私有的private Looper();那么我怎么创建这个Looper对象呢?
构造方法如下:
private Looper() { //每创建一个Looper都会创建一个消息队列以及它所属的线程 mQueue = new MessageQueue(); mRun = true; mThread = Thread.currentThread(); }
每一个Looper都有一个消息队列
prepare()方法:
public static final void prepare() { //ThreadLocal只能有一个 if (sThreadLocal.get() != null) { //如果试图创建的话,就会抛出异常 throw new RuntimeException("Only one Looper may be created per thread"); } //将looper创建出对象,然后定义为ThreadLocal sThreadLocal.set(new Looper()); }
我们在prepare()里面找到了我们想要的答案,并且发现了,一个Thread只能有一个Looper对象
loop()方法: 抽取有用的
public static final void loop() { Looper me = myLooper(); //得到当前线程的loop MessageQueue queue = me.mQueue;//得到当前loop的MQ ....... while (true) { Message msg = queue.next(); // 从消息队列中取出msg //将真正的处理工作交给handler msg.target.dispatchMessage(msg); //回收 msg.recycle(); } }
这个loop()被调用以后,looper线程就真正的开始工作了,不断取出消息,将这些消息通过dispatchMessage(msg)方法交给handler;在这里你可能会说,没有发现handler啊,怎么交给它的?那我们就去找找Message和Handler这两个类吧!
message类中
public void setTarget(Handler target) { this.target = target; }
Handler类中 还是抽取有用的
public void dispatchMessage(Message msg) { ........ //如果没有callback接口 调用handlemessage方法 handleMessage(msg); }
所以到这儿应该理解了,底层调用了 handleMessage()方法,让我们具体实现;
第三步:对Looper做一个总结
- 我们已经看完了Looper的源码,发现这个循环者(消息泵),工作原理是这样的
- prepare()方法将looper创建出对象,然后定义为sThreadLocal,以及一个MessageQueue
- Looper对象通过MessageQueue按照时间顺序来存放消息和事件
- queue.next(); 取出消息 → msg.target.dispatchMessage(msg);提交给Handler。
- 画个图来理解一下
深入解析第四步:谁给消息队列发消息呢?
前面已经提到过了啊,没错!就是Handler:添加消息和处理消息的角色(只处理由自己发出的消息),默认的构造方法:
public Handler() {
.....
//默认关联当前线程的looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//直接在当前线程关联的looper上插入消息
mQueue = mLooper.mQueue;
//让Callback为空
mCallback = null;
}
所以!!!一个线程可以有多个Handler,但是只能有一个Looper! 画图理解
好吧!那我们来具体的分析一下如何发消息
第一类:sendMessage(Message)类
- 有4种直接发送Message对象的方法,稍微带大家看一下
第二类:post(Runnable);
- 有3中通过Runnable对象转换成Message对象,间接发消息的方法,以post为例,走一下源码
- post(Runnable r)→getPostMessage(r)→interface Callback→handleMessage
- 上个项目的RollViewPager里面
其实以上两类是一种方式:Handler发消息的方式
第三类:runOnUiThread
咦?发现在Handler中没有,原来他在Activity中!
public final void runOnUiThread(Runnable action) { //如果不是UI线程就post出去 if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { //如果是UI线程就直接run action.run(); } }
特殊的更新UI的方式:View.post()
public boolean post(Runnable action) { Handler handler; if (mAttachInfo != null) { handler = mAttachInfo.mHandler; } else { // Assume that post will succeed later ViewRoot.getRunQueue().post(action); return true; } return handler.post(action); }
深入解析第五步:对Handler做一个总结
- handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper;
- 通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),
- 整个过程是异步的。
- 画出整个流程图
深入解析第六步:Message的一些细节
消息类(Message)被存放在MessageQueue中,一个MessageQueue中可以包含多个Message对象
不要用构造方法而是用Message.obtain(),消息池,复用,提高性能
- message只需要携带简单的int信息,请优先使用Message.arg1和 Message.arg2来传递信息,这比用Bundle更省内存
来了解一下AsyncTask,封装好的异步处理任务的工具
1、是一个抽象类,如果想使用它,酒席须创建一个子类去继承它,继承时指定三个泛型参数 Params 执行AsyncTask时需要传入的参数,可用在后台任务中使用 指定为void(不需要) Progress 如果需要在界面上显示当前的君度 指定为Integer(整型) Result 如果需要对结果进行返回 指定为Boolean(布尔型返回结果) 2、四个方法 onPreExecute() 开始之前的初始化,薄如显示一个对话框 doInBackground(Params。。。) 处理所有的耗时任务 可以执行publicProgress(Progress..方法) onProgressUpdate(Progress..) 更新UI操作 3、不要忘了 .execute();启动这个任务
author:花落惊雨辰 谢谢大家观看 end;