您的位置:首页 > 移动开发 > Android开发

Handler源码详解及导致内存泄漏的分析

2016-09-05 16:18 531 查看
[TOC]

简介

android的消息处理有三个核心类:Looper,Handler和Message,

主要接受子线程发送的数据, 并用此数据配合主线程更新UI。

部分图片来至CodingMyWorld博客,3Q

使用方法

public class LooperThread extends Thread {
@Override
public void run() {
// 将当前线程初始化为Looper线程
Looper.prepare();

// ...其他处理,如实例化handler

// 开始循环处理消息队列
Looper.loop();
}
}


通过上面两行核心代码,你的线程就升级为Looper线程了,就具备消息处理的功能!

Looper.prepare()



通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。注意,一个Thread只能有一个Looper对象,以下源码使用到ThreadLoacal,可以想象成一个线程的属性/变量,想了解更多请点击链接

public static void prepare() {
prepare(true);
}

private static void prepare(boolean quitAllowed) {

//获取当前线程对应线程变量:Looper,重复执行此方法会有如下报错提示
//
//"Only one Looper may be created per thread"
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}

//将当前初始化的Looper对象保存到当前线程变量中
sThreadLocal.set(new Looper(quitAllowed));
}


Looper.loop()



调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也叫任务)执行。

/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {

//获得当前线程的Looper对象
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}

//获得消息队列
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;
}

// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}

//向目标对象中分发当前循环到的消息体,下面将会具体讲解
msg.target.dispatchMessage(msg);

if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}

// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}

//使用完消息之后需要对它进行回收
msg.recycleUnchecked();
}
}


msg.target.dispatchMessage(msg)
解释

Message
类中查找可以发现

/*package*/ Handler target;


其实target就是handler对象,那handler是如何和一个Message发生联系的,稍等?下面移步Handler源码分析

Handler的创建就已经获取了当前线程的Looper和消息队列

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());
}
}

//获取同一线程的Looper
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;
}


真正Message和Handler关联的地方来了

在我们
sendMessage()
的时候都是先用
obtainMessage
来获取一个Message

public final Message obtainMessage()
{
return Message.obtain(this);
}


移步Message的方法看详细

public static Message obtain(Handler h) {
Message m = obtain();

//是不是so easy,真正的联系在这里
m.target = h;

return m;
}


如果你是使用
post(Runnable r)
来发送消息的,那应该构造一个Message来发出去,不信可以看源码

Handler类中:

public final boolean post(Runnable r)
{
return  sendMessageDelayed(getPostMessage(r), 0);
}

/**
*构造一个Message
*/
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();

//与PostMessage不同的是这个runable是给了callback属性
m.callback = r;
return m;
}


具体消息处理:Handler处理消息

/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {

//处理Runable消息
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}

//处理具体Message,这个是你在消息处理的时候重写的方法实现
handleMessage(msg);
}
}


总结

Lopper–消息的集合和消息的循环

Handler–消息的管理接口和消息的处理

主线程已经持有Looper,所以不需要Looper.prepare()

Activity源码查看:

/*package*/ ActivityThread mMainThread;


ActivityThread源码中查看:

final Looper mLooper = Looper.myLooper();


导致内存泄漏的分析

内存泄漏场景

在一个activity中post已个message

关闭这个activity

由于某些原因这个message开始执行或者正在执行(如上一个message比较耗时/当前message比较耗时),

更严重的是你发送一个延时消息前把activity关闭

参考代码

private Handler mLeakHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Logger.d(msg.toString());
}
};
@Override
protected void onResume() {
super.onResume();

//延迟10s来模拟场景
mLeakHandler.sendEmptyMessageDelayed(0x1,10000);
finish();
}
//省略其他代码


分析及修改方法

由于这个Handler作为内部类声明在Activity内部,普通的内部类对象隐式地保存了一个指向外部类对象的引用,所以这个Handler对象保存了一个指向Activity对象的引用。而这个Handler对象的生命周期可能比Activity生命周期长,比如当有一个后台线程持有该Handler,且该线程在执行一个长时间任务。Handler通过发送Message与主线程交互,Message发出之后是存储在MessageQueue中的,有些Message也不是马上就被处理的。在Message中存在一个 target,是Handler的一个引用,如果Message在Queue中存在的时间越长,就会导致Handler无法被回收。如果Handler是非静态的,则会导致Activity不会被回收。但是注意这个泄漏时临时的!当这个消息处理完引用关系也就不存在了,下次GC的时候也就能回收啦

修改方法:

private static class MyHandler extends Handler {
private WeakReference<Activity> reference;

public MyHandler(Activity activity) {
reference = new WeakReference<Activity>(activity);
}

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
LeakActivity activity = (LeakActivity) reference.get();
if (activity != null) {
Logger.d("activity != null"+activity.toString());
} else {
Logger.d("activity = null");
}
}
}

private final Handler mHandler = new MyHandler(this);


同时你需要在调用一下方法,避免不必要的回调(虽然不会报错了)

@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}


参考文献:

http://www.cnblogs.com/codingmyworld/archive/2011/09/14/2174255.html

http://blog.csdn.net/qjyong/article/details/2158097

http://m.blog.csdn.net/article/details?id=51493352 (代码来源博客)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android Handler