Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android Handler 原理初探

Android Handler 原理初探

編輯:關於Android編程

前言

Android 開發過程中,遇到了冗長的耗時的操作,亦或是為了使得代碼結構更加清晰,或者是要動態的更新UI。一言不合就上Handler,這裡不討論java編程時的一些多線程模型,只探討一下Android中提供給開發者使用的Handler。

經典使用方法

關於Handler/Looper/Message之間的關系,上一篇博文有較為詳細的描述。這裡打算說人話,對照代碼來解釋一遍。

代碼寫法

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)方法

    /**
     * 

Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.

* * @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

通過上一篇文章,我們知道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之間的關系,抽時間在分析。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved