欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Handler实现

程序员文章站 2022-07-14 17:35:28
...

要了解Handler的话,首先要了解一下Looper与MessageQueue机制

  • looper 是android里面的消息泵,不断的从MessageQueue内抽取消息
  • MessageQueue 存储消息的队列是一个链表

MessageQueue没什么好讲的主要是Looper的机制,需要介绍下,Looper里面有一个比较重要的属性ThreadLocal<Looper>,需要简单介绍一下。ThreadLocal与Synchronized相比较,ThreadLocal是为了隔离共享冲突,不进行线程间通信,而synchronized则是为了解决多线程对相同资源的并发访问问题,进行线程通信,也就是说如果需要同步的资源就不应该使用ThreadLocal,这里需要注意的是以前ThreadLocal存放资源使用Map,JDK1.7后好像是使用一个数组

public T get() {    
// Optimized for the fast path.    
Thread currentThread = Thread.currentThread();    
Values values = values(currentThread);   
 if (values != null) {        
Object[] table = values.table;       
 int index = hash & values.mask;        
if (this.reference == table[index]) {            
return (T) table[index + 1];        
}    
} else {       
 values = initializeValues(currentThread);   
 }    
return (T) values.getAfterMiss(this);
}

Values就是与该线程挂钩的变量集合,然后我们来看看Looper里面的函数,启动一个Looper的时候我们都需要调用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有点static的性质只会被初始化一次 也就保证了一个线程只会拥有一个Looper的一对一关系,每个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();
}

for (; ; )循环里,不断的获取MessageQueue里面的Message,不断的对message进行处理,这里我们看msg.target.dispatchMessage(msg)而这里的msg.target正好是个handler所以我们回过头来看看handler的dispatchMessage函数

public void dispatchMessage(Message msg) {    
if (msg.callback != null) {        
handleCallback(msg);    
} else {       
 if (mCallback != null) {           
       if (mCallback.handleMessage(msg)) {                
            return;            
        }
     }       
 handleMessage(msg);   
 }
}

直接回调了public void handleMessage(Message msg) {}函数,这个函数我们再熟悉不过也就是我们写处理的地方,现在让我们理清整个流程
首先我们new Handler->然后Handler会new 一个looper->looper又会生成唯一的MessageQueue进行绑定->当looper开始工作时他会查询MessageQueue->当MessageQueue有消息时又会回调handler的处理函数
那么我们就可以自己来写一个消息队列了

class LooperThread extends Thread {
public Handler mHandler;
public void run() {  
Looper.prepare();//给线程创建一个消息循环  
mHandler = new Handler() {   
 public void handleMessage(Message msg) {     
// process incoming messages here   
 }  
};  
Looper.loop();
//使消息循环起作用,从消息队列里取消息,处理消息  
}}

写在Looper.loop()之后的代码不会被立即执行,当调用后 mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。Looper对象通过MessageQueue 来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
这样你的线程就具有了消息处理机制了,在Handler中进行消息处理。参考
在了解了handler的基本流程后我们来看看android中怎样用handler来与主线程进行通信

主线程Handler的生成

Android中的线程主要在ActivityThread.Java中,程序入口public static void main(String[] args)

Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {    
sMainThreadHandler = thread.getHandler();
}
if (false) {    
Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

Activity在启动的时候直接生成了一个Looper将主线程与此绑定,在末尾开启了消息查询,所以当我们在Activity里声明了一个Handler变量它所处的当前线程就是Activity的主线程(Thread.currentThread()),也就获得了主线程的消息队列,因为UI是属于主线程的资源和我们当前的handler属于同一作用域里,自然我们可以在主线程的MQ的回调里去操作UI

泄漏问题

上厕所接了个面试好紧张,问到泄漏问题,由于自己没接触过,也被虐了番,赶紧做记录,原来只是停留在使用,许多细节未发现,非常感谢那位面试官啊,仔细想想确实有些未发现的细节,在ActivityThread中虽然做了Handler的初始化,保证了我们后面new的Handler获得同一个looper,但是activity收尾的时候是没有销毁handler,万一这时候MessageQueue里还有任务未完成,那么Activity是持有handler的引用的,因此activity就无法销毁,导致持有的activity资源泄漏,虽然平时使用都会在onDestroy里置空Handler,确实没考虑为啥会泄漏的问题,当然工作线程的就没这个问题
既然知道了原因,那么我们来看看解决方案。
声明Handler为static类;在外部类中实例化一个外部类的WeakReference(弱引用)并且在Handler初始化时传入这个对象给你的Handler;将所有引用的外部类成员使用WeakReference对象。
下次有机会测试看看