1. Handler 的作用
在Android为了保障线程安全,规定只能由主线程来更新UI信息。而在实际开发中,会经常遇到多个子线程都去操作UI信息的情况,那么就会导致UI线程不安全。这时,我们就需要借助 Handler 作为媒介,让 Handler 通知主线程按顺序一个个去更新UI,避免UI线程不安全。
那么,子线程要更新UI的信息时,我们就需要将要更新的消息传递到 UI主线程中,再由主线程完成更新,从而实现工作线程对UI的更新处理,最终完成异步消息的处理(如图1所示)。
2. Handler 相关概念解释
主要涉及的有:处理器(Handler)、消息(Message)、消息队列(Message Queue)、循环器(Looper)。
主线程mainThread:当应用第一次启动时就会开启一条主线程。作用:处理UI相关的事件。
子线程:人为手动开启的。作用:执行耗时的操作(如,网络请求等)。
Message:线程间通讯的数据单元(即Handler接受/处理的对象)。作用:存储需要操作的信息
Message Queue:一种数据结构(队:先进先出)。作用:存储Handler发来的消息(Message)
Handler:主线程与子线程的通讯媒介;◆ 线程消息的处理者。作用::◆ 添加消息(Message)到消息队列(Message Queue);
◆ 处理由循环器(Looper)分配过来的消息(Message)。
Looper:消息队列(Message Queue)与 Handler的通讯媒介。作用: ◆ 消息获取:循环取出消息队列(Message Queue)中的消息(Message);
◆ 消息分发:将取出的消息(Message)发送给对应的处理者(Handler)。
3. Handler 有哪些发送消息的方法
sendMessage(Message msg)
sendMessageDelayed(Message msg, long uptimeMillis)
post(Runnable r)
postDelayed(Runnable r, long uptimeMillis)
sendMessageAtTime(Message msg,long when)
MessageQueue 中的 Message 是有序的吗?排序的依据是什么
Queue 都是有序的,Set 才是无序的。排序的依据是 Messag.when 字段,表示一个相对时间,该值是由 MessageQueue.enqueueMessage(Message, Long) 方法设置的。
Message#when 是指的是什么
Message#when 是一个时间,用于表示 Message 期望被分发的时间,该值是 SystemClock#uptimeMillis() 与 delayMillis 之和。
SystemClock#uptimeMillis() 是一个表示当前时间的一个相对时间,它代表的是 自系统启动开始从0开始的到调用该方法时相差的毫秒数 。
子线程中可以创建 Handler 对象吗?
不可以在子线程中直接调用 Handler 的无参构造方法,因为 Handler 在创建时必须要绑定一个 Looper 对象,有两种方法绑定
1先调用 Looper.prepare() 在当前线程初始化一个 Looper
Looper.prepare();
Handler handler = new Handler();
// ....
// 这一步可别可少了
Looper.loop();
2通过构造方法传入一个 Looper
Looper looper = .....;
Handler handler = new Handler(looper);
Handler 是如何与 Looper 关联的
上个问题应该告知了其中一种情况:通过构造方法传参。
还有一种是我们直接调用无参构造方法时会有一个自动绑定过程
// Handler.java:192
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper(); // 就是这里
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Looper 是如何与 Thread 关联的
Looper 与 Thread 之间是通过 ThreadLocal 关联的,这个可以看 Looper#prepare() 方法
// Looper.java:93
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 中有一个 ThreadLocal 类型的 sThreadLocal静态字段,Looper通过它的 get 和 set 方法来赋值和取值。
由于 ThreadLocal是与线程绑定的,所以我们只要把 Looper 与 ThreadLocal 绑定了,那 Looper 和 Thread 也就关联上了
Handler 有哪些构造方法
无参、单Looper、单Callback、Looper和Handler,共4种。
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
this(looper, callback, false);
}
还有一个 boolean 的 async, 不过这个不是开放API,即使不知道个人觉得完全没有问题。
looper为什么调用的是Handler的dispatchMessage方法
看一下dispatchMessage方法
// Handler.java:97
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
从上面的代码不难看出有两个原因:
当msg.callback != null时会执行 handleCallback(msg),这表示这个 msg 对象是通过 handler#postAtTime(Runnable, long) 相关方法发送的,所以 msg.what和 msg.obj 都是零值,不会交给Handler#handleMessage方法。
从上一个问题你应该看到了Handler可以接受一个 Callback 参数,类似于 View 里的 OnTouchListener ,会先把事件交给 Callback#handleMessage(Message) 处理,如果返回 false 时该消息才会交给 Handler#handleMessage(Message)方法。
在子线程中如何获取当前线程的 Looper
Looper.myLooper()
内部原理就是同过上面提到的 sThreadLocal#get() 来获取 Looper
// Looper.java:203
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
如何在任意线程获取主线程的 Looper
Looper.getMainLooper()
这个在我们开发 library 时特别有用,毕竟你不知道别人在调用使用你的库时会在哪个线程初始化,所以我们在创建 Handler 时每次都通过指定主线程的 Looper 的方式保证库的正常运行。
如何判断当前线程是不是主线程
方法一:
Looper.myLooper() == Looper.getMainLooper()
方法二:
Looper.getMainLooper().getThread() == Thread.currentThread()
方法三: 方法二的简化版
Looper.getMainLooper().isCurrentThread()
Looper.loop() 会退出吗?
不会自动退出,但是我们可以通过 Looper#quit() 或者 Looper#quitSafely() 让它退出。
两个方法都是调用了 MessageQueue#quit(boolean) 方法,当 MessageQueue#next() 方法发现已经调用过 MessageQueue#quit(boolean) 时会 return null 结束当前调用,否则的话即使 MessageQueue 已经是空的了也会阻塞等待。
MessageQueue#next 在没有消息的时候会阻塞,如何恢复?
当其他线程调用 MessageQueue#enqueueMessage 时会唤醒 MessageQueue,这个方法会被 Handler#sendMessage、Handler#post 等一系列发送消息的方法调用。
boolean enqueueMessage(Message msg, long when) {
// 略
synchronized (this) {
// 略
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 {
// 略
}
if (needWake) {
nativeWake(mPtr); // 唤醒
}
}
return true;
}
Looper.loop() 方法是一个死循环为什么不会阻塞APP
public static void main(String[] args){
while(true){
// do work in while
}
doSomeThingOutWhile();
}
对于从整个main方法来看,while(true) 确实阻塞了 doSomeThingOutWhile() 这个方法的执行,对于这样看,好像确实是卡住了,因为我们在 doSomeThingOutWhile 方法中想要做的事没法做了,但是如果我们把我们要做的事情通过队列放到 while 里面去做,那么是不是你就不会觉得卡了,你想要做的事情都完成了,虽然有个死循环但并不影响你想要做什么,而Android中 Looper.loop() 就是这样的原理,因为所有让我们会觉得卡住的都被放到 MessageQueue 里,然后通过Looper取出并交给 Handler执行了。
PS:不仅仅是Android,几乎所有和UI操作的都有一个类似Android Handler机制的事件循环处理机制
Handler造成的内存泄露
Handler是由系统提供的异步消息处理的一种常用的方式,一般情况下不会导致内存泄漏,至于其为什么可能会导致内存泄漏,这里的内存泄漏常常指的是泄露了Acitivity等组件
public class HandlerTestActivity extends Activity{
public Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
这个写法有什么问题呢?这个问题就在于该Handler的实例采用了内部类的写法,在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用,造成外部类在使用完成后不能被系统回收内存,从而造成内存泄漏。
生命周期较短的组件引用了生命周期较长的组件。Handler就是一种典型的示例,以上面的代码举例。HandlerTestActivity可能会被泄漏,也就是该组件没有用了,比如调用了finish()后,垃圾回收器却迟迟没有回收该Activity。原因出在该实例的handler内部类引用了它,而该handler实例可能被MessageQueue引用着。其实正常情况下是不会内存泄漏的,除非handler队列等待太久,想要解决handler导致的内存泄漏,你就需要理解泄漏的原因,(handler持有activity,然后message持有handler,然后MessgQuene持有message),理解了这样持有链,那么handler持有activity可以通过弱引用或静态内部类等方式解决,而removeCallbacksAndMessages则是清除后面的引用,其实如果你的handler中没有什么耗时操作,任务完成了handler就会释放Activity的引用就不会导致内存泄漏了;或者你的handler中是一个2秒的操作,在Activity退出的2s后,释放了Activity的引用,这种情况属于短时间内存泄漏,它也会释放的,所以也可以不管,但是如果你的handler中是死循环啥的的话,就会导致内存泄漏了。
解决Handler造成的内存泄露:
1.保证Activity被finish()时该线程的消息队列没有这个Activity的handler内部类的引用。这个场景是及其常见的,因为handler经常被用来发延时消息。一个补救的办法就是在该类需要回收的时候,手动地把消息队列中的消息清空:mHandler.removeCallbacksAndMessages(null);
2.要么让这个handler不持有Activity等外部组件实例,让该Handler成为静态内部类。(静态内部类是不持有外部类的实例的,因而也就调用不了外部的实例方法了)
3.在2方法的基础上,为了能调用外部的实例方法,传递一个外部的弱引用进来)
public class HandlerTestActivity extends Activity {
private static class MyHandler extends Handler {
private final WeakReference<HandlerTestActivity> mActivity;
public MyHandler(HandlerTestActivity activity) {
mActivity = new WeakReference<HandlerTestActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
ShanActivity activity = mActivity.get();
if (activity != null) {
//do Something
}
}
}
4.将Handler放到抽取出来放入一个单独的顶层类文件中。
/**
* 实现回调弱引用的Handler
* 防止由于内部持有导致的内存泄露
* 传入的Callback不能使用匿名实现的变量,必须与使用这个Handle的对象的生命周期一
* 致否则会被立即释放掉了
*/
public class WeakRefHandler extends Handler {
private WeakReference<Callback> mWeakReference;
public WeakRefHandler(Callback callback) {
mWeakReference = new WeakReference<Handler.Callback>(callback);
}
public WeakRefHandler(Callback callback, Looper looper) {
super(looper);
mWeakReference = new WeakReference<Handler.Callback>(callback);
}
@Override
public void handleMessage(Message msg) {
if (mWeakReference != null && mWeakReference.get() != null) {
Callback callback = mWeakReference.get();
callback.handleMessage(msg);
}
}
}
检测内存泄漏的工具:LeakCancry/li:k,Can,krai/
使用方法:在AndroidStudio中添加依赖
dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2'
}
在Application中初始化并单例,就可以监听Activity中内存泄漏情况
@Override
public void onCreate() {
super.onCreate();
refWatcher=LeakCanary.install(this);
}
//增加监测内存泄漏的Leak
public static RefWatcher getRefWatcher(Context context) {
AppContext application = (AppContext) context.getApplicationContext();
return application.refWatcher;
}
private RefWatcher refWatcher;
在Fragment增加监听内存泄漏
public class BaseFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
//增加OOM监听
RefWatcher refWatcher= AppContext.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}