关于Handler的理解,子线程不能更新UI的纠正和回调的思考

  开发Android这么久了,总会听到有人说:主线程不能访问网络,子线程不能更新UI。Android的主线程的确不能长时间阻塞,可是子线程为何不能更新UI呢?今天把这些东西整理,顺便在子线程更新UI。java

  首先写了一个handler在子线程更新主线程UI,在子线程作了一个耗时操做:从网络下载了一个图片并利用handler发送到handleMessage()的回调中,并更新到主线程的bitmap。图片显示成功,没有问题。接下来在子线程中更新onCreate()中实例化的textview,报错:android

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.只有建立了这个view的thread才能操纵这个view。Android加载view有两种方式,一是setContentView,二是inflater.inflate()。因此第三步,在子线程中用WindowManager.add()展现了view,run()代码以下:windows

LayoutInflater layoutInflater = LayoutInflater.from(getApplicationContext());
                View view = layoutInflater.inflate(R.layout.test, null);
                TextView textView = ((TextView) view.findViewById(R.id.test_tv));
                textView.setText("十年一剑");
                textView.setTextColor(getResources().getColor(R.color.colorAccent));
                WindowManager windowManager = getWindowManager();
                WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
                layoutParams.height = 600;
                layoutParams.width = 400;
                layoutParams.flags = 2;
                layoutParams.format = 1;
                windowManager.addView(view, layoutParams);

报错: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare().看他这意思,还须要looper。加上后,果真子线程内view显示出来了。尴尬的是,这个WindowManager.LayoutParams的flags和format等属性还没掌握。并且在代码前加上sleep方法view仍能够显示。那么,在onCreate()中的子线程中更新主线程UI呢?安全

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        new Thread() {
            @Override
            public void run() {
                super.run();
                textview.setText("heun3540");
            }
        }.start();
    }

没有问题,子线程更新了主线程的UI,没有报错。但在加了一句Thread.sleep(1000)后,出现了android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.此外在点击事件中new Thread()更新主线程UI,点击后能够看到textview的文本已经改变,随后应用崩掉。网络

  对以上几个例子总结一下:利用handler能够从子线程发送消息到主线程达到更新UI的目的;在子线程里能够更新在子线程中加载的view(须要looper);在主线程的onCreate()中建立的子线程也能够更新主线程的UI,前提是不作其余耗时操做;除外,在onCreate()的子线程作耗时后更新UI报错;子线程没有looper想更新本身的UI也报错;点击事件(能够看作耗时事件)中的子线程更新主线程UI报错。由此能够看出,持有view的线程均可以更改本身的view,主线程默认looper不须要手动添加。通常的更新其余线程的UI须要handler即线程间的通讯但handler只是线程间传递数据,更新操做仍是要rootview来完成。那为何在onCreate()的子线程更新主线程UI没有报错呢?而稍一耗时就报错了呢?必然是由于更新UI快于异常线程检测以致UI更新已经完了可能ViewRootImpl才刚刚初始化完成,但这样是不安全的,你们都不推荐这种方式。并发

妈的,觉得下午换办公室,直接电源断了,一上午写的全没了,日。app

咱们都很是熟悉的生命周期,为何按照onCreate,onStart这样的顺序执行的呢,多是在源码中某个方法肯定了调用顺序,也可能跟堆栈,硬件交互,全局变量有关。less

关于Java中的接口回调,在Android的点击事件和网络请求等有大量的应用。好比项目中有大量重复的工做须要抽取到工具类中,但具体的业务逻辑灵活多变,后者业务后期的维护升级这些都要求代码没法在工具类中一步到位。这时就能够用回调解决,封装的时候调用接口的抽象方法,在业务实现的地方添加相应的代码。那么关于接口的理解,就再也不仅限于初学时的定义了。ide

  接口的出现,一是解决了Java类只能继承一个父类而致使父类过于庞杂,抽象程度不高的问题,二是对于往后代码维护十分的方便。好比说一个项目里有动物,鸟,人,猴子,海豚,飞机这些东西,动物做为抽象类,具体的动物做为实现类,动物有吃,睡觉,走抽象方法,鸟是啄,人是用碗吃。至于鸟和飞机,他们均可以飞,飞就能够设计成接口。工具

  再举个例子,鸟,喜鹊,鸵鸟,孔雀,飞机,战斗机。在这个例子中,鸟是须要抽象的,那么到底抽象成接口仍是抽象类呢?假如是接口的话,走,叫,吃这些能够有,但是鸵鸟不会飞,只有孔雀能够开屏,那就须要再单独设计两个接口,一个包含fly(),一个包含开屏(),三个接口分别选择实现,而飞机只要实现飞这个接口就能够了。假如鸟抽象成抽象类,一样走吃叫设计为抽象方法,剩下的同样,这样看起来接口和抽象类对于鸟来讲没太大区别。这个倒很好解释,由于鸟的几个方法都设计成了抽象方法,假如吃这个方法已经定下来了,显然抽象类更合适。

  此外,按照java的规则,一切皆对象,而类是对象的抽象。那么孔雀是鸟,因此鸟也应该设计成类更合适。套用一句大神的话:类定义了是否是;接口定义了有没有。孔雀继承了鸟,那他就是鸟的一种,而飞机实现了飞,则是具有了飞的能力。须要提到一点是:飞设计成接口,是由于这个行为与吃,叫不是同一类行为,倘若飞和吃放在一块儿,那飞机岂不是得会吃才行。因此也能够看出来,两种不一样属性的行为是不能写到一块儿的。

   而Handler更新主线程的UI也是在主线程中进行的,只不过经过handler对象将子线程等耗时操做中获得的数据利用message传到了主线程。关于handler的原理,老生常谈。温故而知新。今天试着解释一下相关的源码,6.0以上的。

Looper是final修饰,不可继承。

Class used to run a message loop for a thread.

Threads by default do not have a message loop associated with them; to create one ,call #prepare in the thread that is to run the loop,and then #loop to have it process messages until the loop is stopped.

Most interaction with a message loop is through the #Handler class.

Looper类的解释告诉咱们两个主要方法,prepare和loop。主线程不须要显示调用Looper的两个方法。但在子线程中,则须要显式调用。几个全局变量:

 private static final String TAG = "Looper";

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;

ThreadLocal在这里理解为将Looper对象与当前线程绑定,在同一个线程做用域内可见,是一个Java工具类。一个静态的looper引用,一个messageQueue引用,一个线程引用。

/** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
    public static void prepare() {
        prepare(true);
    }
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));
}

prepare方法中传入的布尔值最终传给了MessageQueue的构造方法中,它表明了 True if the message queue can be quit。prepare方法获得了looper对象而且looper在实例化的时候同时获取到当前线程的引用,还会实例化一个成员变量MessageQueue。

Looper中的构造方法是私有的:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

Looper的loop方法,首先会拿到looper和消息队列实例,接着在无限循环中调用queue.next()取出队列的消息,交给msg.target.dispatchMessage(msg)处理。这其中消息的发送正是由handler.sendMessageAtTime()来作。

 1     /**
 2      * Run the message queue in this thread. Be sure to call
 3      * {@link #quit()} to end the loop.
 4      */
 5     public static void loop() {
 6         final Looper me = myLooper();
 7         if (me == null) {
 8             throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
 9         }
10         final MessageQueue queue = me.mQueue;
11 
12         // Make sure the identity of this thread is that of the local process,
13         // and keep track of what that identity token actually is.
14         Binder.clearCallingIdentity();
15         final long ident = Binder.clearCallingIdentity();
16 
17         for (;;) {
18             Message msg = queue.next(); // might block
19             if (msg == null) {
20                 // No message indicates that the message queue is quitting.
21                 return;
22             }
23 
24             // This must be in a local variable, in case a UI event sets the logger
25             final Printer logging = me.mLogging;
26             if (logging != null) {
27                 logging.println(">>>>> Dispatching to " + msg.target + " " +
28                         msg.callback + ": " + msg.what);
29             }
30 
31             final long traceTag = me.mTraceTag;
32             if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
33                 Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
34             }
35             try {
36                 msg.target.dispatchMessage(msg);
37             } finally {
38                 if (traceTag != 0) {
39                     Trace.traceEnd(traceTag);
40                 }
41             }
42 
43             if (logging != null) {
44                 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
45             }
46 
47             // Make sure that during the course of dispatching the
48             // identity of the thread wasn't corrupted.
49             final long newIdent = Binder.clearCallingIdentity();
50             if (ident != newIdent) {
51                 Log.wtf(TAG, "Thread identity changed from 0x"
52                         + Long.toHexString(ident) + " to 0x"
53                         + Long.toHexString(newIdent) + " while dispatching to "
54                         + msg.target.getClass().getName() + " "
55                         + msg.callback + " what=" + msg.what);
56             }
57 
58             msg.recycleUnchecked();
59         }
60     }

在第6行,调用myLooper方法返回了ThreadLocal保存的looper对象:

 /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

第十行将looper实例化时的messageQueue传给了新的引用MessageQueue,十七到二十行是无限循环,queue.next()取出消息。msg.target.dispatchMessage(msg)处理消息,msg.recycleUnchecked()回收资源。到此,消息队列和轮询已经创建,下面应该是发送消息了。那就来看一下Handler的代码:

 

 1 /**
 2  * A Handler allows you to send and process {@link Message} and Runnable      Handler对象容许发送处理和一个线程的消息队列相关联的message和runnable对象。
 3  * objects associated with a thread's {@link MessageQueue}.  Each Handler     每个Handler实例都与一个单独的线程和它的消息队列关联。
 4  * instance is associated with a single thread and that thread's message    当建立一个Handler对象时,它就与建立它的线程和线程的消息队列绑定了。
 5  * queue.  When you create a new Handler, it is bound to the thread /
 6  * message queue of the thread that is creating it -- from that point on,   至此,它就会分发消息和runnable对象到绑定的消息队列,而且在它们从消息队列取出时执行。
 7  * it will deliver messages and runnables to that message queue and execute
 8  * them as they come out of the message queue.
 9  * 
10  * <p>There are two main uses for a Handler: (1) to schedule messages and    handler主要有两个用处:一是安排messages和runnable对象在将来的某一刻执行;
11  * runnables to be executed as some point in the future; and (2) to enqueue   二是为在其余线程执行的动做排队(此处翻译不当)
12  * an action to be performed on a different thread than your own.
13  * 
14  * <p>Scheduling messages is accomplished with the                  借助post和send系列方法,调度消息获得完成。
15  * {@link #post}, {@link #postAtTime(Runnable, long)},
16  * {@link #postDelayed}, {@link #sendEmptyMessage},
17  * {@link #sendMessage}, {@link #sendMessageAtTime}, and
18  * {@link #sendMessageDelayed} methods.  The <em>post</em> versions allow    post容许当Runnable对象被接收且将要被messagequeue调用时为它们排队;
19  * you to enqueue Runnable objects to be called by the message queue when    sendMessage容许为一个包含了数据集且将会被handler的handleMessage方法(须要本身重写)处理的消息对象排队。
20  * they are received; the <em>sendMessage</em> versions allow you to enqueue
21  * a {@link Message} object containing a bundle of data that will be
22  * processed by the Handler's {@link #handleMessage} method (requiring that
23  * you implement a subclass of Handler).
24  * 
25  * <p>When posting or sending to a Handler, you can either            当用post或者send向handler发消息时,能够在消息队列就绪时当即处理也能够指定延迟作延时处理。后者须要实现超时等时间行为。
26  * allow the item to be processed as soon as the message queue is ready
27  * to do so, or specify a delay before it gets processed or absolute time for
28  * it to be processed.  The latter two allow you to implement timeouts,
29  * ticks, and other timing-based behavior.
30  * 
31  * <p>When a
32  * process is created for your application, its main thread is dedicated to  当应用中的进程建立时,主线程致力于运行消息队列。队列着重于顶层的应用组件如活动,广播接收者等和任何这些组件建立的window。
33  * running a message queue that takes care of managing the top-level      能够建立子线程而且经过handler与主线程通讯。和之前同样,是靠调用post或者sendMessage来实现,固然,是在子线程中调用。
34  * application objects (activities, broadcast receivers, etc) and any windows 发出的Runnable对象或者消息就会调度到handler的消息队列中并在恰当时处理。
35  * they create.  You can create your own threads, and communicate back with
36  * the main application thread through a Handler.  This is done by calling
37  * the same <em>post</em> or <em>sendMessage</em> methods as before, but from
38  * your new thread.  The given Runnable or Message will then be scheduled
39  * in the Handler's message queue and processed when appropriate.
40  */

 

说来惭愧,这一段类注释翻译花了好长时间,好歹六级也过了好多年了。从类的注释中得知,handler主要用send和post发送消息,在重写的handleMessage方法处理消息。

全部的send方法底层都是经过sendMessageAtTime实现的,其中在sendMessageDelayed方法中调用sendMessageAtTime时传入了SystemClock.uptimeMills():

 1  /**
 2      * Enqueue a message into the message queue after all pending messages
 3      * before (current time + delayMillis). You will receive it in
 4      * {@link #handleMessage}, in the thread attached to this handler.
 5      *  
 6      * @return Returns true if the message was successfully placed in to the 
 7      *         message queue.  Returns false on failure, usually because the
 8      *         looper processing the message queue is exiting.  Note that a
 9      *         result of true does not mean the message will be processed -- if
10      *         the looper is quit before the delivery time of the message
11      *         occurs then the message will be dropped.
12      */
13     public final boolean sendMessageDelayed(Message msg, long delayMillis)
14     {
15         if (delayMillis < 0) {
16             delayMillis = 0;
17         }
18         return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
19     }
20 
21     /**
22      * Enqueue a message into the message queue after all pending messages
23      * before the absolute time (in milliseconds) <var>uptimeMillis</var>.
24      * <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
25      * Time spent in deep sleep will add an additional delay to execution.
26      * You will receive it in {@link #handleMessage}, in the thread attached
27      * to this handler.
28      * 
29      * @param uptimeMillis The absolute time at which the message should be
30      *         delivered, using the
31      *         {@link android.os.SystemClock#uptimeMillis} time-base.
32      *         
33      * @return Returns true if the message was successfully placed in to the 
34      *         message queue.  Returns false on failure, usually because the
35      *         looper processing the message queue is exiting.  Note that a
36      *         result of true does not mean the message will be processed -- if
37      *         the looper is quit before the delivery time of the message
38      *         occurs then the message will be dropped.
39      */
40     public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
41         MessageQueue queue = mQueue;
42         if (queue == null) {
43             RuntimeException e = new RuntimeException(
44                     this + " sendMessageAtTime() called with no mQueue");
45             Log.w("Looper", e.getMessage(), e);
46             return false;
47         }
48         return enqueueMessage(queue, msg, uptimeMillis);
49     }
50 
51 
52  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
53         msg.target = this;
54         if (mAsynchronous) {
55             msg.setAsynchronous(true);
56         }
57         return queue.enqueueMessage(msg, uptimeMillis);
58     }

handler的enqueueMessage方法中将handler对象赋给了msg的target属性,接着调用了MessageQueue的enqueueMessage方法。MessageQueue也是final类,算是这几个类中比较native的,不少都是与底层交互的方法。在它的enqueueMessage方法中将message压入消息队列,接着loop()方法中msg.target.dispatchMessage(msg),上文已经提到了。Message也是final类。因此最后是调用handler的dispatchMessage方法:

 1     /**
 2      * Subclasses must implement this to receive messages.
 3      */
 4     public void handleMessage(Message msg) {
 5     }
 6     
 7     /**
 8      * Handle system messages here.
 9      */
10     public void dispatchMessage(Message msg) {
11         if (msg.callback != null) {
12             handleCallback(msg);
13         } else {
14             if (mCallback != null) {
15                 if (mCallback.handleMessage(msg)) {
16                     return;
17                 }
18             }
19             handleMessage(msg);
20         }
21     }

因此看到消息最后的处理正是在咱们实例化handler时覆写的HandlerMessage方法,至此,消息发送处理机制走完了。

  那么总结一下:消息机制的过程是,Looper.prepare()实例化looper对象和消息队列,handler实例化得到上一步的looper对象和消息队列的引用,handler.sendMessageAtTime()发送消息到消息队列(这其中包括了给message的target赋值,将message压入到消息队列),Looper.loop()轮询队列取出消息交给message.target.dispatchMessage()处理,实质上是调用了咱们本身重写的handleMessage()。而Android为咱们作了大量的封装工做。开发人员只须要构造message并发送,自定义消息处理逻辑就能够了。

在研究源码时,首先看类注释,接着明确本身的需求,再去找关键方法,千万莫要在庞杂的代码中迷失。

  在探寻源码的过程当中,发现了下一次博客的内容,就是WindowManager.LayoutParams,SystemClock,ThreadLocal,AtomicInteger。

  水往低处流,人往高处走。

相关文章
相关标签/搜索