Handler 的基本使用常见问题的源码解析以及运行机制源码讲解
Posted 涂程
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Handler 的基本使用常见问题的源码解析以及运行机制源码讲解相关的知识,希望对你有一定的参考价值。
一、定义
- 一套android消息传递机制/异步通信机制。
二、作用
- 多线程场景下,子线程需要将更新UI操作信息传递到主线程,实现异步消息的处理。
三、使用
使用的方式有 2
种:
- 一种是通过
sendMessage()
的方式来实现异步通信。 - 一种是通过
mHandler.post()
的方式来实现异步通信。
3.1 sendMessage()
方式
1. 创建 Handler
对象,下面列举 3
种方式。
- 匿名内部类
// 在主线程中通过匿名内部类创建Handler类对象。
private Handler mHandler = new Handler(){
// 通过复写handlerMessage方法更新UI
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
// ...... 执行更新UI操作
}
};
- 实现
Callback
接口
// 主线程中通过实现Callback 接口创建 handler 类对象。
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});
- 在子线程中创建
Handler
类对象 (比较少见,基本不用)
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler mHandler = new Handler();
Looper.loop();
}
}).start();
2. 创建 Message
对象,同时 handler
发送消息。
- 创建
Message
对象
// 方式一:
Message message1 = new Message();
// 方式二:
Message message2 = Message.obtain();
// 方式三:
Message message3 = mHandler.obtainMessage();
Message
对象携带数据
Message message = new Message();
message.what = 1; // 消息的标识
// 方式一:通过arg1,arg2 传递
message.obj = "啦啦啦"; // object
message.arg1 = 1; // int 类型
message.arg2 = 2; // int 类型
// 方式二:通过Bundle 传递
Bundle bundle = new Bundle();
bundle.putInt("1",1);
message.setData(bundle);
handler
发送消息
// 方式一:发送普通消息
mHandler.sendMessage(message);
// 方式二:发送空消息,传入what标识值,便于在接收消息中判断。
mHandler.sendEmptyMessage(0);
// 方式三:延时发送消息
mHandler.sendEmptyMessageDelayed(0,1000);
3. handler
接收消息并处理。
private Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
// 通过发送消息时的what标识,对不同消息做判断处理
switch (msg.what) {
case 0:
System.out.println("接收到的消息为空消息");
break;
case 1:
System.out.println(msg.obj.toString());
break;
default:
break;
}
}
};
3.2 mHandler.post()
方式
- 1.创建
Handler
对象。 - 2.在子线程通过调用实例对象的
post()
方法,传入runnable
对象,重写run()
方法,在重写的run()
方法中更新UI
。
private Handler mHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
// 更新UI操作
}
});
}
});
好了,关于
Handler
的基本使用就说完了,下面通过通过一个个的问题以及相应问题的源码解释来加深我们对Handler
的理解,做到知其然知其所以然。
四、常见问题及源码解答
1. 主线程创建 Handler
与子线程创建 Handler
有什么不同 ?
主线程创建 Handler
:
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});
子线程创建 Handler
:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler mHandler = new Handler();
Looper.loop();
}
}).start();
通过对比 ,我们可以看到在子线程创建 Handler
多了两行代码,那么为什么在主线程创建时不需要呢?我们通过源码来解释一下:
应用在启动的时候,会调用 ActivityThread
这个类,在这个类的 main()
方法中,调用了 Looper.prepareMainLooper()
:
// ActivityThread 类中的 main 方法
public static void main(String[] args) {
// ....
// 创建主线程的Looper
Looper.prepareMainLooper();
//...
Looper.loop();
}
Looper.prepareMainLooper();
- 这行代码的含义就是给当前线程初始化一个
Looper
,所以并不是主线程不需要调用Looper.prepare()
方法,而是系统已经做了操作。
Looper.loop();
- 这行代码的含义就是开启主线程的轮询,这是一个死循环,会一直轮询。
读到这儿,可能有人就想问,主线程创建时,系统自动帮我们加了这两行代码,如果我们在子线程创建 Handler
时不加入这两行代码,行不行?答案是否定的,不行,会报异常:
throw new RuntimeException("Only one Looper may be created per thread");
这个时候我们就会思考,为什么系统会抛出异常?还是需要我们从源码中找答案。这个时候我们就需要看看 new Handler()
的时候,在源码中具体做了什么?
public Handler(@Nullable Callback callback, boolean async) {
//.....
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//....
}
从源码中可以看到会给成员变量 mLooper
赋值,一旦没有获取到值,就会抛出我们之前提到的异常,所以我们要看看 Looper.myLooper()
这个操作做了什么?
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//......
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
从源码看出,这个方法的返回值就是 Looper
对象,接着我们看一下 get()
方法,到底这个 Looper
对象是如何获取的?
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
这么一看,我们就知道了,获取当前线程的对象作为 key
值,然后从 ThreadLocalMap
中获取 value
值,从结果来看是没获取到,为什么呢?,我们重新回到 ActivityThread
类中 main()
方法中,点击 Looper.prepareMainLooper()
的 prepareMainLooper()
方法:
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
再点击此方法中的 prepare()
方法:
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));
}
下面是 sThreadLocal.set()
方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
我们将这两个方法对着看,可以看出在应用启动时,ThreadLocalMap
里面会创建唯一一条数据,key
的值就是 主线程,value
值就是主线程的 Looper
对象,而当我们在子线程创建 Handler
的时候,是需要获取 Looper
对象的,那么此时传入的 key
的值是子线程,当然从 ThreadLocalMap
中获取不到 Looper
对象了,因为 key
的值不一样。所以取出的 Looper
对象为 null
,也就抛出了上面的异常。
通过这个问题 可以做以下总结:
- 子线程默认是没有
Looper
的,如果需要使用Handler
就必须为线程创建Looper
。 - 一个线程中必须要有一个
Looper
,也就是说在一个线程中,如果想使用Handler
,必须要有一个Looper
。想要正确在子线程中创建handler
对象,做法如上面创建handler
对象第3
种方法。 Handler
创建的时候会采用当前线程的Looper
来构造消息循环系统,因为应用启动时,ActivityThread
类会初始化Looper
,所以在主线程中默认可以使用handler
,但不能说主线程没有创建Looper
对象。
2. 更新控件内容真的只能在主线程运行 ?
首先给出结论,这句话太过绝对,不一定。不相信的童鞋可以在子线程中更新 UI
,结果可能会让你惊讶,怀疑自我,哈哈,我们以 TextView.setText()
为例,从源码的角度去分析一波:
一路点击 setText()
方法到底,找到最后一个 setText
方法里面的 checkForRelayout()
方法 。点击此方法,直接看到最后面的代码,可以看到不管 if
里面的代码还是 else
里面的代码,最终都会走 requestLayout()
(请求布局) 和 invalidate()
(刷新布局)这两行代码。接着我们就看看 requestLayout()
里面做了什么事?
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
// ------ ViewRootImpl 实现了 mParent 接口 --------
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
// ---------mParent 是一个接口---------
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
ViewRootImpl
恰恰实现了 mParent
接口,也就是会调用 ViewRootImpl
里面的 requestLayout()
方法,那么我们看一下源码:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
里面有一个检查线程的方法 checkThread()
,点击进入:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
此段代码会检查当前线程是否是主线程,不是则抛出异常。
由此可以看出来,我们在子线程中更新 UI
,如果 requestLayout()
这个检查线程的执行速度 慢于 invalidate()
绘制界面的执行速度,不会抛出异常。
我们可以做一个操作来验证一下,在子线程中延迟一秒,更新 UI
,结果报错信息如下:
Process: com.example.handlerdemo, PID: 10569
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.view.View.requestLayout(View.java:23093)
at android.widget.RelativeLayout.requestLayout(RelativeLayout.java:360)
at android.view.View.requestLayout(View.java:23093)
at android.widget.TextView.checkForRelayout(TextView.java:8908)
at android.widget.TextView.setText(TextView.java:5730)
at android.widget.TextView.setText(TextView.java:5571)
at android.widget.TextView.setText(TextView.java:5528)
at com.example.handlerdemo.MainActivity$3.run(MainActivity.java:75)
at java.lang.Thread.run(Thread.java:764)
可以看出来指向的报错地方就是我们阅读源码看到的 ViewRootImpl.requestLayout
这个检查线程的地方。
所以才说更新 UI
的操作只能在主线程,这句话太过绝对了,不一定就是的,要看情况分析。
3. 主线程创建 Handler
两种写法有何区别?
方式一:
private Handler mHandler1 = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});
方式二:
private Handler mHandler2 = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
mTextView.setText(msg.obj.toString());
}
};
用过的人都知道 方式二
有黄色警告,这是谷歌备胎的 api
,不推荐使用。
有了前面的分析,我们都知道在 AcitivityThread
类的 main()
方法中会除了会创建 Looper
对象, 还会调用 Looper.loop()
方法不断的循环消息队列,点击此方法:
// Looper 类
for (;;) {
//....
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
}
}
//...
在这个循环中,会调用 msg.target
的 dispatchMessage()
方法去分发消息,那么这个 msg.target
是什么呢?msg
是 Message
对象,我们进入 Message
类的源码,看到以下代码:
// Message 类
@UnsupportedAppUsage
/*package*/ Handler target;
由此看出 msg.target
就是 Handler
对象,所以其实调用的是 Handler
的 dispatchMessage()
方法。那么我们就看看 Handler
类中的 dispatchMessage()
方法:
// Handler 类
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
if
里面的判断是给 mHandler.post()
方法准备的,我们先不看,直接看到 else
里面的方法, 首先判断 mCallback
是否为空,mCallback
是 Handler
类中的一个成员变量,它是一个接口:
// Handler 类
final Callback mCallback;
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
我们在方式一
中的写法就是传入了一个新建的Handler的callback
对象,所以判断不为空,就会走 mCallback.handleMessage();
方法,也就是我们实现 callBack
接口中的 handleMessage()
方法,在我们重写的方法中,return true
或者 return false
其实并没有区别,为什么这么说 ?
如果我们返回 true
,那么源码中直接 return
, 如果我们返回false ,在源码中就会走下面的 handleMessage()
, 这个是 Handler
类中暴露出来的可以重写的公开方法,但是它的方法名和接口中的方法名一样,我们重写不了,在方式一的 Handler
类中是无法重写 handleMessage()
方法,所以才说写 return true
或者 return false
并没有区别。
如果我们用的是方式二
的写法,那么就会走下面的 handleMessage()
方法。也就是走我们在方式二中重写的 Handler
类中暴露出来的可以重写的公开方法 handleMessage()
。
好了,这两种方式的区别就说完了,现在来说一下之前要忽略的 dispatchMessage()
方法里面的 if
里面的代码 为什么说是给 mHandler.post()
准备的呢?
我们看一下自己写的 mHandler.post()
:
new Thread(new Runnable() {
@Override
public void run() {
mHandler1.post(new Runnable() {
@Override
public void run() {
//.....
}
});
}
});
我们再看一下 Handler
类中的 post()
源码:
// Handler 类
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
可以看到我们传入的 Runnable
的对象, 会调用 getPostMessage(r)
方法将传入的Runnable
的对象进行一个封装。那么这个方法中做了什么事情呢?
// Handler 类
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
可以看到源码中将传入的 Runnable
对象赋值给了 Message.callback
, 而这个 Message.callback
是 Message
类中的 callback
属性,它就是是一个 Runnable
对象:
// Message 类中的
Android培训HandlerThread的使用及源码解析
Android笔记之从源码解析Handler中ThreadLocal的作用以及IntentService解析