編輯:關於Android編程
Android的消息機制其實在android的開發過程中指的也就是Handler的運行機制,這也就引出了android中常見的面試問題:
簡述Handler、Looper、MessageQueue的含義,以及它們之間的關系 簡述Handler的運行機制 說明Handler、Looper以及Message之間的關系Handler機制為什麼這麼重要呢?
我們知道android設備作為一台移動設備,不管是內存或者還是它的性能都會受到一定的限制:過多的使用內存會使內存溢出(OOM);另外一方面,大量的使用CPU的資 源,一般是指CPU做了大量耗時的操作,會導致手機變得很卡甚至會出現程序無法響應的情況,即出現ANR異常(Application Not Responding)。ANR異常對於我們來說的話其實並不陌生:在UI線程中如果5秒之內沒有相應的輸入事件或者是BroadcastReceiver中超過10秒沒有完成返回的話就會觸發ANR異常,這樣就要求我們必須要在寫代碼的時候格外注意不能將大量耗時的操作放在UI線程中去執行,例如網絡訪問、數據庫操作、讀取大容量的文件資源、分析位圖資源等…
Handler機制的主要作用
Android系統的這些特性導致開發者在進行開發的時候必須要將比較耗時的操作遠離UI線程(ActivityThread),單獨的將這些操作放入到子線程中進行執行,在子線程完成代碼執行之後將等到的結果數據返回到UI線程(android系統規定在子線程中不允許訪問UI),同時UI線程根據這些返回的數據更新界面UI,從而實現了人機之間友好的交互。
那麼對於以上的結論,我們會發現如下的問題:
在子線程執行完畢的代碼如何才能將數據返回到UI線程呢? 在這個過程中,android系統到底都做了些什麼呢?接下來我們就開始介紹Handler的具體的運行機制
Handler機制簡介
Handler運行機制是需要MessageQueue、Looper、Handler三者的相互協調工作,但是實際上這三者就是一個整體,只不過我們在開發的過程中只是接觸到了Handler一個而已。Handler的主要作用就是將一個任務切換到指定的線程中去執行。而這樣的特性正好可以用來解決在子線程中無法訪問UI的矛盾。
那麼接下來我們就來了解下MessageQueue、Looper、Handler的運行機制,以及它們最基本的運行原理。
Handler機制之MessageQueue介紹
MessageQueue通常翻譯為“消息隊列”,它內部存儲了一組數據,以隊列的形式對外提供插入和刪除操作。雖然被稱之為消息隊列,但是實際上它的數據結構卻是采用的單鏈表的結構來存儲消息列表(單鏈表在插入和刪除操作上效率比較高)。
MessageQueue主要包含兩個操作:插入(enqueueMessage)和讀取(next)。
對於enqueueMessage和next方法的源碼請讀者去自行查看,因為這兩個方法占得篇幅較大,在這裡就不貼出來了,本處就說明下這兩個方法的主要作用。需要指出的是,next方法是一個無限循環的方法,如果消息隊列中沒有消息,那麼next方法會一直堵塞,有新消息的事後,next方法會返回這條消息並從鏈表中刪除該消息。
Handler機制之Looper介紹
Looper可以理解為消息循環,因為MessageQueue只是一個存儲消息隊列,不能去處理消息,所以需要Looper無限循環的去查看是否有新的消息,如果有的話就處理消息,否則就一直等待(阻塞)。 從宏觀的角度來分析的話,每一個異步線程,都維護著唯一的一個Looper,每一個Looper會初始化(維護)一個MessageQueue,之後進入一個無限循環一直在讀取MessageQueue中存儲的消息,如果沒有消息那就一直阻塞等待。
Looper中有2個比較重要的方法:
Prepare(); Loop();Looper.prepare()簡介
Handler的工作需要Looper,沒有Looper的線程就會報錯,所以就必須要通過Looper.prepare()方法為當前線程創建一個Looper,之後用Looper.loop()方法來開啟消息循環。但是對於我們開發者來說,當我們在UI線程中實例化handler的時候並沒有要求對Looper進行初始化,照樣可以是運行沒有任何問題的。其原因就是因為UI線程在被創建的時候就會初始化Looper,這樣也就明白了在UI線程中可以默認使用handler的原因了。
Looper.prepare()源碼如下:
/** 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() {
prepare(true);
}
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));
}
我們發現該方法比較簡單,prepare方法完成了Looper對象的初始化,對於源碼中所說的sThreadLocal對象來說,它是ThreadLocal的實例化對象。
通過以上對於源碼的理解,我們可以知道該方法主要做了如下工作:
另外,下面我們通過一個Demo來展示下Handle初始化是需要事先調用Looper.perpare()的必要性:
**
* @author zwjian
* @des init Looper testActivity
*/
public class InitLooperActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread("Thread#1") {
@Override
public void run() {
super.run();
Handler handler = new Handler();
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
super.run();
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();
}
}
上述代碼很簡單,我們來看一下運行結果:
我們可以發現該運行時異常指的就是在Thread#1中沒有初始化Looper導致handler實例化失敗(後面我們會講到這個異常是怎麼拋出的)。
Looper.loZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcCgpvPK96TwvZW0+PC9zdHJvbmc+PC9wPg0KPHA+uMO3vbeotcTW99KquabE3L7Nyseyu7bPtNNNZXNzYWdlUXVldWXW0LbByKHP+8+io6y9u7j4z/vPorXEdGFyZ2V0yvTQ1LXEZGlzcGF0Y2hNZXNzYWdlyKW0psDtoaM8L3A+DQo8cD7G5NS0wuvVucq+yOfPwqO6PC9wPg0KPHByZSBjbGFzcz0="brush:java;">
在該方法中,我們可以看到通過之前存儲在threadLocal對象中的looper對象得到了該線程中對應looper對象中維護的MessageQueue消息隊列,之後進入了一個無限循環,對消息隊列中的數據進行讀取,並且會把消息通過msg.target.dispatchMessage(msg);進行處理,對於msg.target來說,其實它就是與之綁定的handler實例,在下面我們會進行詳細分析。 以上就是對於Looper的詳細介紹了,對於Looper的總結可以如下: Handler機制之Handler介紹 handler的工作主要是消息的發送和接收過程。消息的發送可以通過post和send等一系列的方法來實現。但是需要說明的是,通過post的方法最後還是通過send來實現的。 通常我們在使用handler之前都會進行初始化,比如說比較常見的更新UI線程,我們會在聲明的時候直接初始化。那麼問題來了,handler是怎樣和Looper以及MessageQueue聯系起來的呢?它在子線程中發送的消息怎麼發送到了MessageQueue中呢? 下面我們通過查看Handler的源碼來進行這些疑點的解釋: 首先我們先來看下Handler的構造函數: 很顯然,我們可以發現在該構造函數中完成了該handler與looper的綁定,以及獲得該looper對象中的消息隊列等操作。同時在對mLooper進行判空的時候我們就可以發現一句熟悉的異常:”Can’t create handler inside thread that has not called Looper.prepare()”,而這個異常就是在前文中我們在沒有調用Looper.prepare()方法而直接實例化handler時所報的異常信息了。 接下來我們來看一下handler發送消息的過程: 一般我們使用handler發送消息的函數可以有好幾個,在這裡我們使用如下代碼進行發送: 接下來我們就去看看它的發送過程是怎樣的: 最後調用的是handler的enqueueMessage方法,在該方法中我們發現了一條熟悉的語句:msg.target=this;這句話就是對msg.target賦值為handler對象本身,正好對應上前文在looper.loop()方法中msg.target.dispatchMessage(msg); 同時我們一路跟下來發現最後調用的就是queue.enqueueMessage方法,而這個方法就是我們前面介紹的消息隊列中進行插入消息的那個方法。這樣一來,我們就明確了handler發送的消息其實最後是進入了消息隊列中,那插入消息到消息隊列之後的操作是什麼呢?我們接著往下看。 通過前面我們所說的Looper中的loop方法,Looper . Loop()方法中通過無限循環對消息隊列進行讀取消息,得到消息之後通過消息的target屬性中的disPatchMessage()方法回調handlerMessage等方法。而handlerMessage方法就是我們在UI線程中實例化handler的過程中重寫的handlerMessage方法。 disPatchMessage()源碼如下: 我們發現在這個函數中其實有兩種處理,一個是執行handleCallback(msg);另外一個就是執行handleMessage(msg);對應源碼如下: 我們知道在我們實例化handler的時候,如果采用post方法,一般要new 一個Runnable對象,具體如下: 或者是這樣的形式: 而所謂的disPatchMessage所要做的就是對這兩種操作進行回調,之後執行開發者自行重寫的代碼,這樣就實現了從一個線程中發送消息,在另外一個線程收到消息的全過程。 下面我們照樣是通過一個簡單的Demo來說明下: 相應的xml布局文件: 對應的Activity 以上,我們通過在thread#2中發送了一條消息,在UI線程中進行接收,當然這裡寫了2種接收方式,讀者可以自行去注釋掉某一種去進行演示,最後可以通過這個demo發現這兩種方式都是可以被回調執行的,通過執行我們可以看到textView的顯示發生了相對應的變化,這樣,我們就完成了handler消息的傳遞的全過程。 最後,我們來總結下handler機制的全過程: 以上就是handler機制的所有分析,由於個人理解有限,有錯誤之處還請諸位讀者能批評指正。 /**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
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;
// 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();
for (;;) {
Message msg = queue.next(); // might block
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);
}
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.recycleUnchecked();
}
}
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class 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 = callback;
mAsynchronous = async;
}
handler.sendMessage(new Message());
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
/**
* Handle system messages here.
*/
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();
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
//to do change UI
}
});
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case xxx:
//to do change UI
break;
}
}
};
/**
* @author zwjian
* @des show handler + message changes UI
*/
public class HandlerAct extends AppCompatActivity {
private static final int HANDLER_TEST_VALUE = 0X10;
private Handler handler;
private TextView test_tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
test_tv = (TextView) findViewById(R.id.test_tv);
//first method
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case HANDLER_TEST_VALUE:
test_tv.setText("我是第一個方法:誰說的?你才是一個壞人!");
break;
}
}
};
//another method
handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
test_tv.setText("我是第二個方法:誰說的?你才是一個壞人!");
}
});
new Thread("Thread#2") {
@Override
public void run() {
super.run();
// handler.sendEmptyMessage(HANDLER_TEST_VALUE);
handler.sendMessage(new Message());
}
}.start();
}
}
我們打開QQ空間的時候有個箭頭按鈕點擊之後彈出PopupWindow會根據位置的變化顯示在箭頭的上方還是下方,比普通的PopupWindow彈在屏幕中間顯示好看的多。先看
1.界面設置默認的 Android Studio 為灰色界面,可以選擇使用炫酷的黑色界面。Settings --> Appearance --> Theme
在輸入框中輸入我們想要輸入的信息就會出現其他與其相關的提示信息,這種效果在Android中是用AutoCompleteTextView實現的。<AutoComple
小豬的Android入門之路 Day 7 part 4 Android的數據存儲與訪問之——ContentProvider(內容提供者)