###Android面试之Handler相关学习
1.Android消息机制之Looper.java源代码学习
1). 准备阶段,在子线程中调用Looper.prepare()方法或者在主线程调用Looper.prepareMainLooper()方法来创建当前的Looper对象
2).Looper对象内部会创建消息队列,代码如下
//Looper.java源代码
// sThreadLocal.get() will return null unless you've called prepare().
//sThreadLocal是当前子线程与绑定的Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
我们在调用Looper.prepare()的时候会调用如下代码
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实例,Looper里面包含有mQueue
}
主线程MainLooper产生的代码,由Android环境调用产生,不需要我们手工调用
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
Looper.loop()代码都做了什么,我们来仔细阅读一下,我会加入响应的一些注释.
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
* 返回当前线程的消息队列,确定在结束Loop的时候调用quit(),后面我们再来分析quit()做了什么
*/
public static void loop() {
final Looper me = myLooper();//获取当前线程的looper
if (me == null) { //looper为空就抛出异常
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取到looper对应的消息队列
final MessageQueue queue = me.mQueue;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//为了方便查看,我删除了一些不相关代码
......
//重点循环,主要的内容在下面
for (;;) {
Message msg = queue.next(); // might block ,取出消息体,有可能阻塞线程,当没有消息的时候会阻塞线程
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//为了方便查看,我删除了一些不相关代码
......
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);//分配消息,其实这个才是最重要的. msg.target是一个handler,handler的分配消息被调用,而且因为这个是在一个for的死循环里面,所以这个就是不停的再获取队列消息并分配出去
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
//为了方便查看,我删除了一些不相关代码
......
}
//为了方便查看,我删除了一些不相关代码
......
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
//为了方便查看,我删除了一些不相关代码
......
msg.recycleUnchecked();
}
}
3).使用线程对应的Handler发送消息
我们可以调用handler的sendMessage()发送到Message到MessageQueue并唤醒阻塞的MessageQueue
2.一个线程能否创建多个Handler?Handler与Looper的对应关系?
我们可以从1中的代码发现,线程与Looper之间的关系,在prepare()方法内,代码的关联是针对线程和Looper的,Handler发送消息的时候是使用enqueueMessage方法,将消息传递到Looper的队列中的,消息到达队列以后,会在loop方法中执行获取消息 Message msg = queue.next(); 方法,并使用 msg.target.dispatchMessage(msg); 分配消息出去,其中,target是handler.
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实例,Looper里面包含有mQueue
}
总结:
一个线程可以创建多个Handler,但是只会有一个Looper并对应一个内含的MessageQueue.Handler与Looper之间没有直接对应关系
Handler通过sendMessage发送消息时,就会将handler对象存储到message中,然后Looper在loop中通过MessageQueue的next方法取出消息后,会通过之前消息封装的handler将消息发送到指定的handler,即通过调用msg.target.dispatchMessage方法
3.Handler引起内存泄露的一些情况以及应对策略
1).在handler的handleMessage内进行了对activity的引用,同时又使用了handler发送延时消息的情况.会在当前引用的Activity销毁的时候引起内存泄露问题,具体情况如下
/**
* 下面的情况会导致handler对Activity的一个引用
*/
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Toast.makeText(DemoActivity.this, "内存泄露测试", Toast.LENGTH_LONG).show();
}
};
//当我们用Handler.sendMessageDelay等方法延迟发送消息的时候,Activity关闭了,这就会导致DemoActivity.this的引用无法被释放.
正确做法如下
//正确情况,先要保证销毁Activity的时候,去除所有的callback和mesage
@Override
protected void onDestroy() {
super.onDestroy();
if (handler!=null){
handler.removeCallbacksAndMessages(null);
}
}
//在handler内是使用引用的时候,也要注意使用弱引用而不是直接使用对应的Activity.这样保证在Activity销毁的时候可以正确被释放,如果是使用runnable,而且是非静态内部类,也会造成类似的内存泄露,所以解决方案类似.
private static class MyHandler extends Handler{
private WeakReference<MainActivity>reference;
public MyHandler(MainActivity activity){
reference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity = reference.get();
if (activity!=null){
Toast.makeText(activity, "测试", Toast.LENGTH_SHORT).show();
activity.tv.setText("测试");
}
}
}