天天看點

handler機制簡析

首先要知道,android 中有兩個重要的機制,一個是Binder機制,另一個是handler機制.

為什麼要引入handler機制呢,因為在android中,主線程不能進行耗時的操作,否則會發生ANR異常.子線程做耗時的操作,如聯網操作,i/o操作,做完耗時操作後可能需要更新UI,但是android不允許在子線程中更新UI,否則就會觸發程式異常,這個時候就可以通過Handler+子線程将更新UI的操作切換到主線程中進行.

  • 為什麼不可以在子線程中更新UI
    1. 因為android的UI線程不是線程安全的(就是指如果多個線程調用這個類的方法,會出現不可預知的不正常的情況,那麼線程安全的意思就是多個線程同時操作不會有問題),如果多個子線程并發發通路(同時通路)會導緻UI空間處于不可預期的狀态
    2. UI空間不可以枷鎖,因為一會導緻UI通路的邏輯變得複雜,其次會降低UI通路的效率,會阻塞某些線程的執行’
    由于上邊的兩個缺點,就可以采用單線程的模式來更新UI,對于開發人員來說隻需要通過handler(也可以通過SyncTask類來實作)來切換一下UI通路的線程即可.

2.消息機制原理分析

1. 核心類

* message: 可分為硬體産生的消息和軟體産生的消息,一個handler可以發送多個消息
* messageQueue:消息隊列,主要用來存放handler發送過來的消息,由系統維護,隻有一個執行個體
* handler:消息輔助類,主要通過sendMessage()發送消息,通過handlerMessage()接受消息,每個activity都可以new一個handler,是以可以有多個handler
* looper:輪詢器 ,不斷從消息隊列中輪詢,取出消息,當沒有消息的時候回休眠,同時還負責分發消息
           

首先需要在主線程中建立一個Handler對象,并重寫Handler.Message(),并調用sendMessage(),将之存到MessageQueue中,而Looper則會一直嘗試從MessageQueue中取出待處理消息,最後分發回handleMessage()中,這是一個無限循環。Looper若沒有新消息需要處理則會進入等待狀态,直到有消息需要處理為止。

2.Message,handler,MessageQueue,Looper對象的建立

2.1 message的建立

有三種方式:
        1.new Message()
        2.message.obtain()
        3.handler.obtainMessage() 内部調用了message.obtain()
           
源碼:
//message.obtain()
    public static Message obtain() {
        synchronized (mPoolSync) {
            if (mPool != null) {
                Message m = mPool;
                mPool = m.next;
                m.next = null;
                return m;
            }
        }
        return new Message();
    }
//Handler中的obtainMessage(),本質上還是調用了Message.obtain(this)
    public final Message obtainMessage()
    {
        return Message.obtain(this);//this其實就是handler執行個體
    }
           

2.2 handler looper messagequeue的建立

new handler();

一個應用中可以同時有多個handler對象,一個handler對象也可以發送多個message
           
handler相關源碼:
 public Handler() {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == ) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = null;
    }

//looper中的相關源碼
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());//looper就是在這裡被執行個體化的,一定要注意
    }
    public static final void prepareMainLooper() {
        prepare();//調用了prepare(),
        setMainLooper(myLooper());
        if (Process.supportsProcesses()) {
            myLooper().mQueue.mQuitAllowed = false;
        }
    }
//ActivityThread中的源碼

  public static final void main(String[] args) {
        SamplingProfilerIntegration.start();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();//在這裡調用了prepareMainLooper()
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

//在looper的構造方法中建立了Messagequeue,looper的構造又是在prepare()被調用
  private Looper() {
        mQueue = new MessageQueue();//建立Messagequeue
        mRun = true;
        mThread = Thread.currentThread();
    }
           

2.3 handler是如何發送消息的

handler依次調用以下3個方法:

sendMessage(Message msg)

sendMessageDelayed(Message msg, long delayMillis)

endMessageAtTime(Message msg, long uptimeMillis)

具體源碼如下:

//調用了 sendMessageDelayed(Message msg, long delayMillis)
    public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, );
    }

//endMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
  public final boolean  sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < ) {
            delayMillis = ;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);//手機開機時間,加上延遲發送的時間

//通過調用enqueueMessage(msg, uptimeMillis)将消息插入消息隊列
    public boolean sendMessageAtTime(Message msg, long uptimeMillis)
    {
        boolean sent = false;
        MessageQueue queue = mQueue;
        if (queue != null) {
            msg.target = this;
            sent = queue.enqueueMessage(msg, uptimeMillis);//将消息插入隊列
        }
        else {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
        }
        return sent;
    }
           

2.4 messagequeue插入消息的原理

主要由 enqueueMessage(Message msg, long when)方法實作

情形一:當隊列中沒有消息,或者要插入的消息比隊列中的第一消息還要早執行

final boolean enqueueMessage(Message msg, long when) {
            msg.when = when;
            //Log.d("MessageQueue", "Enqueing: " + msg);
            Message p = mMessages; //mMessages表示隊列中的第一個消息
            if (p == null || when ==  || when < p.when) {
            //message的next屬性指向的是下一個消息
                msg.next = p;//原來隊列第一個消息變為第二個消息
                mMessages = msg;//将要插入的消息變為第一個消息
                needWake = mBlocked; // new head, might need to wake up
            } else {
           

情形二:需要将消息插入到中間的情況

else {
                Message prev = null;//prev中間變量
                while (p != null && p.when <= when) {
                /**通過while循環不斷判斷,如果要插入的消息比原來第一個晚執行
                /*
                    prev = p;
                    p = p.next;
                }
                msg.next = prev.next;
                prev.next = msg;
                needWake = false; // still waiting on head, no need to wake up
            }
           

情形二的邏輯比較複雜,下假設需要插入到第二個,一定要畫圖進行了解:

handler機制簡析

插入消息完整的源碼如下:

final boolean enqueueMessage(Message msg, long when) {
        if (msg.when != ) {
            throw new AndroidRuntimeException(msg
                    + " This message is already in use.");
        }
        if (msg.target == null && !mQuitAllowed) {
            throw new RuntimeException("Main thread not allowed to quit");
        }
        final boolean needWake;
        synchronized (this) {
            if (mQuiting) {
                RuntimeException e = new RuntimeException(
                    msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            } else if (msg.target == null) {
                mQuiting = true;
            }

            msg.when = when;
            //Log.d("MessageQueue", "Enqueing: " + msg);
            Message p = mMessages;
            if (p == null || when ==  || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked; // new head, might need to wake up
            } else {
                Message prev = null;
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
                msg.next = prev.next;
                prev.next = msg;
                needWake = false; // still waiting on head, no need to wake up
            }
        }
        if (needWake) {
            nativeWake(mPtr);
        }
        return true;
    }
           

2.5 looper喚醒

looper會一直輪詢messagequeue,當隊列中沒有消息的時候,looper會休眠,當handler發送消息後,它又會被喚醒

hanlder在發送消息的時候,寫入一個标志:W

2.6 looper如何将msg發送給制定的handler對象

msg.sendToTarget(); //發送給handler

msg.sendToTarget(); //發送給handler
    public void sendToTarget() {
        target.sendMessage(this);//this就是handler,根據不同的handler對象将消息發送給不同的hanglder
    }
           

2.7 looper如何分發消息

首先調用loop(),在loop()中一次調用下邊幾個方法:

Looper me = myLooper();//擷取looper對象

MessageQueue queue = me.mQueue;//找到消息隊列

Message msg = queue.next();//取消息

msg.target.dispatchMessage(msg);//調用handler的 dispatchMessage(msg)方法

handler中的 dispatchmessage()

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);//當msg中傳有回調時執行此方法
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {//mCallback 表示handler中自己的回調
                    return;
                }
            }
            handleMessage(msg);//調用handleMessage() 具體是有我們自己實作
        }
    }
           

注:handler移除消息 有待研究

總結:

其實Asychtask内部也是使用了handler,從Android 1.5開始系統将AsyncTask引入到android.os包中,過去在很早1.1和1.0 SDK時其實官方将其命名為UserTask,其内部是JDK 1.5開始新增的concurrent庫,做過J2EE的網友可能明白并發庫效率和強大性,比Java原始的Thread更靈活和強大,但對于輕量級的使 用更為占用系統資源。Thread是Java早期為實作多線程而設計的,比較簡單不支援concurrent中很多特性在同步和線程池類中需要自己去實作 很多的東西,對于分布式應用來說更需要自己寫排程代碼,而為了Android UI的重新整理Google引入了Handler和Looper機制,它們均基于消息實作,有時可能消息隊列阻塞或其他原因無法準确的使用。

推薦大家使用AsyncTask代替Thread+Handler的方式,不僅調用上更為簡單,經過實測更可靠一些,Google在Browser中大量 使用了異步任務作為處理耗時的I/O操作,比如下載下傳檔案、讀寫資料庫等等,它們在本質上都離不開消息,但是AsyncTask相比Thread加 Handler更為可靠,更易于維護,但AsyncTask缺點也是有的比如一旦線程開啟即dobackground方法執行後無法給線程發送消息,僅能 通過預先設定好的标記來控制邏輯,當然可以通過線程的挂起等待标志位的改變來通訊,對于某些應用Thread和Handler以及Looper可能更靈 活。

(1)發送消息

Handler支援2種消息類型,即Runnable和Message。是以發送消息提供了post(Runnable r)和sendMessage(Message msg)兩個方法。從下面源碼可以看出Runnable指派給了Message的callback,最終也是封裝成Message對象對象。學姐個人認為外部調用不統一使用Message,應該是相容Java的線程任務,學姐認為這種思想也可以借鑒到平常開發過程中。發送的消息都會入隊到MessageQueue隊列中。

(2)處理消息

Looper循環過程的時候,是通過dispatchMessage(Message msg)對消息進行處理。處理過程:先看是否是Runnable對象,如果是則調用handleCallback(msg)進行處理,最終調到Runnable.run()方法執行線程;如果不是Runnable對象,再看外部是否傳入了Callback處理機制,若有則使用外部Callback進行處理;若既不是Runnable對象也沒有外部Callback,則調用handleMessage(msg),這個也是我們開發過程中最常覆寫的方法了。

(3)移除消息

removeCallbacksAndMessages(),移除消息其實也是從>>MessageQueue中将Message對象移除掉。