安卓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的关系。

1)Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。 
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的初始化流程如图所示:

技术分享

Hander持有对UI主线程消息队列MessageQueue和消息循环Looper的引用,子线程可以通过Handler将消息发送到UI线程的消息队列MessageQueue中。

3.Handler处理消息

UI主线程通过Looper循环查询消息队列UI_MQ,当发现有消息存在时会将消息从消息队列中取出。首先分析消息,通过消息的参数判断该消息对应的Handler,然后将消息分发到指定的Handler进行处理。

子线程通过Handler、Looper与UI主线程通信的流程如图所示。

技术分享

每一个线程里可含有一个Looper 对象以及一个MessageQueue 数据结构。在你的应用程序里,可以定义Handler 的子类别来接收Looper 所送出的消息。

那么什么情况下面我们的子线程才能看做是一个有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();
}

然后handle可以使用handler.dispatchMessage(msg);handel.sendMessage(),handle.sendEmptyMessage()等就不说了,自己去看代码。

使用注意:

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



郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。