Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android更新Ui進階精解(二)

Android更新Ui進階精解(二)

編輯:關於Android編程

《代碼裡的世界》

 

 1.回顧

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

提煉

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


2.分析

  為了方便分析,借用一下找到的模型圖綜合看一下:
  消息系統模型
  首先在一個線程中初始化一個looper並prepare(准備),然後創建該loZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcGVyttTP87XEtKbA7bbUz/NIYW5kbGVyo6y909fFtbHQ6NKqvbu7pbHkuPzKsaOsv8nS1NTaxuTL+8/fs8yjqLvy19TJ7c/fs8yjqdbQyrnTw2hhbmRsZXK3os/7z6LWwbjDz/vPorbTwdBNZXNzYWdlUXVldWWjrNfuuvNsb29wZXK74dfUtq/T0NDys+nIoc/7z6KjqMO709DP+8+i1PK50sbwo6mjrL27uPhoYW5kbGVy1rTQ0M/7z6K0psDtwt+8raGjPGJyIC8+DQqhoaGhT3J6LM7StcS4xcTuw+jK9ru5ysfSu8v6uv3Nv6Osu7nKx9eqtPrC68u1w/ewyaO6PGJyIC8+DQo8ZW0+scjI587Sw8fT0Lj2z9+zzNeow8W4utTw0rvA4LSmwO3C37ytLLKix9LWu9TK0O24w8/fs8zAtLSmwO3V4sDgwt+8rSzEx8O0ztLDx9T1w7TX9rW9xNijvzwvZW0+PGJyIC8+DQoxLiA8c3Ryb25nPtTa0ru49s/fs8zA77HftqjS5dK7uPZMb29wZXI8L3N0cm9uZz48L3A+DQo8cHJlIGNsYXNzPQ=="brush:java;"> 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


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