【Android 開發】: Android 訊息處理機制之四: Android 訊息迴圈 Looper 及其原始碼解析
上一講我們學習Handler和Message的一些使用方式,我們知道Handler它會發送訊息和處理訊息,並且關聯一個子執行緒,如何傳送訊息入隊和出隊處理訊息等這些都是交給Looper去管理分發的,也就是它是負責整個訊息佇列運轉的一個類,這一講我們就來學習一下Android中的Looper的操作。
一、Looper類介紹
這個類是用來在一個執行緒中執行一個訊息迴圈(Message),預設情況下執行緒是沒有一個訊息迴圈來關聯它們的,在這個執行緒中呼叫prepare()方法來啟動一個迴圈,然後呼叫loop()就可以處理訊息至到迴圈停止。下面就是一個典型的例子實現一個Looper執行緒,使用 prepare()方法 和 loop()來建立一個初始的Handler並且能夠與訊息迴圈(Looper)進行溝通關聯
【注意】:預設情況下的android新誕生的一個執行緒是沒有開啟一個訊息迴圈(Looper)的,但是主執行緒除外,主執行緒系統會自動為其建立Looper物件,開啟訊息迴圈。
二、程式Demo
1. 佈局檔案定義一個Button和TextView,這裡不貼出來,讀者可以閱讀附件原始碼
2. MainActivity.java
... public class MainActivity extends Activity { private Button btn; private TextView txt; private MyHandler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ...
mHandler = new MyHandler(); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub // 啟動執行緒 new Thread(new MyThread()).start(); } }); } public class MyThread implements Runnable { @Override public void run() { // TODO Auto-generated method stub Message msg = Message.obtain(); msg.obj = "AHuier"; mHandler.sendMessage(msg); } } public class MyHandler extends Handler { public MyHandler() { super(); // TODO Auto-generated constructor stub } // Handler中有個傳遞Looper物件的構造方法,這個構造方法比較少用 public MyHandler(Looper looper) { super(looper); // TODO Auto-generated constructor stub } @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); txt.setText("接受子執行緒傳送的訊息 --->" + msg.obj); } } ... }3. 程式執行結果
4. 【說明】: 在上面的程式碼中我們並沒有去手動生成Looper物件,主執行緒依然可以完成接受子執行緒訊息並顯示的操作,在這裡我們需要明白為什麼我們之前的例子中雖然沒有建立一個Looper去管理訊息,但是子執行緒中傳送訊息依然能夠被主執行緒接受到,原因是因為我們主執行緒中已經存在了預設的一個Looper物件。
這裡我們在做一個小測試,我們給其生成一個Looper物件,在onCreate()方法中新增程式碼如下:
程式執行依然會接受到子執行緒傳送的訊息。為什麼會是這樣的呢?我們來檢視一下它們的原始碼@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initComponent(); // 在Activity中有一個預設的Looper物件,來處理子執行緒傳送的訊息 // 這裡我們嘗試的給其生成一個Looper物件,也是可以的 Looper looper = Looper.myLooper(); //獲得與子執行緒關聯的Looper物件 mHandler = new MyHandler(looper); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub // 啟動執行緒 new Thread(new MyThread()).start(); } }); }
1) 檢視Handler原始碼中的構造方法
可以發現在其構造方法中就已經預設為幫其生成一個Looper物件了: mLooper = Looper.myLooper();/** * Default constructor associates this handler with the queue for the * current thread. * * If there isn't one, this handler won't be able to receive messages. */ public Handler() { 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()); } } 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中獲取到一個訊息佇列,並且賦值給Handler的本地的mQueque,我們在看一下Handler(Looper looper)這個構造方法如下:同樣也是接受使用者生成的一個Looper物件。所以是底層實現方式都是一模一樣了,從這裡我們也知道了為什麼預設情況下主執行緒都會預設的Looper物件去維護了。/** * Use the provided queue instead of the default one. */ public Handler(Looper looper) { mLooper = looper; mQueue = looper.mQueue; mCallback = null; }
2) 這裡我們需要在看一下為什麼會呼叫 Looper.myLooper();會獲取到一個Looper物件,跟蹤其原始碼如下:
繼續跟蹤是誰給其sThreadLocal例項化/** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static Looper myLooper() { return sThreadLocal.get(); }
sThreadLocal 是從一個本地執行緒中獲取Looper型別的本地執行緒ThreadLocal物件,這裡只需要明白ThreadLocal是一個Android提供管理執行緒的一個東西。// sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
在prepare()方法中,會從sThreadLocal通過get獲取一個本地執行緒的物件,如果是空的話,這個東西中將new出來的Looper物件set到本地執行緒中。檢視ThreadLocal的get和set方法/** Initialize the current thread as a looper. * This gives you a chance to create handlers that then reference * this looper, before actually starting the loop. Be sure to call * {@link #loop()} after calling this method, and end it by calling * {@link #quit()}. */ public static void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper()); }
public T get() { // Optimized for the fast path. Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values != null) { Object[] table = values.table; int index = hash & values.mask; if (this.reference == table[index]) { return (T) table[index + 1]; } } else { values = initializeValues(currentThread); } return (T) values.getAfterMiss(this); }
public void set(T value) { Thread currentThread = Thread.currentThread(); Values values = values(currentThread); if (values == null) { values = initializeValues(currentThread); } values.put(this, value); }
也就是說它終究是通過set的方式講new出來的Looper物件扔到ThreadLocal中,由它來完成初始化和關聯一個執行緒,如果要得到一個Looper物件就從ThreadLocal中get出來。通過這種方式來關聯和初始化指定執行緒的Looper物件。
5. 在上面的一個Demo中,我們是實現了子執行緒傳送訊息給主執行緒來更新UI的操作和Looper的關係,子執行緒預設情況下是沒有Looper的物件的,下面我就來測試一下主執行緒向子執行緒傳送訊息,由於子執行緒預設沒有Looper,我們就來測試一下這樣實現會發生什麼情況?[注意,這種方式我們一般在實際開發中是很少見的],Demo2如下所示:
1) MainActivity.java 中貼出onCreate()和 MyThread 類裡面的程式碼段,讀者可以閱讀附件中的原始碼
編譯執行發出異常:@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initComponent(); new Thread(new MyThread()).start(); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // TODO Auto-generated method stub // 點選按鈕的時候在UI主執行緒中向子執行緒傳送訊息 Message message = Message.obtain(); message.obj = "AHuier"; mHandler.sendMessage(message); } }); } public class MyThread implements Runnable { @Override public void run() { mHandler = new Handler(){ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); // 由於不能在子執行緒中更新UI,所以我們輸出到控制檯. System.out.println("接受主執行緒中發出來的訊息" + msg.obj); } }; } }
從這裡我們可以得出結論在子執行緒中,預設情況下是沒有Looper物件的,所以我們需要根據博文上面的Looper類的說明新增prepare()方法 和 loop()方法來啟動Looper訊息迴圈。修改程式如下2)
2) 在MyThread子執行緒中新增prepare()方法 和 loop()方法完成Looper訊息迴圈的啟動
程式執行結果:public class MyThread implements Runnable { @Override public void run() { Looper.prepare(); mHandler = new Handler(){ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); // 由於不能在子執行緒中更新UI,所以我們輸出到控制檯. System.out.println("接受主執行緒中發出來的訊息" + msg.obj); } }; Looper.loop(); } }
6. 在這裡為什麼新增完這兩個方法之後就會有Looper訊息迴圈了?我們來檢視一下Looper的相關原始碼
1) prepare() 方法我們在上面已經知道,它會初始化當前的執行緒關聯一個Looper.
2) loop()原始碼如下
它首先獲取Looper物件,然後將訊息從Looper中取出,然後賦值給MessageQueue,讓MessageQueue去管理,接著在While(true)這個死迴圈裡面一直在輪轉的取訊息和分發訊息(從Message msg = queue.next();和msg.target.dispatchMessage(msg);)這兩句程式碼讀出。/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } MessageQueue queue = me.mQueue; // 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(); while (true) { Message msg = queue.next(); // might block if (msg != null) { if (msg.target == null) { // No target is a magic identifier for the quit message. return; } long wallStart = 0; long threadStart = 0; // 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); wallStart = SystemClock.currentTimeMicro(); threadStart = SystemClock.currentThreadTimeMicro(); } msg.target.dispatchMessage(msg); if (logging != null) { long wallTime = SystemClock.currentTimeMicro() - wallStart; long threadTime = SystemClock.currentThreadTimeMicro() - threadStart; logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); if (logging instanceof Profiler) { ((Profiler) logging).profile(msg, wallStart, wallTime, threadStart, threadTime); } } // 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(); } } }
三、總結與相關原始碼
通過上述兩個Demo和Looper相關原始碼的分析,我們可以知道Looper作為一個迴圈機制它的作用就是初始化執行緒和將Handler與該執行緒關聯的工作,以及管理,維護整個訊息迴圈的機制。但是具體的傳送訊息還有處理訊息都是靠Handler和Message來完成的。所以在一個新誕生的執行緒中,Looper都會關聯到這個Thread,以及它的MessageQueue和Handler.
原始碼下載: