編輯:關於Android編程
最近剛好在做關於異步通信的需求,那麼,今天我們來講解下Android開發中的Handler異步通信傳遞機制(包括Looper、Message Queue)
Android提供的一套消息傳遞機制
用於實現子線程對UI線程的更新,實現異步消息的處理:
- 在新啟動的線程中發送消息
- 在主線程中獲取並處理信息
在安卓開發中:
- 為了保證Android的UI操作是線程安全的,Android規定了只允許UI線程修改Activity裡的UI組件;
- 但在實際開發中,必然會用到多個線程並發操作UI組件,這又將導致UI操作的線程不安全
所以問題在於:如何同時滿足:
- 保證線程安全
- 使多個線程並發操作UI組件
Handler消息傳遞機制就是這個問題的。
主線程(UI線程)
- 定義:當程序第一次啟動時,Android會同時啟動一條主線程(Main Thread)
- 作用:主線程主要負責處理與UI相關的事件,所以主線程又叫UI線程
子線程則負責一些比較耗時的操作(聯網、取數據、SD卡數據加載等操作),而主線程和子線程之間的通信,就是要靠Handler了。
Message
- 定義:消息,理解為線程間通訊的數據單元(Handler接受和處理的消息對象。)
例如後台線程在處理數據完畢後需要更新UI,則可發送一條包含更新信息的Message給UI線程
Message Queue
- 定義:消息隊列
- 作用:用來存放通過Handler發過來的消息,按照先進先出執行
Handler
- 定義:Handler是Message的主要處理者
- 作用:負責將Message添加到消息隊列&處理Looper分派過來的Message
Looper
- 定義:循環器,扮演Message Queue和Handler之間橋梁的角色
每個線程中只能擁有一個Looper,但是一個Looper可以和多個線程的Handler綁定起來,也就是說很多個線程可以往一個Looper所持有的MessageQueue中發送消息。這就給我們提供了線程之間通信的可能。
Handler在創建的時候可以顯示指定Looper,這樣在Handler在調用sendMessage()投遞消息的時候會將消息添加到指定的Looper裡面的MessageQueue。如果不指定Looper,Handler默認綁定的是創建它的線程的Looper。
vcGVyoaJNZXNzYWdlUXVldWW52M+1wODNvA==" src="/uploadfile/Collfiles/20160502/2016050211204979.png" title="\" />
Handler
- 提供sendMessage方法,將消息放置到隊列中
- 提供handleMessage方法,定義個各種消息的處理方式;
Looper
- Looper.prepare():實例化Looper對象;為當前線程生成一個消息隊列;
- Looper.loop() :循環從消息隊列中獲取消息,交給Handler處理;此時線程處於無限循環中,不停的從MessageQueue中獲取Message 消息 ;如果沒有消息就阻塞
MessageQueue
- 提供enqueueMessage 方法,將消息根據時間放置到隊列中;
- 提供next方法,從隊列中獲取消息,沒有消息的時候阻塞;
Handler工作流程解釋
異步通信傳遞機制步驟主要包括異步通信的准備、消息發送、消息循環和消息處理
好像很復雜?那就先看下這個簡圖了解下:
public static final void prepare() {
//判斷sThreadLocal是否為null,否則拋出異常
//即Looper.prepare()方法不能被調用兩次
//也就是說,一個線程中只能對應一個Looper實例
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//sThreadLocal是一個ThreadLocal對象,用於在一個線程中存儲變量
//實例化Looper對象並存放在ThreadLocal
//這說明Looper是存放在Thread線程裡的
sThreadLocal.set(new Looper(true));
}
//再來看下Looper的構造方法
private Looper(boolean quitAllowed) {
//創建了一個MessageQueue(消息隊列)
//這說明,當創建一個Looper實例時,會自動創建一個與之配對的MessageQueue(消息隊列)
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
**2. 消息循環:loop()方法**
public static void loop() {
//myLooper()方法作用是返回sThreadLocal存儲的Looper實例,如果me為null,loop()則拋出異常
//也就是說loop方法的執行必須在prepare方法之後運行
//也就是說,消息循環必須要先在線程當中創建Looper實例
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//獲取looper實例中的mQueue(消息隊列)
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//進入消息循環
for (;;) {
//next()方法用於取出消息隊列裡的消息
//如果取出的消息為空,則線程阻塞
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//消息派發:把消息派發給msg的target屬性,然後用dispatchMessage方法去處理
//Msg的target其實就是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();
}
}
總結Looper的作用: 1. 實例化本身、與當前線程綁定、創建與之相應的MessageQueue:prepare()方法
消息循環(消息取出、消息派發):loop()方法一個線程只會有一個Looper實例,同時一個Looper實例也只有一個MessageQueue
首先看Handler的構造方法Handler是需要和線程綁定在一起的,在初始化Handler的時候一般通過指定Looper對象從而綁定相應線程,即給Handler指定Looper對象=綁定到了Looper對象所在的線程中,Handler的消息處理回調會在那個線程中執行。一般有兩種方法創建:
1. 通過Loop.myLooper()得到當前線程的Looper對象/通過Loop.getMainLooper()可以獲得當前進程的主線程的Looper對象。
2. 不指定Looper對象,那麼這個Handler綁定到了創建這個線程的線程上,消息處理回調也就在創建線程中執行.
public Handler() {
this(null, false);
}
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());
}
}
//通過Looper.myLooper()獲取了當前線程保存的Looper實例,如果線程沒有Looper實例那麼會拋出異常
//這說明在一個沒有創建Looper的線程中是無法創建一個Handler對象的
//所以說我們在子線程中創建一個Handler時首先需要創建Looper,並且開啟消息循環才能夠使用這個Handler。
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
//獲取了這個Looper實例中保存的MessageQueue(消息隊列)
//這樣就保證了handler的實例與我們Looper實例中MessageQueue關聯上了
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
上述說明:當Handler初始化時,可通過構造方法自動關聯Looper和相應的MessageQueue
send的發送方法:sendMessage()
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
//我們往下扒
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
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
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//調用了enqueueMessage方法
return enqueueMessage(queue, msg, uptimeMillis);
}
//調用sendMessage方法其實最後是調用了enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//為msg.target賦值為this,也就是把當前的handler作為msg的target屬性
//如果大家還記得Looper的loop()方法會取出每個msg然後執行msg.target.dispatchMessage(msg)去處理消息,其實就是派發給相應的Handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//最終調用queue的enqueueMessage的方法,也就是說handler發出的消息,最終會保存到消息隊列中去。
return queue.enqueueMessage(msg, uptimeMillis);
}
Post的發送方法:sendMessage()
showhandler.post(new Runnable() {
@Override
public void run() {
String line = "\n";
StringBuffer text = new StringBuffer(show.getText());
text.append(line).append("angelababy:Yes,I do");
show.setText(text);
}
相比send方法,post方法最大的不同在於,更新UI操作可直接在重寫的run方法定義。 其實Runnable並沒有創建什麼線程,而是發送了一條消息,下面看源碼:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
//創建了一個Message對象
//創建Message對象可以new,也可以使用Message.obtain()方法;
//但是更建議使用obtain方法,因為Message內部維護了一個Message池用於Message的復用,避免使用new 重新分配內存。
Message m = Message.obtain();
//將我們創建的Runable對象作為callback屬性,賦值給了此message.
m.callback = r;
//創建了一個Message對象
return m;
}
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);
}
從上面的源碼發現了吧?和send中的handler.sendMessage是一樣的。
調用了sendMessageAtTime,然後調用了enqueueMessage方法,給msg.target賦值為handler,最終Handler將消息加入MessagQueue.
但是細心的你會發現,在使用Post方法時會將我們創建的Runable對象作為callback屬性賦值給了此message
那麼msg的callback和target都有值,那麼會執行哪個呢?
我們已知回調發送消息的方法是:dispatchMessage()
public void dispatchMessage(Message msg) {
//一開始就會進行判斷
//如果msg.callback屬性不為null,則執行callback回調,也就是我們的Runnable對象
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
####2. 處理Looper派發過來的消息:dispathMessage()方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
public void handleMessage(Message msg) {
}
可以看到dispathMessage()方法裡調用了 handleMessage()方法,但handleMessage()是一個空方法 因為Handler發送消息過來是希望進行一定的處理,至於怎麼處理消息是該Handler最終控制的,所以我們在創建handler時需要通過復寫handleMessage()方法從而實現我們需要的消息處理方式,然後根據msg.what標識進行消息處理。
這就是為什麼我們在主線程中實例化Handler的時候需要重寫handleMessage()
在一個Android應用啟動的時候,會創建一個主線程,即ActivityThread(也叫UI線程),在ActivityThread中有一個靜態的main方法:應用程序的入口點
//一個進程會默認生成一個主線程
public static void main(String[] args) {
......
//主線程生成時自動通過prepareMainLooper方法為主線程創建一個Looper
//prepare()方法是用於在子線程中創建一個Looper對象,在子線程中是可以退出消息循環的:調用消息隊列的quit方法
//Looper生成時會自動生成與之配套的消息隊列
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
......
//loop()方法開啟消息循環
//主線程的消息循環是不允許被退出的
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
即消息隊列,用於存放Handler發送過來的消息
為了提高插入刪除的效率,采用單鏈表的方式實現。
對於MessageQueue,我們來看下入隊和出隊操作
MessageQueue入隊
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
msg.markInUse();
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創建的時間進行插入。
MessageQueue出隊
Message next() {
......
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// nativePollOnce方法在native層,若是nextPollTimeoutMillis為-1,這時候消息隊列處於等待狀態。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//按照我們設置的時間取出消息
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// 如果消息隊列中沒有消息,將nextPollTimeoutMillis設為-1,下次循環消息隊列則處於等待狀態
nextPollTimeoutMillis = -1;
}
//退出消息隊列,返回null,這時候Looper中的消息循環也會終止。
if (mQuitting) {
dispose();
return null;
}
......
}
.....
}
}
把Handler工作原理都講解完了,我們再來看下一開始說的工作原理圖,你大概會有更深的理解了。
沒有實際應用的博客都不是好博客!現在是時候看一下實際應用了
1. 布局文件:
activity_main
2. 1 send方法:MainActivity
package com.example.carson_ho.handlerdemo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private TextView show;
private Handler showhandler;
@Override
//主線程創建時便自動創建Looper和對應的MessageQueue,之前執行Loop()進入消息循環
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
show = (TextView) findViewById(R.id.show);
//實例化Handler,這裡並無指定Looper,即自動綁定當前線程(主線程)的Looper和MessageQueue
showhandler = new FHandler();
//啟動子線程
new Thread_1().start();
new Thread_2().start();
}
class FHandler extends Handler{
//通過復寫handlerMessage()從而決定如何進行更新UI操作
@Override
public void handleMessage(Message msg) {
StringBuffer text = new StringBuffer();
switch (msg.what) {
case 1:
text.append("I love Carson_Ho");
show.setText(text);
break;
case 2:
text.append("I hate Carson_Ho");
show.setText(text);
break;
}
}
}
class Thread_1 extends Thread {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//定義要發送的消息
Message msg = Message.obtain();
msg.what = 1;//用於消息的標識
msg.obj = "AA";//用於消息的存放
//傳入主線程的Handler並其MessageQueue發送消息
showhandler.sendMessage(msg);
}
}
class Thread_2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = Message.obtain();
msg.what = 2;
msg.obj = "BB";
showhandler.sendMessage(msg);
}
}
}
2. 2 Post方法:MainActivity
package com.example.carson_ho.handlerdemo;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
public TextView show;
public Handler showhandler;
@Override
//主線程創建時便自動創建Looper和對應的MessageQueue,之前執行Loop()進入消息循環
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
show = (TextView) findViewById(R.id.AA);
StringBuffer text = new StringBuffer();
text.append("Carson_Ho:Do you love me?");
show.setText(text);
//實例化Handler,這裡並無指定Looper,即自動綁定當前線程(主線程)的Looper和MessageQueue
showhandler = new Handler();
//啟動子線程
new Thread_1().start();
new Thread_2().start();
}
class Thread_1 extends Thread {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
showhandler.post(new Runnable() {
@Override
public void run() {
String line = "\n";
StringBuffer text = new StringBuffer(show.getText());
text.append(line).append("angelababy:Yes,I do");
show.setText(text);
}
});
}
}
class Thread_2 extends Thread {
@Override
public void run() {
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
showhandler.post(new Runnable() {
@Override
public void run() {
String line = "\n";
StringBuffer text = new StringBuffer(show.getText());
text.append(line).append("黃曉明:what the fuck?");
show.setText(text);
}
});
}
}
}
本文對Handler異步通信機制全面解析(包含Looper、Message Queue)進行了全面介紹和分析,接下來我會介紹繼續介紹Android開發中的相關知識。
前面的文章已經講述了隨手拍項目圖像處理的技術部分,該篇文章主要是主界面的布局及屏幕滑動切換,並結合鴻洋大神的視頻和郭神的第一行代碼(強推兩人Android博客),完成了下
遇到的問題如下:java.lang.NullPointerException: Attempt to invoke virtual method ‘void
出於性能優化考慮,android的UI操作並不是線程安全的,這意味著意味著如果有多個線程並發操作UI組件,可能導致線程安全問題,未解決此問題, 從開發的角度來說, Han
今天在逛安智的時候看到一個軟件,我對注冊碼驗證的程序比較感興趣哈,- -那個帖子的軟件是通過爆破法實現破解的,之前我在這個帖子講過http://www.52pojie.c