1. 程式人生 > >Android---Handler消息處理機制

Android---Handler消息處理機制

bsp cte string ack 消息處理 libc from 原因 entity

搞Android的人都知道。android是不同意你在子線程中更新UI操作的。這主要出於線程安全方面的考慮。通常的做法是在主線程中創建一個Handler對象,在子線程中創建一個Message對象。該Message對象中封裝一些更新UI操作的數據,通過Handler的sendMessage方法發送出去,主線程利用Handler的handleMessage方法來對該Message進行對應的處理。但發現沒有,子線程調用Handler的sendMessage發出Message之後,消息是怎麽傳遞到主線程的handleMessage方法裏面進而進行處理的呢。接下來我們從源代碼角度慢慢進行分析:

先來看看尋常我們是怎麽使用handler的:

實例1:

public class MainActivity extends Activity {

	public Handler mHandler;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mHandler = new Handler(){

			@Override
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case 1:
					System.out.println("接收到空消息");
					break;
				default:
					break;
				}
			}
		};
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				mHandler.sendEmptyMessage(1);
			}
		}).start();
	}
}

解釋:

能夠看到。在子線程中我們使用的是主線程的Handler來進行sendMessage的,那麽問題來了。子線程中能夠存在自己的Handler麽?而且用這個Handler來sendMessage?以下我們進行測試:

實例2:

public class MainActivity extends Activity {

	public Handler mHandler;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mHandler = new Handler(){

			@Override
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case 1:
					System.out.println("接收到空消息");
					break;
				default:
					break;
				}
			}
		};
		new Thread(new Runnable() {
			@Override
			public void run() {
				Handler handler = new Handler();
				handler.sendEmptyMessage(1);
			}
		}).start();
	}
}

解釋:

這段程序我們調用的是自己的子線程自己的Handler,執行之後報了下面錯誤:

E/AndroidRuntime(963): java.lang.RuntimeException: Can‘t create handler inside thread that has not called Looper.prepare()

意思就是說在沒有調用Looper.prepare()之前是不同意創建Handler對象的,這一點我們能夠從Handler的構造函數中查看原因:

下面是Handler的構造函數源代碼:

public Handler() {
    this(null, false);
}
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
    //從ThreadLocal獲取到Looper對象,這個對象是由Looper.prepare()函數創建而且加入到ThreadLocal中的
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        <span style="color:#ff6666;">throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");</span>
    }
    //獲得Looper對象中的MessageQueue
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

解釋:

拋出異常原因在於mLooper為null,而mLooper是一個Looper對象,這個對象是通過Looper的static方法myLooper從ThreadLocal中獲取的。而創建Looper對象是由Looper的static方法prepare()實現的,而且將其增加到了ThreadLocal中。

因此我們找到了拋出異常的原因。也就是Looper對象為null。正如異常中所提示的一樣,須要調用Looper.prepare()來創建Looper對象。

我們來查看Looper的static方法prepare源代碼:

public static void prepare() {
    prepare(true);//創建子線程的Looper對象的時候,此處始終為true。可是待會會發現主線程的Looper對象在創建的時候此參數值是false
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

解釋:

從源代碼中我們看到在創建Looper對象之前先查看ThreadLocal中是否已經存在一個Looper對象。一個線程僅僅能創建一個Looper對象,假設多次創建Looper會拋異常。假設不存在的話,調用new Looper(true)創建當前線程的Looper對象,而且將其set到ThreadLocal中(在此多少能夠發現ThreadLocal事實上是一個Map型的數據結構實現的,其源代碼分析以後補上),來吧,該看看new Looper(true)究竟做了寫什麽事的時候了,源代碼例如以下:

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);//創建了一個MessageQueue消息隊列
        mRun = true;
        mThread = Thread.currentThread();
    }

解釋:

非常easy,就是創建了一個MessageQueue而且將mThread設置為當前線程;

好了,至此我們創建了Looper對象。那麽我們把Looper.prepare()增加到實例2的Handler handler = new Handler( )之前,看看會有什麽事發生吧:

實例3:

public class MainActivity extends Activity {

	public Handler mHandler;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mHandler = new Handler(){

			@Override
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case 0:
					System.out.println("1:   "+Thread.currentThread());
					break;
				default:
					break;
				}
			}
		};
		new Thread(new Runnable() {
			@Override
			public void run() {
				Looper.prepare();
				Handler handler = new Handler(){
					@Override
					public void handleMessage(Message msg) {
						System.out.println("2:  "+Thread.currentThread());
					}
				};
				handler.sendEmptyMessage(0);
				mHandler.sendEmptyMessage(0);
			}
		}).start();
	}
}

解釋:

輸出結果:1: Thread[main,5,main]

發如今我們的輸出信息中,並沒有輸出:子線程中handleMessage的信息

原因事實上也非常easy:你如今僅僅是有Looper對象了,可是你並沒有對Looper對象進行不論什麽操作,就像你非常有錢,可是你不花這些錢,那錢還有什麽用呢?那該怎麽用呢?要想找到這個問題的答案,我們須要分析主線程中是怎麽創建Looper對象以及怎麽使用這個Looper對象的呢?

我們知道應用程序是通過ActivityThread主線程來創建的,為什麽這樣子說呢?看看源代碼就知道啦:

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

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        Security.addProvider(new AndroidKeyStoreProvider());

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

        Looper.prepareMainLooper();//創建Looper對象

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        AsyncTask.init();

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();//使用Looper對象中的MessageQueue來進行消息處理

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

解釋:

由於這個類裏面有我們苦苦想要尋找的main函數,可能你曾經也會疑惑android程序究竟是怎麽啟動的呢?如今明確了吧,入口函數main在這裏呢,來看看裏面我們可能熟悉的代碼吧。Looper.prepareMainLooper()有點類似於我們之前見過的Looper.prepare()吧,顯然他也是用來創建一個Looper對象而且放入ThreadLocal裏面的。僅僅只是他是在主線程中創建的:

public static void prepareMainLooper() {
        <span style="color:#ff6666;">prepare(false);//這裏的參數值是false,子線程創建Looper的prepare參數值是true</span>
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

解釋:

在有了Looper對象之後。main方法中的Looper.loop()就是利用Looper對象中的MessageQueue來進行消息接收和處理的。源代碼例如以下:

public static void loop() {
        final Looper me = myLooper();//獲得Looper對象
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        <span style="color:#ff6666;">final MessageQueue queue = me.mQueue;//獲得Looper對象中的MessageQueue</span>

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
        //採用死循環的方式從MessageQueue中取出消息
        for (;;) {
           <span style="color:#ff6666;"> Message msg = queue.next(); // might block</span>
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
           //調用dispatchMessage來進行消息的處理
            msg.target.dispatchMessage(msg);

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            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();
        }
    }

解釋:

詳細過程是:

(1)首先通過myLooper( )靜態方法獲取到Looper對象;

(2)通過獲取到的Looper對象來獲取到該對象中的MessageQueue消息處理隊列;

(3)採用死循環的方式對消息隊列中的每一個消息調用此消息所在的handler(通過msg.target獲取此handler)的dispatchMessage方法進行處理;

(4)消息處處理結束後調用recycle方法回收消息;
這裏最重要的方法當然就是dispatchMessage消息處理和消息回收函數啦,接下來我們先分析一下消息是怎麽傳遞到MessageQueue隊列中的。隨後再來分別來看看究竟在dispatchMessage和recycle這兩個函數中做了什麽?

要想處理消息,首先你得有消息吧。就像你想花錢一樣,首先你總得有錢吧,handler機制中,消息是從哪來的呢?

非常顯然從實例中我們能夠看出,我們都是通過handler的sendEmptyMessage方法來發送消息的,在handler中存在非常多發送消息的方法,可是歸根結底他們最後都會調用sendMessageAtTime方法的(無論你是採用Runnable的post機制還是採用Message的send機制),源代碼例如以下:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
解釋:

這種方法有兩個參數。一個是想要傳遞給主線程的Message對象,uptimeMillis表示我們發送消息的時間,假設調用的不是sendMessageDelayed的話。uptimeMillis的值為0。在消息隊列非空的前提下調用enqueueMessage將消息增加到隊列中;

我們來看看enqueueMessage的源代碼:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
解釋:

非常easy。他就是直接調用了MessageQueue隊列中的enqueueMessage入隊方法,來看MessageQueue裏面的enqueueMessage方法源代碼:

    boolean enqueueMessage(Message msg, long when) {
        if (msg.isInUse()) {
            throw new AndroidRuntimeException(msg + " This message is already in use.");
        }
        if (msg.target == null) {
            throw new AndroidRuntimeException("Message must have a target.");
        }

        synchronized (this) {
            if (mQuitting) {
                RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                return false;
            }

            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
解釋:

這裏我們僅僅討論核心代碼。由於要插入Message對象到隊列。所以我們必須找到隊尾元素的位置。而這個位置在上面的源代碼是通過p的指針值是否為空來進行推斷的,假設p本身為空的話,說明p已經到達了隊尾,我們僅僅須要將該Message對象插入到p之後就可以啦,假如隊列本身為空的話,那麽p本身就是隊尾。直接插入;假如隊列本身不為空的話。須要遍歷整個隊列。找到隊尾元素就可以啦,然後插入,我們能夠看到。MessageQueue的隊列是由單鏈表來實現的。
好了,這下子Message對象增加到了MessageQueue中啦,隨後就是我們該怎麽處理的問題啦;

查看Handler.java源代碼中的dispatchMessage方法:

 public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

解釋:

在正式分析此方法之前先補充一點。Handler支持兩種消息類型。Runnable和Message,因此發送消息提供了post(Runnable r)和sendMessage(Message msg)兩個方法。Message中的callback屬性存儲的就是Runnable對象;

詳細分析:

(1)首先查看該Message的callback字段是否為null,即查看此消息是否存在Runnable屬性值(該值是通過post方法將其增加到Message的callback中的)。有的話則運行handleCallback方法。該方法會運行Runnable的run方法,源代碼例如以下:

private static void handleCallback(Message message) {
        message.callback.run();
    }
(2)假設Message的callback字段為null的話,則查看是否存在外部的Callback類型對象,這個值是在創建Handler的時候通過構造函數傳遞進來的,假設存在的話運行該Callback對象的handleMessage方法;

(3)假設既不存在Runnable對象。又不存在外部的Callback對象的話,則直接運行handler自身的handleMessage方法,這種方法須要我們在new Handler之後進行重寫,由於Handler本身是沒有實現這種方法的。

 public void handleMessage(Message msg) {
 }
好了,消息已經處理結束啦,接下來我們就該回收該消息啦。也就是Looper.loop( )方法所運行的最後一句代碼,msg.recycle( ),再次回到了源代碼級別查看:

   public void recycle() {
        clearForRecycle();//將Message的各種標識位所有歸位

        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;
    }
解釋:

釋放消息前首先將消息裏面的各個消息標識位歸位。隨後將該消息增加到消息緩沖池中,以備下次我們使用消息的時候能夠直接調用Message.obtain( )方法來獲取消息。這樣子就不用new Message( )啦,從而降低了new對象的時空開銷,這就是緩沖機制的優點,由於緩沖池裏面的全部消息對象是能夠反復使用的。僅僅是在使用的時候進行必要標識位的設置就可以,看看Message的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();
    }
解釋:

我們能夠發現,僅僅有當消息緩沖池為null的時候我們才會new Message出來,假設消息緩沖池不為空的話,直接獲取緩沖池中的第一個,而且讓緩沖池中的消息個數降低1就可以;

至此,整個消息處理過程已經結束了。
那麽我們應該知道怎麽改動實例3讓他可以輸出:子線程中handleMessage的信息
實例4:

public class MainActivity extends Activity {

	public Handler mHandler;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		mHandler = new Handler(){

			@Override
			public void handleMessage(Message msg) {
				switch (msg.what) {
				case 0:
					System.out.println("1:   "+Thread.currentThread());
					break;
				default:
					break;
				}
			}
		};
		new Thread(new Runnable() {
			@Override
			public void run() {
				Looper.prepare();
				Handler handler = new Handler(){
					@Override
					public void handleMessage(Message msg) {
						System.out.println("2:  "+Thread.currentThread());
					}
				};
				handler.sendEmptyMessage(0);
				mHandler.sendEmptyMessage(0);
				Looper.loop();
			}
		}).start();
	}
}
區別就是添加了Looper.loop( )這句代碼而已。

輸出結果:

2: Thread[Thread-120,5,main]
1: Thread[main,5,main]

究竟,我們能夠做做總結啦。看看Handler消息處理機制中究竟用到些什麽?

1. Looper
(1)創建消息循環

prepare( )用於創建Looper對象,而且保存到ThreadLocal中;

(2)獲得消息循環對象

myLooper( ),採用ThreadLocal的get方式獲取存儲在ThreadLocal裏面的消息循環對象;

(3)開始消息循環

詳細過程:

首選獲取MessageQueue裏面的隊頭Message

接著調用該Message所在handler的dispatchMessage方法。最後在dispatchMessage裏面調用handlerMessage方法

消息使用完成之後將其增加到本地消息緩沖池中。以便下次使用,節省創建Message的開銷
2. MessageQueue

每一個Looper對象相應一個MessageQueue隊列,他是消息的存儲區,向Handler發送的消息終於都會存儲到該隊列中
(1)消息入隊

息入隊採用的方法是enqueueMessage( )。首先會查看隊列是否為空,假設為空。則直接入隊就可以。假設非空須要輪詢鏈表,依據when從低到高的順序插入鏈表的合適位置。這裏的隊列是由單鏈表實現的;

(2)輪詢隊列

next( )用於獲取MessageQueue中的Message

3. Handler

(1)獲取消息

以obtain打頭的方法,這些方法實現的功能就是從本地消息緩沖池中獲取消息,這樣做的目的就僅僅是為了提高時空效率,這些方法實際上還是調用的Message中的各種obtain方法

(2)發送消息

Handler支持兩種消息類型,各自是Runnable和Message,他們發送消息的方法各自是以post打頭的post(Runnable runnable)和以sendMessage(Message message),可是post方法中的Runnable對象在最後還是會被封裝到Message中稱為Message的屬性callback的值,也就是說原則上還是自由Message這樣的方式的,在調用各種sendMessage的時候。都會終於運行sendMessageAtTime。而在這種方法裏面就會調用MessageQueue的enqueueMessage來將Message增加到隊列中啦;

(3)處理消息

Handler中處理消息的開始方法是dispatchMessage,在這種方法裏面會調用Handler本身的handleMessage方法。而這種方法是須要我們在創建Handler的時候重寫的

4. Message

(1)創建消息

創建消息有兩種方式。我們能夠通過new Message( )的方式創建一個新的Message,也能夠通過Message.obtain從本地消息緩沖池中獲取一個消息,後者在時空上效率更高;

(2)釋放消息

消息使用完成之後。調用recycle函數釋放消息,將該消息增加到本地消息緩沖池中,以便下次使用;

以上就是Handler消息處理機制的主要內容了。我們也自己實現了一個子線程中的handler而且創建了子線程自己的MessageQueue,此外子線程可以通過loop方法處理傳遞給子線程的消息了。可是假設每次我們想讓子線程實現這種功能的話,我們都必須利用Looper.prepare( )和Looper.loop( )方法將我們的子線程中的handler代碼包裹起來。這樣是不是太麻煩啦,這就導致了HandlerThread的出現,在下一篇。我們解說下HandlerThread的源代碼分析。









Android---Handler消息處理機制