异步消息处理机制-handler详解
- 什么是handler
- handler的使用方法
postRunnable,sendMessage
- handler的使用方法
- handler机制的原理
handler,looper,messagequeue,ThreadLooper四者的关系
- handler机制的原理
- handler引起的内存泄露以及解决办法
非静态内部类持有外部类的引用造成的
- handler引起的内存泄露以及解决办法
1.什么是handler
现象
1.异常1:android.view.ViewRootImpl$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.
- 根本原因是,android的View不是线程安全的,程序主线程负责UI的展示,UI事件消息的分发,主线程也称作UI线程
-
- Handler机制,可以通过handler在子线程发送消息给主线程来更新UI,子线程可以做耗时操作,用handler将结果切换的主线程处理
- 简单讲:handler是更新UI的机制
- 复杂点:在一些场景中,文件的读取,网络数据的获取,这些耗时操作完成后需要在UI线程做出改变,耗时操作在子线程做的,又由于android是不安全的(??怎样的不安全),不能再子线程中更新UI.
-
- 总结:handler通过发送和处理Message和Runnable对象来关联相对应MessageQueue
- 可以让对应的Message和Runnable在未来的某个时间点进行相应的处理
- 让自己想要处理的耗时操作放在子线程,让更新UI的操作放在主线程。
2.handler的使用方法
-
方式1:handler.post(Runnable),2,handler.sendMessage(Message)
步骤:
- 创建:Handler mHandler = new Handler();创建的handler会绑定到当前所在的线程,在Activity中也就是主线程
- 2 发送消息 mHandler.post(Runnable)
-
-
方式2 :sendMessage
- 创建Handler,复写handleMessage方法
- Message msg = Messager.obtain();
msg.what = 1;
msg.arg1 = xx;
msg.arg2 = xx;
- Message msg = Messager.obtain();
- handler.sendMessage(msg);
-
3.handler机制的原理
-
- handler,looper,messagequeue,ThreadLooper四者的关系
逻辑图:
UML图:
ThreadLocal 图见1
#sThreadLocal:ThreadLocal<Looper>
-mMainLooper:static Looper
#mQueue:MessageQueue
#mThread:Thread
----------------
+prepare()
+looper()
+myLooper()
+quit()
Looper
其中+Looper有一个mQueue消息队列
MessageQueue
#mMessages:Message
-------------------
+isIdle():boolaen
#next():Message
+enqueueMessage(msg:Message,when long)
+removeMessage()
MessageQueue
其中MessageQueue有一个mMessages消息
Message
+what int
+arg1 int
+arg2 int
+obj Object
#when long
#target Handler
#next Message
---------------------
+Obtain() Message
+recycle()
Message
其中+Message有一个Handler对象
Handler
#mQueue:MessageQueue
#mLooper:Looper
-----------------------
+dispatchMessage(msg:Message)
+handleMessage(msg:Message)
+obtainMessage():Message
+sendMessage(msg:Message)
+removeMessage()
- Looper是每一个线程所独有,Looper通过looper方法,读取MessageQueue的消息,读到消息后,把消息发送给handler来处理。
- 2.MessageQueue是一个消息队里,只是一个先进先出的方式来管理message。
1.在创建Looper时会同时创建MessageQueue,二者会关联在一起
2.Message是消息对象
-
3.Handler:两个作用:1.发送消息 2.处理消息
- handler只能将消息发送到自己相关的线程,也就是他相关线程的MessageQueue当中
- 而MessageQueue又是跟Looper相关联的,所以说handler要发送消息必须要有一个looper。所以这三者关系到了一起
现象
抛异常:Can‘t craeate handler inside thread that has not called Looper.prepare();
构造方法中:
public Handler(Callback callback,boolean async){
//xx判断
mLooper = Looper.myLooper();
//判断mLooper是否为空,为空 抛异常:Can‘t craeate handler inside thread that has not called Looper.prepare();
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
其他线程使用
Looper.prepare();
Hander handler = new Handler()
Looper.loop();
其中Looper类中mLooper.myLooper(){
return sThreadLocal.get();
}
sThreadLocal 是什么呢
- 就是不同的线程访问同一个ThreadLocal,不管是set还是get方法,他们对ThreadLocal的所有的读写操作仅限于各自的线程内部,这就是为什么handler里面要通过ThreadLocal来保存looper对象,这样他就可以使每一个线程有一个唯一的looper
疑问:这里用的是ThreadLocal的get方法,那set方法什么时候调用呢
其中Looper类中
public static void prepareMainLooper(){
prepare(false);
synchronized(Looper.class){
if(mMainLooper !=null){
throw new IllegalStateException("The main looper has already bean prepared.")
}
mMainLooper = myLooper();
}
}
其中Looper类中
public static void prepare(boolean quiallowed){
if(sThreadLocal.get()!=null){
throw new RuntimeException("Only one Looper may be created per thread")
}
sThreadLocal.set(new Looper(quitAllowed));
}
- 这个set方法,将Looper设置到ThreadLocal中,保证了每个线程looper对象的唯一性
- 这时候整个MessageQueue通过Looper和线程关联上了,这样我们可以在不同的线程访问不同的消息队列
- 回到Handler的构造方法:由于在handler构造方法中创建了looper,接着又根据mQueue = mLooper.mQueue.这样hanlder和MessageQueue关联到了一起,而MessageQueue通过Looper来管理,这是三者最重要的机制。
- 前面说,只能在主线程(UI线程)更新UI,就是因为每一个Handler要与主线程的消息队列关联上,所以一定要在主线程创建Handler(构造方法会将所在线程的mQueue进行绑定),而不能在内部类创建Handler的原因,这样就能将处理Message在主线程执行,就不会抛出最开始的异常
Looper 相关
post(Runnable),sendMessage方法都需要先开启Looper.looper()开启轮询才能走起来,
Looper.loop(){
final Looper me = myLooper();
if(me ==null) //throw new RuntimeException("No Looper;Looper.prepare() wasn't called on the thread")
final MessageQueue queue = me.mQueue;
for(;;){
Message msg = queue.next();
if(msg == null){
return;
}
final Printer logging = me.mLogging;
if()xxx
try{
msg.target.dispatchMessage(msg);
}finally{}
}
}
创建了一个死循环,从消息队列中逐个的获取消息
总结Looper
- Looper通过prepare创建Looper并保存在ThreadLocal中,然后通过loop来进行消息的分发msg.target.dispatchMessage(msg);
- 这个target就是Handler,handler发送消息给消息队列,消息队列执行时交给handler处理,一个是发送消息,一个是处理消息
其中Handler类中
public void dispatchMessage(Message msg){
if(msg.callback !=null){
handleCallback(msg);
}else{
if(mCallback !=null){
if(mCallback.handleMessage(msg)){
return;
}
}
handleMessage(msg);
}
}
- 这个方法是一个中转器(分发器),先判断msg.callback是否为空,
不为空则:
private static void handleCallback(Message message){
message.callback.run();就是调用的线程的Runn方法
}
4.handler引起的内存泄露以及解决办法
-
- 非静态内部类持有外部类的引用造成的
- 在Activity中private Handler mHandler = new Handler();
- 由于不是静态内部类,所以会引用外部即、Activity的引用,
-
- 当Activity要回收时,Handler可能在做一些耗时操作,没有释放引用,导致Activity不能及时销毁
- 解决方式,改用private static Handler = new Handler()
- 处理完使用removeCallBack
- 若在内部类需要引用外部对象,采用弱引用