安卓Handle的深入剖析和使用
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"><span style="font-size:14px;">在公司开发项目你不能说handle用的不多,反正这种更新主线程的机制是必须要懂的。面试的时候也总会叫你回答handle、looper、MessageQueen和Message的区别,所以你不仅仅只是会用而且必须要知道handle的运行机制。本文参考了很多的博主的文章见解,包括里面的原理和图解(<a target=_blank href="http://blog.csdn.net/itachi85/article/details/8035333">http://blog.csdn.net/itachi85/article/details/8035333</a>)。 </span></span>
handle:是安卓程序的一套更新ui的机制,它也是一套消息的处理机制,所以我们既可以用它来创建、发送消息,也能用它来处理消息,如果我们不遵循这些机制的话,程序就会crash。
那么ui线程是不能直接更新吗?
一般情况是不能的,但是还是有特殊情况:比如直接在子线程里更新textView的值,不会报错但是如果加了Threed.sleep()就会报错,因为textview的源码里面会有Inviliad()一直追溯父类会发现里面有个viewRoot的checkThread()方法,而activity的生成准确的说是OnResume()然后里面会创建rootImp,所以判断一下就会报错。所以还是推荐用handle机制来进行ui刷新。
一、handle是处理Ui线程的消息机制
直接在UI线程中开启子线程来更新TextView显示的内容,运行程序我们会发现,如下错 误:android.view.ViewRoot$CalledFromWrongThreadException: Only the original
thread that created a view hierarchy can touch its views.翻译过来就是:只有创建这个控件的线程才能去更新该控件的内容。
安卓为什么只能通过Handle机制更新ui线程呢?
最根本的原因就是处理多线程的并发问题。假如在一个activity里面有多个线程去更新ui的操作并且都没有进行加锁的机制,那么可能出现界面错乱和线程堵塞。而handle机制给我们一套解决并发问题的机制,提供了Handler 和 Looper 来满足线程间的通信。Handler先进先出原则。Looper类用来管理特定线程内对象之间的消息交换(MessageExchange)。
线程可以分为:ui线程(ActivityThread)和渲染线程(RenderThread):<surfaceView(就包含了这种机制,所以只需拿到getHandle()就能够进行ui的刷新),然后我们用的AsyncTask(AsyncTask的本质是一个线程池,所有提交的异步任务都会在这个线程池中的工作线程内执行,当工作线程需要跟UI线程交互时,工作线程会通过向在UI线程创建的Handler)>.
主线程一般不做耗时操作(如果UI线程花太多时间处理后台的工作,当UI事件发生时,让用户等待时间超过5秒而未处理,Android系统就会给用户显示ANR提示信息,广播10秒,服务20秒)。
那什么是ui线程(主线程)呢?
有时候面试的时候也会问我们安卓的程序入口,通常我们也会说application,但是你一定要知道我们的主线程就是ActivityThread,所以我们就有必要去了解安卓的程序的消息机制了。它管理应用进程的主线程的执行(相当于普通Java程序的main入口函数),并根据AMS的要求(通过IApplicationThread接口,AMS为Client、ActivityThread.ApplicationThread为Server)负责调度和执行activities、broadcasts和其它操作。
那么我们来看下IApplicationThread接口
由上图我们可以看到ActivityThread 有几个比较重要的成员变量,会在创建ActivityThread对象时初始化。
(1)final ApplicationThread mAppThread = new ApplicationThread();
ApplicationThread继承自ApplicationThreadNative, 而ApplicationThreadNative又继承自Binder并实现了IApplicationThread接口。IApplicationThread继承自IInterface。这是一个很明显的binder结构,用于于Ams通信。IApplicationThread接口定义了对一个程序(linux的进程)操作的接口。ApplicationThread通过binder与Ams通信,并将Ams的调用,通过下面的H类(也就是Handler)将消息发送到消息队列,然后进行相应的操作,入activity的start,
stop。
(2)final H mH = new H();
private final class H extends Handler
mH负责处理ApplicationThread发送到消息队列的消息,所以handle是贯穿整个安卓程序的消息机制的传递。
(3). handleLaunchActivity(r, null);
从中就可以看出,这里就将进行启动activity的工作了。
方法中主要调用了这一句:
Activity a = performLaunchActivity(r, customIntent);
(4)performLaunchActivity()
进行了一些初始化和赋值操作后,创建activity。
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
然后调用:
mInstrumentation.callActivityOnCreate(activity, r.state);
这一句就会调用到acitivity的onCreate方法了,就进入了大多数应用开发的入口了,所以handle的重要性不言而喻了吧。
二、Handler的重点剖析
先看handle、looper、MessageQueen和Message的关系。
2)Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。
3) Message Queue(消息队列):用来存放线程放入的消息。
4)线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。
1.Handler创建消息
Handler通过创建消息来完成接收和处理UI的更新。Android消息机制中引入了消息池。Handler创建消息时首先查询消息池中是否有消息存在,如果有直接从消息池中取得,如果没有则重新初始化一个消息实例。使用消息池的好处是:消息不被使用时,并不作为垃圾回收,而是放入消息池,可供下次Handler创建消息时使用。消息池提高了消息对象的复用,减少系统垃圾回收的次数。消息的创建流程如图所示。
2.Handler发送消息
UI主线程初始化第一个Handler时会通过ThreadLocal创建一个Looper,该Looper与UI主线程一一对应。使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。之后其他Handler初始化的时候直接获取第一个Handler创建的Looper。Looper初始化的时候会创建一个消息队列MessageQueue。至此,主线程、消息循环、消息队列之间的关系是1:1:1。
Handler、Looper、MessageQueue的初始化流程如图所示:
3.Handler处理消息
UI主线程通过Looper循环查询消息队列UI_MQ,当发现有消息存在时会将消息从消息队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。
子线程通过Handler、Looper与UI主线程通信的流程如图所示。
那么什么情况下面我们的子线程才能看做是一个有Looper的线程呢?我们如何得到它Looper的句柄呢?
Looper.myLooper(); //获得当前的Looper
Looper.getMainLooper () ; //获得UI线程的Lopper
我们看看Handle的初始化函数,如果没有参数,那么他就默认使用的是当前的Looper,如果有Looper参数,就是用对应的线程的Looper。
如 果一个线程中调用Looper.prepare(),那么系统就会自动的为该线程建立一个消息队列,然后调用 Looper.loop();之后就进入了消息循环,这个之后就可以发消息、取消息、和处理消息。这个如何发送消息和如何处理消息可以再其他的线程中通过 Handle来做,但前提是我们的Hanle知道这个子线程的Looper,但是你如果不是在子线程运行
Looper.myLooper(),一般是得不到子线程的looper的。
三、handel的使用
我们先来看handle的构造:
**
* Use the provided {@link Looper} instead of the default one and take a callback
* interface in which to handle messages. Also set whether the handler
* should be asynchronous.
*
* Handlers are synchronous by default unless this constructor is used to make
* one that is strictly asynchronous.
*
* Asynchronous messages represent interrupts or events that do not require global ordering
* with represent to synchronous messages. Asynchronous messages are not subject to
* the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
*
* @param looper The looper, must not be null.
* @param callback The callback interface in which to handle messages, or null.
* @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
* each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
*
* @hide
*/
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
handle的looper是一定不为空的默认会执行prepareMainLooper()方法,然后放入ThreadLocal池中用来处理多线程,而callBack{}是一个接口的回调,当我们使用的时候调用里面的handleMessage(msg)时是一个带返回值的,我们可以通过对这个返回值值的控制来打断handle的消息传递机制,如果我们返回的是false的话那么就不会打断handle对消息的处理,如果返回值设置为true那么将不会执行handle的消息处理只会执行callback{}里面的handleMessage()方法,不过我们是一般是不会这么去做的。
常见的使用刷新ui的方法:
A:常见方法:(1)runOnUiThread(runnale) (2)view.post()
上述2方法看源码就知道里面也是调用了handle的方法,或者本来就是ui线程里面操作
B、handle刷新ui的方法包括:(1)handle post (2)handle sendMessage,
1 handle post
handle不和Message配套使用的时候,直接使用handle.post(runable)其实你没有看到Message对象作为参数的传入,但是里面已经是通过looper.loop()已经push了一个message对象到消息队列里用来进行ui刷新。你传入一个runnable对象那么Handle就会调用
<span style="font-size:14px;">private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }</span>其实这里面同时调用了sendMessageDelayed(getPostMessage(r), 0)的方法,而后面那个参数赋值为0代表的是delayMillis(延迟毫秒数),可见postDelayed(r,delayMillis)和
handle.post(runable)是同一个方法的不同表现形式。
2 handle sendMessage
使用handle必然是离不开Message
Message对象参数我们必须熟悉里面包括:what,obj,data,target,arg;其中我们通过what来区分不同的消息处理,obj是一个object对象是便于我们传递任一的对象,data是一个bundle对象,target是一个handle对象,arg用于传递一个整数值,所以我们使用时可以根据自己的需要来设置。
Message对象的生成:(1)handle.obtanMessage() (2)new meassage()
这两种方法推荐使用前一个,第一种是一个Message的池来管理
public static Message obtain(Handler h) {
Message m = obtain();
m.target = h;
return m;
}
其中这target就相当于this,然后当池中的Message对象为空的时候就相当于第二种方法
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
使用注意:
1、使用handle的时候如果在子线程去创建,会报handle没有准备的错误
java.lang.RuntimeException: Can‘t create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:121)
at com.cao.android.demos.handles.HandleTestActivity$MyThread$1.<init>(HandleTestActivity.java:86)
at com.cao.android.demos.handles.HandleTestActivity$MyThread.run(HandleTestActivity.java:86)
所以我们就要指定looper对象,使用new handle(Looper.getMainLooper())来处理。
2、.Handler对Activity finish影响。
在开发的过程中碰到一个棘手的问题,调用Activity.finish函数Acitivity没有执行生命周期的ondestory函数,后面查找半天是因为有一个handler成员,因为它有一个delay消息没有处理,调用Activity.finish,Activity不会马上destory,所以记得在Ativity finish前清理一下handle中的未处理的消息,这样Activity才会顺利的destory
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。