Android更新Ui進階精解(二)

來源:互聯網
上載者:User

Android更新Ui進階精解(二)
《代碼裡的世界》

 

 1.回顧

  第一篇講了對Ui線程更新的方法和見解,然後接著講了線程檢查機制,這裡來詳細分析下更新Ui的核心——Android中訊息系統模型。當然,這裡要講的其實也已經不再簡簡單單地是更新Ui的範疇了。不過還是很值得學習和分析一下。另外,其實網上關於這方面的講解也有很多了,本篇也是綜合整理並用自己的理解加以描述和概括。同時也感謝有更高造詣的大大能予以批評指正。

提煉

  Android中的訊息機制主要有如下幾個要點,這裡也著重圍繞這些內容來講解:
  
1. Handler 調度訊息和runnable對象在不同線程中執行相應動作。
2. Looper訊息泵,用來為一個線程開啟一個訊息迴圈
3. MessageQueue 遵循FIFO先進先出規則的訊息佇列,以鏈表形式存取Message,供looper提取

2.分析

  為了方便分析,借用一下找到的模型圖綜合看一下:
  
  首先在一個線程中初始化一個looper並prepare(準備),然後建立該looper對象的處理對象Handler,接著當需要互動變更時,可以在其他線程(或自身線程)中使用handler發訊息至該訊息佇列MessageQueue,最後looper會自動有序抽取訊息(沒有訊息則掛起),交給handler執行訊息處理邏輯。
  Orz,我的概念描述還是一塌糊塗,還是轉代碼說明吧:
比如我們有個線程專門負責一類處理邏輯,並且只允許該線程來處理這類邏輯,那麼我們怎麼做到呢?
1. 在一個線程裡邊定義一個Looper

    Looper.prepare(); //稍微有點兒多,詳細見下文

2.定義一個處理訊息的Handler

    handler = new Handler(){            @Override            public void handleMessage(Message msg) {                super.handleMessage(msg);                //處理邏輯            }    };

3.啟動looper,並開始工作,輪詢訊息

    Looper.loop(); //詳細見下文    //要停止的話,使用Looper.quit();

4.在其他線程將要處理的資料data或回調對象callback以訊息Message模式通過Handler發至該訊息佇列MessageQueue

    handler.sendMessage(msg)

5.Looper的loop()方法會從訊息佇列中取到該訊息並喚醒處理邏輯

    //即loop()方法中的代碼    for (;;) { //顯然這個死迴圈一直在等待訊息到來並處理            Message msg = queue.next(); // 取一條訊息            if (msg == null) {                return;            }            msg.target.dispatchMessage(msg); //調用訊息綁定的handler執行處理邏輯            //other code....    }

6.handler跳轉到執行處理邏輯的過程

    public void dispatchMessage(Message msg) {        if (msg.callback != null) { //如果有回調,則調用            handleCallback(msg);        } else {            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }    //調用回調方法     private static void handleCallback(Message message) {        message.callback.run();    }

以上便是整個訊息系統的過程,後邊我們會逐個分析精講。

3.回到我們更新UI講解

  在ActivityManagerService為Android應用程式建立新的進程並啟動activity時候,主線程ActivityThread首先被建立。該進程 Process.java@start(“android.app.ActivityThread”,…)會載入執行ActivityThread的靜態成員函數main,開啟該方法:

    public static void main(String[] args) {      //other code.. 我們只看有用的部分,其他暫略過    Looper.prepareMainLooper();  //準備looper,注,綁定的為當前主線程    ActivityThread thread = new ActivityThread();//開啟一個新ActivityThread線程    thread.attach(false);//最後執行到activity    //other code..    Looper.loop();  //啟動looper

  這個靜態函數做了兩件事情,一是在主線程中建立了一個ActivityThread執行個體,二是通過Looper類使主線程進入訊息迴圈。
  然後,代碼經過一系列邏輯( ActivityThread.attach->IActivityManager. attachApplication -> attachApplicationApplicationThread.scheduleLaunchActivity ->… ->ActivityThread.performLaunchActivity ),最終會調用activity的attach方法。
  我們開啟activity類。可以看到,它定義了uiThread和Handler參數

    ActivityThread mMainThread;//對應上邊的ActivityThread線程     private Thread mUiThread;//主Ui線程    final Handler mHandler = new Handler();//這個handler就是activity用來處理Ui的了。我們自己定義的handler其實等於重新定義了這個mHandler;

  我們來看activity的attach方法:

final void attach(Context context, ActivityThread aThread,            Instrumentation instr, IBinder token, int ident,            Application application, Intent intent, ActivityInfo info,            CharSequence title, Activity parent, String id,            NonConfigurationInstances lastNonConfigurationInstances,            Configuration config, IVoiceInteractor voiceInteractor) {        mUiThread = Thread.currentThread();//當前主線程Ui線程        mMainThread = aThread;   //對應上邊的ActivityThread線程   }

  所以,當我們要更新UI的時候,都會用到sendMessage,比如使用runOnUiThread,來看下這個方法

public final void runOnUiThread(Runnable action) {        /**        *如果當前線程不為Ui主線程則使用定義好的mHandler        */        if (Thread.currentThread() != mUiThread) {            mHandler.post(action);        } else {            action.run();        }}

開啟post方法:

    public final boolean post(Runnable r)    {       return  sendMessageDelayed(getPostMessage(r), 0);    }

  還是熟悉的配方,還是熟悉的味道。。封裝成訊息,然後發送出去。好,我們再回頭看看最初第一篇文章裡的四種方法:
  1.new 一個handler來 sendMessage();
  2.利用new handler來post
  不過是把上邊已經定義好Activity的mHandler重新定義了一遍,然後封裝成訊息發送出去;
  3.runOnUiThread
  同樣不過是用了Activity的mHandler發送訊息;
  4.view.post
  稍微看一下代碼:

    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應該不算陌生吧,附加view到Activity的時候會把activity的一些屬性附加給AttachInfo,這裡同樣調用取得mHandler然後再post。。繞了一圈又回來了。
  
  綜上看來,整個UI更新機制其實就是Android訊息系統模型的一個簡單實現。至此,我們的更新UI部分也算講完了,那麼作為補充部分,還是從源碼上完整細緻的過一下整個訊息系統模型吧。

4.精解

  這裡通過剖析源碼來理解各部分的具體實現,再結合前面講的內容加以融會貫通,便於深入理解最終達到在不同情境的熟練使用。
  我們將按照順序來逐個查看。
  首先說說訊息對象,畢竟其他類操作的最基本元素也都是它。

4.1 Message
public final class Message implements Parcelable {    //繼承Parcelable 用於資料傳遞    /**幾種資料類型**/    public int arg1;     public int arg2;     public Object obj;    Bundle data;    public int what;//供handler處理的訊息識別標識身份    long when;//什麼時候處理該訊息    Handler target;//處理該訊息的目標handler    Runnable callback;  //回調方法    int flags;//標籤標識    static final int FLAG_IN_USE = 1 << 0;//是否可用(回收利用)    static final int FLAG_ASYNCHRONOUS = 1 << 1;    static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;    public Messenger replyTo;//可選對象,可以用來記錄發送方或接收者    Message next;//這條訊息的下一條訊息    /**    *開一個訊息池,便於迴圈利用訊息,避免產生新對象並分配記憶體    */    private static final Object sPoolSync = new Object();    private static Message sPool;    private static int sPoolSize = 0;    private static final int MAX_POOL_SIZE = 50;}

  既然提到訊息池又在前面講了利用obtain()節省記憶體資源,那麼我們就看下這個obtain()

    /**     *從訊息池中返回一個新的訊息執行個體,避免我們通常情況下分配新對象。     */     public static Message obtain() {        synchronized (sPoolSync) {            if (sPool != null) {                Message m = sPool;                sPool = m.next;                m.next = null;                sPoolSize--;                return m;            }        }        return new Message();    }

  然後就是基於此方法的一系列運用:先調用obtain()方法,然後把擷取的Message執行個體的 各種參數賦值傳參。

    //取一個訊息對象,把已存在的訊息內容賦值過去    public static Message obtain(Message orig) {        Message m = obtain();        m.what = orig.what;        m.arg1 = orig.arg1;        m.arg2 = orig.arg2;        m.obj = orig.obj;        m.replyTo = orig.replyTo;        if (orig.data != null) {            m.data = new Bundle(orig.data);        }        m.target = orig.target;        m.callback = orig.callback;        return m;    }    public static Message obtain(Handler h, int what,             int arg1, int arg2, Object obj) {        Message m = obtain();        m.target = h;        m.what = what;        m.arg1 = arg1;        m.arg2 = arg2;        m.obj = obj;        return m;    }    //調用obtain並賦值,不再一一列出    public static Message obtain(Handler h) {//..}    public static Message obtain(Handler h, Runnable callback) {//..}    public static Message obtain(Handler h, int what) {//...}    public static Message obtain(Handler h, int what, Object obj) {//...}    public static Message obtain(Handler h, int what, int arg1, int arg2) {//...}

  然後就是回收釋放recycle,它返回一個訊息池執行個體。釋放之後將不能再使用此方法。

    public void recycle() {        clearForRecycle();        synchronized (sPoolSync) {            if (sPoolSize < MAX_POOL_SIZE) {                next = sPool;                sPool = this;                sPoolSize++;            }        }    }    //清空所有資料    void clearForRecycle() {        flags = 0;        what = 0;        arg1 = 0;        arg2 = 0;        obj = null;        replyTo = null;        when = 0;        target = null;        callback = null;        data = null;    }    //拷貝訊息內容    public void copyFrom(Message o) {        this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;        this.what = o.what;        this.arg1 = o.arg1;        this.arg2 = o.arg2;        this.obj = o.obj;        this.replyTo = o.replyTo;        if (o.data != null) {            this.data = (Bundle) o.data.clone();        } else {            this.data = null;        }    }

  後邊就是get和set方法以及Parcelable 的讀寫。

4.2 Looper

  先看他所定義的屬性:

public class Looper {    private static final String TAG = Looper;    static final ThreadLocal sThreadLocal = new ThreadLocal();    private static Looper sMainLooper;//唯一對應一個主線程的looper靜態執行個體    final MessageQueue mQueue;//訊息佇列    final Thread mThread; //當前綁定線程    volatile boolean mRun; //是否允許退出    private Printer mLogging;//日誌列印    //....各種方法體....}

  通常作業系統都為線程提供了內部儲存空間,一個線程對應一個記憶體空間,因此這裡很方便的為一個線程定義唯一對應的looper執行個體:ThreadLocal< Looper > 這個有點類似C中申請記憶體大小 *malloc(sizeof Looper),或者我們可以簡單理解為只作用於當前線程的new Looper.
  而sMainLooper是當前應用程式的主線程looper,區別是適用於主線程。
  我們再看他的構造方法:

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

  此構造方法是私人的,即不允許在外部執行個體化,這樣通過單例模式保證外部從該線程取得looper唯一。另外它主要初始化了mQueue 、mRun 和 mThread 幾個屬性,並綁定了當前線程。找一下它調用執行個體化的部分:

    //重載,主要看下邊的prepare(boolean quitAllowed)方法    public static void prepare() {        prepare(true);    }    /**    *初始化當前線程作為Looper並存為本地變數,    *並由此來建立handler和處理常式    *    *@quitAllowed 是否允許退出(迴圈取訊息)    *通過調用loop()和quit()來開啟和結束迴圈    */    private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) { //保證一個線程唯一對應一個Looper            throw new RuntimeException(Only one Looper may be created per thread);        }        sThreadLocal.set(new Looper(quitAllowed));//線上程中初始化一個執行個體    }

sThreadLocal的get和set,負責在記憶體中存取與線程唯一對應的looper。
  同時我們會注意到有兩個方法prepareMainLooper和getMainLooper:

    /**    *初始化當前線程作為Looper並作為android應用的取訊息邏輯,    *是由當前運行環境來建立,不需要手動調用    */    public static void prepareMainLooper() {        prepare(false);        synchronized (Looper.class) {  //加鎖,保證執行個體化唯一一個looper            if (sMainLooper != null) {                throw new IllegalStateException(The main Looper has already been prepared.);            }            sMainLooper = myLooper();        }    }    /**     * 返回當前應用程式主線程的looper執行個體    */    public static Looper getMainLooper() {        synchronized (Looper.class) {            return sMainLooper;        }    }

  這部分是共應用程式初始化的時候調用的,我們一般用不到,不過也可以看出只是初始化了主線程的looper。
  好的,基本的初始化工作也已經完成了,來看該類中的核心部分,迴圈取訊息的loop()

    /**     * 啟動隊列的迴圈取訊息作業,直到調用quit()退出    */    public static void loop() {        final Looper me = myLooper();        if (me == null) {            throw new RuntimeException(No Looper; Looper.prepare() wasn't called on this thread.);        }        final MessageQueue queue = me.mQueue;        // 確保當前線程是本地進程的唯一標示        Binder.clearCallingIdentity();        final long ident = Binder.clearCallingIdentity();        //開始迴圈取訊息作業        for (;;) {            Message msg = queue.next(); //取下一條訊息            if (msg == null) {                // 如果訊息佇列沒有訊息則掛起                return;            }            // 列印日誌部分            Printer logging = me.mLogging;            if (logging != null) {                logging.println(>>>>> Dispatching to  + msg.target +   +                        msg.callback + :  + msg.what);            }            //調用訊息處理邏輯(回調和執行handler處理)            msg.target.dispatchMessage(msg);            if (logging != null) {                logging.println(<<<<< Finished to  + msg.target +   + msg.callback);            }            // 確保在處理訊息邏輯時當前線程並沒有被打斷            final long newIdent = Binder.clearCallingIdentity();            if (ident != newIdent) {                Log.wtf(TAG, Thread identity changed from 0x                        + Long.toHexString(ident) +  to 0x                        + Long.toHexString(newIdent) +  while dispatching to                         + msg.target.getClass().getName() +                          + msg.callback +  what= + msg.what);            }            //回收訊息            msg.recycle();        }    }

  基本都有備忘,不用過多解釋了。msg.target.dispatchMessage前面已經講過,後便可能會在拉出來遛遛。
  其他再就是基本的get和列印和異常捕獲相關的了,有興趣的可以自己去看一下。

4.3 MessageQueue 4.4 Handler

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.