Android Handler 原理初探

前言

Android 开发过程中,遇到了冗长的耗时的操作,亦或是为了使得代码结构更加清晰,或者是要动态的更新UI。一言不合就上Handler,这里不讨论java编程时的一些多线程模型,只探讨一下Android中提供给开发者使用的Handler。

经典使用方法

关于Handler/Looper/Message之间的关系,在上一篇博文 Android Handler机制初探 中有较为详细的描述。这里打算说人话,对照代码来解释一遍。

代码写法

new Handler对象

Handler myHandler = new Handler() {  
          public void handleMessage(Message msg) {   
               switch (msg.what) {   
                    case MESSAGE_WHAT:   
                         //do something
                         break;   
               }   
               super.handleMessage(msg);   
          }   
     }; 

自定义Thread

class myThread implements Runnable {   
          public void run() {  
               while (!Thread.currentThread().isInterrupted()) {    
                    Message message = new Message();   
                    message.what = MESSAGE_WHAT;   
                    myHandler.sendMessage(message);   
                    try {   
                         Thread.sleep(100);    
                    } catch (InterruptedException e) {   
                         Thread.currentThread().interrupt();   
                    }   
               }   
          }   
     } 

通过上面两步,通常情况下,我们就可以正常的使用Handler了。但,这不是本文的目的;不是告诉你用法就行了,再说了这些东西Google一下立马就出来很多,说不定比我的还好。打算在深入一下,到底为什么可以这样做!

子线程更新UI

使用Handler作为子线程更新UI可能是Handler用法中最为常用、经典的了。但是问题是为什么要这么做?为什么是我?其他人呢(其他方式)?

在更新UI时为什么要用到子线程,都有哪些常用的更新UI的方法

如果不在UI线程中更新,而新开启一个线程处理逻辑,然后在子线程中更新UI线程;会面临线程安全问题,再说了,Android压根儿就不让你这么高。吧所有的操作都放在UI线程中到不是不可能,但当遇到高耗时的操作时,你能等得了?程序会崩溃的呀。。所以,还是Handler大法好。常见的更新UI的方法有如下几种

Handler.post(Runnable)
Handler.sendMessage()
View.post(Runnable)
AsyncTask
Activity.runOnUiThread()

会用到的文件

//frameworks/base/core/java/android/app/ActivityThread.java
//frameworks/base/core/java/android/app/Activity.java
//frameworks/base/core/java/android/os/Looper.java
//frameworks/base/core/java/android/os/Handler.java
//frameworks/base/core/java/android/os/HandlerThread.java
//frameworks/base/core/java/android/os/Message.java
//frameworks/base/core/java/android/view/View.java
//frameworks/base/core/java/android/view/ViewRootImpl.java
//frameworks/base/core/java/android/view/WindowManagerGlobal.java

View.post(Runnable)

有关其他的方式,这儿不多说啦。主要看下View.post(Runnable)方法

    /**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     *
     * @param action The Runnable that will be executed.
     *
     * @return Returns true if the Runnable was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     *
     * @see #postDelayed
     * @see #removeCallbacks
     */
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

看不论是attachInfo.mHandler.post(action)还是ViewRootImpl.getRunQueue().post(action)最后都调用的是Handler.post getRunQueue()最终的实现

 //Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);

Activity.runOnUIThread(Runnable)

如果是主线程,直接更新;如果不是,使用Handler更新。

    /**
     * Runs the specified action on the UI thread. If the current thread is the UI
     * thread, then the action is executed immediately. If the current thread is
     * not the UI thread, the action is posted to the event queue of the UI thread.
     *
     * @param action the action to run on the UI thread
     */
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

子线程如何将状态更新到UI

通过上一篇文章 Android Handler机制初探,我们知道Handler和Looper之间是1对1的关系。问题是,我们在自定义的代码中实现了Handler,可是Looper在哪儿?这里区分一下UI线程(主线程)、非UI线程

UI 线程

这种ActivityThread.java中的main函数。看到了Looper.prepareMainLooper();和Looper.loop()了吧!不多说

     public static void main(String[] args) {
        //......
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

子线程

子线程基本上可以看做是自定义的线程;这就属于千变万化的了,你不能在指望ActivityThread帮你干活了,此时就要自己动手丰衣足食。怎么搞呢!当然是参照线程的东西了(ActivityThread中的实现)。
第一步: prepare()
第二部: loop()
为啥不是prepareMainLooper 而是 prepare() The main looper for your application is created by the Android environment, so you should never need to call this function yourself
典型代码如下所示:

package cp.com.clarify;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
//import android.support.v7.app.AlertDialog;
import android.graphics.Color;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MainActivity extends /*AppCompatActivity*/ Activity{
    private final static String TAG="MainActivity";

    private TestThread  mTestThread;
    Intent intent;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        intent = new Intent(getApplicationContext(), Main2Activity.class);
        mTestThread = new TestThread();
        mTestThread.start();
        mTestThread.getHandler().sendEmptyMessage(1);
        final Button mBtn = (Button)findViewById(R.id.button2);

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.e(TAG,""+this);
                Log.e(TAG,"-----------------> set toolbar to RED <------------------------");
                mBtn.setBackgroundColor(Color.RED);
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5000);//注意这里啊,1000000点伤害。。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.e(TAG,"----------------->  set toolbal to Green <------------------------");
                Log.e(TAG,""+this);
                mBtn.setBackgroundColor(Color.GREEN);
            }
        }).start();
    }


    class TestThread extends Thread {
        private Handler mHandler;
        private final Object mLock = new Object();

        public void run() {
            Looper.prepare();
            synchronized (mLock) {
                mHandler = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        //.....
                        switch(msg.what){
                            case 1:
                                Log.d(TAG,"================");
                                break;
                            default:
                                break;
                        }
                    }
                };
                mLock.notifyAll();
            }
            Looper.loop();
        }

        public Handler getHandler() {
            synchronized (mLock) {
                if (mHandler == null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException e) {
                    }
                }
                return mHandler;
            }
        }
        public void exit() {
            getHandler().post(new Runnable(){
                public void run() {
                    Looper.myLooper().quit();
                }});
        }
    }

    @Override
    protected void onResume() {
        Log.e(TAG,"----------------->  onResume <------------------------");
        super.onResume();
    }

    @Override
    protected void onStop() {
        Log.e(TAG,"----------------->  onStop <------------------------");
        super.onStop();
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        Log.e(TAG,"----------------->  onPostCreate <------------------------");
        super.onPostCreate(savedInstanceState);
    }

    @Override
    protected void onPostResume() {
        Log.e(TAG,"----------------->  onPostResume <------------------------");
        super.onPostResume();
    }

    @Override
    protected void onDestroy() {
        Log.e(TAG,"----------------->  onDestroy <------------------------");
        super.onDestroy();
    }

    @Override
    protected void onPause() {
        Log.e(TAG,"----------------->  onPause <------------------------");
        super.onPause();
    }

    @Override
    protected void onRestart() {
        Log.e(TAG,"----------------->  onRestart <------------------------");
        super.onRestart();
    }

}

最后的运行结果显示:Button的颜色是红色的;这说明:子线程是可以更新UI的,但是过了5s之后程序挂了,这好像有说明子线程是不能更新UI的,出现了如下报错;到底是要闹哪样???

 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
     at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6556)
     at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:942)
     at android.view.ViewGroup.invalidateChild(ViewGroup.java:5084)
     at android.view.View.invalidateInternal(View.java:12724)
     at android.view.View.invalidate(View.java:12660)
     at android.view.View.invalidateDrawable(View.java:16805)
     at android.widget.TextView.invalidateDrawable(TextView.java:5408)
     at android.graphics.drawable.Drawable.invalidateSelf(Drawable.java:385)
     at android.graphics.drawable.ColorDrawable.setColor(ColorDrawable.java:136)
     at android.view.View.setBackgroundColor(View.java:17196)
     at cp.com.clarify.MainActivity$3.run(MainActivity.java:76)
     at java.lang.Thread.run(Thread.java:818)

分析一下这份log我们发现,异常是在ViewRootImpl.java的checkThread中抛出的;是不是有点儿诡异?第一个Thread竟然没有checkThread,第二个Thread竟然checkTread?来来跟着我的思路,这是不是说明,第一个Thread的时候压根儿就没有ViewRootImpl,到了第二个Thread(mBtn.setBackgroundColor(Color.GREEN);)的时候ViewRootImpl给创建了!O(∩_∩)O哈哈~机智如我!!

搜了一下这方面的资料。发现还真是这样的。。

    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume{
   // If we are getting ready to gc after going to the background, well
   // we are back active so skip it.
        // TODO Push resumeArgs into the activity for consideration
        if (r != null) {
            final Activity a = r.activity;

            if (localLOGV) Slog.v(
                TAG, "Resume " + r + " started activity: " +
                a.mStartedActivity + ", hideForNow: " + r.hideForNow
                + ", finished: " + a.mFinished);

            // If the window hasn't yet been added to the window manager,
            // and this guy didn't finish itself or start another activity,
            // then go ahead and add the window.

            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //这里。。这里 start
                ViewManager wm = a.getWindowManager();
                //这里。。这里 end
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);//这里。。这里。。
                }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }

看下addView的实现
WindowManagerGlobal.java

            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

好了。。
至于 View, Windows, WindowManager, WindowManagerImpl, View,DecorView, ViewGroup, View,ViewRoot,ViewGroup,ViewRoot,WindowManagerGlobal DecorView之间的关系,抽时间在分析。
可以参考这篇文章,很不错的。。
http://www.cnblogs.com/samchen2009/p/3364327.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章