NetworkOnMainThreadException
一個簡單的案例:我們想通過網(wǎng)絡(luò)請求獲取一段文字,顯示在頁面中
public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
private Button mButton;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(this);
mTextView = (TextView) findViewById(R.id.textView1);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
HttpURLConnection connection;
BufferedReader bufferedReader;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
InputStream inputStream = connection.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
final StringBuilder response = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
response.append(line);
}
mTextView.setText(response.toString());
} catch (IOException e) {
e.printStackTrace();
}
break;
}
}
}
思路很簡單,點擊事件中,通過一段普通的網(wǎng)絡(luò)操作,向百度首頁請求信息,并從返回的字節(jié)流中讀取出文字,最后把結(jié)果顯示在 mTextView上,但運行即可發(fā)現(xiàn)如下報錯:
E/AndroidRuntime: FATAL EXCEPTION: main
android.os.NetworkOnMainThreadException
at android.os.StrictMode$AndroidBlockGuardPolicy.onNetwork(StrictMode.java:1145)
NetworkOnMainThreadException,“在主線程里進行網(wǎng)絡(luò)操作異常”,可以回想起來,我們常常說諸如大型文件讀寫,網(wǎng)絡(luò)操作等耗時的操作,要放在子線程里,以免阻塞主線程的進行。在 Android 應(yīng)用程序里,主線程控制下的應(yīng)用界面很忙,它在一直進行顯示工作,由于 APP 的交互性,還必須去響應(yīng)隨時到來點擊事件,button 鍵、back 鍵和 home 鍵,一旦某個按鍵不立即響應(yīng),就會產(chǎn)生卡死的效果,這是一個交互性應(yīng)用要絕對避免的。
回到我們的例子,主線程中,點下按鈕,開始在主線程進行訪問網(wǎng)絡(luò)操作,如果網(wǎng)絡(luò)過程沒結(jié)束,就意味著主線程內(nèi)其它的操作不能進行,即使這時用戶不耐煩地點下 back 鍵要退出,界面也不會有一點反應(yīng),這太糟糕了!
所以耗時操作放在子線程中,點擊按鈕,OK,讓子線程去忙吧,主線程還是可以應(yīng)付交互操作的。
CalledFromWrongThreadException
于是,我們新建一個子線程,將網(wǎng)絡(luò)操作部分挪了進去,然后 start 起來
public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
private Button mButton;
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(this);
mTextView = (TextView) findViewById(R.id.textView1);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection;
BufferedReader bufferedReader;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
InputStream inputStream = connection.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
final StringBuilder response = new StringBuilder();
String line;
while ((line = bufferedReader.readLine())!=null){
response.append(line);
}
mTextView.setText(response.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
break;
}
}
}
又報錯了,不過這次的異常名稱不太一樣
E/AndroidRuntime: FATAL EXCEPTION: Thread-290
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6087)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:868)
讀一下異常信息:首先這個異常是從 android.view.ViewRootImpl的 checkThread 方法拋出的,內(nèi)容翻譯過來是“只有創(chuàng)建視圖層次的原始線程才能觸及其視圖”,翻譯成人話,其實就是我們常常念叨的“只有主線程才能更改界面”。嗯,回到我們的例子,我們確實是在一個隨意新建的子線程中,試圖去訪問并更改 mTextView 內(nèi)容,這才引發(fā)的異常。
關(guān)于 ViewRootImpl 相關(guān)方法,涉及到布局繪制,AMS等紛繁的領(lǐng)域,筆者暫不能展開詳解…如果一定要知道為什么只有主線程可以訪問 UI,子線程碰都碰不得這個問題,讀者可以暫且這么理解:學習并發(fā)的時候我們就知道,最大的隱患就是多線程的同步問題,如果程序里有多個子線程,在進行完數(shù)據(jù)讀取后,要更改的是同一個目標 —— mTextView,就難免引發(fā)數(shù)據(jù)錯亂,影響界面內(nèi)容。
所以把結(jié)果都交給主線程吧,通知它一個人有條不紊地進行下去。
這樣,我們的目的就很明確了,要在主線程里運行
mTextView.setText(response.toString());
但子線程的工作結(jié)果 response,要如何交給主線程?
在 Android中,這就是異步消息處理問題,Android 提供了 Handler、Message 等工具,為子線程和主線程之間的數(shù)據(jù)傳遞,打開了通道!
初識 Handler/Message
話不多說,看看一段正式的 Handler 代碼是如何解決這個問題的:
public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
private Button mButton;
private TextView mTextView;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 200:
mTextView.setText((String) msg.obj);
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
mButton = (Button) findViewById(R.id.button1);
mButton.setOnClickListener(this);
mTextView = (TextView) findViewById(R.id.textView1);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection connection;
BufferedReader bufferedReader;
try {
URL url = new URL("https://www.baidu.com");
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
InputStream inputStream = connection.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
final StringBuilder response = new StringBuilder();
String line;
while ((line = bufferedReader.readLine())!=null){
response.append(line);
}
Message message = Message.obtain();
message.what = 200;
message.obj = response.toString();
mHandler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
break;
}
}
}
代碼修改成這樣,我們再運行,終于成功地顯示了網(wǎng)絡(luò)請求的數(shù)據(jù)。來看看現(xiàn)在的操作和原來有什么不同:
1.在主線程新建了一個匿名類 mHandler,并實現(xiàn)了它的一個方法叫 handleMessage(Message msg)
2.子線程里訪問 UI 控件的那句代碼被挪到了上述 handleMessage(Message msg) 方法里,空出的地方則替換成了幾句 Message 相關(guān)的代碼
mHandler,是主線程設(shè)置的專門負責接收子線程的消息處理類,當下子線程中 Message 相關(guān)的幾句可以這么理解:
1.子線程找來一個消息盒子:Message message = Message.obtain(); // 為什么不直接 new Message(); 讀者可以自己搜索一下
2.盒子上的代號設(shè)置為200:message.what = 200;
3.盒子內(nèi)容是子線程的結(jié)果:message.obj = response.toString();
4.向目標 mHandler 發(fā)送:mHandler.sendMessage(message);
就這樣,一個消息就由子線程,發(fā)向了身處主線程里的 mHandler,經(jīng)處理后交由主線程決定呈現(xiàn)內(nèi)容~~~
關(guān)于新建 handler 的意外
這時,子線程覺得 mHandler 這個角色很不錯,決定也搞一套類似“主線程 —— Handler —— 子線程”這種模式,自己效仿主線程,也設(shè)立一個消息處理人handler(雖然好像根本沒有其他線程找他通訊托辦事……)但是他不管,就是要 Handler!那就來吧:
public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
……
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
……
}
}
};
private Handler mZiHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
……
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
...
new Thread(new Runnable() {
@Override
public void run() {
……
mZiHandler = new Handler();
}
}).start();
break;
}
}
}
于是,聲明了 mZiHandler 后,子線程在自己的 run() 方法里 new 了一個 Handler 做初始化,運行一下吧
RuntimeException: "Can't create handler inside thread that has not called Looper.prepare()"
報錯了,可見,Handler 這個東西,不是你子線程想建就能建??!
新角色 Looper
“不能在一個沒有 prepare looper 的線程內(nèi)創(chuàng)建 handler”,對于這個解釋,子線程不是很滿意,有至少兩個疑問
1.Looper 是干什么的,為什么沒它連 Handler 都 new 不了?
2.主線程也沒見準備Looper,為什么它就 new 了?
先不管了,讓咱準備 looper 那就先 prepare 一下 looper 吧,然后再新建 handler
public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
……
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
……
}
};
private Handler mZiHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
……
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
new Thread(new Runnable() {
@Override
public void run() {
……
Looper.prepare();
mZiHandler = new Handler();
}
}).start();
break;
}
}
}
果然,加上這句就正常運行了。接下來,就來回答一下子線程的兩個疑問吧,先從第二個疑問開始。
主線程的 Looper 哪兒來的?
原來,應(yīng)用程序啟動的代碼如下
public final class ActivityThread {
public static final void main(String[] args) {
......
Looper.prepareMainLooper();
......
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();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
可以看見里面有這么一句
Looper.prepareMainLooper();
嗯,原來主線程在應(yīng)用啟動的時候就著手準備主 Looper 了,那去看看 Looper 類里這個方法具體干了什么吧。
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
...
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
...
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));
}
...
/**
* Return the Looper object associated with the current thread. Returns null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}
一行一行看過程,走到 prepare(false),先檢查 sThreadLocal.get(),目前的情況是程序剛啟動,如果這剛開始還沒設(shè)置呢,就 get 到了一個不為空的 looper,顯然是不合理的。那就報錯吧,“一條線程只能有一個 looper”。如果順順利利地 get 到了 null,才現(xiàn)場 new 一個不可取消的 looper,并為當前Looper類的靜態(tài)成員 sThreadLocal set 進去這個 looper,這個 Looper,就是主線程獨有的 MainLooper。至此,我們應(yīng)用程序的主線程里就始終存在著這個主 Looper對象了。
(讀者到這里可能有疑問,如果子線程都準備了自己的 looper,想獲得這個 looper 時,應(yīng)該走的是上面代碼中 myLooper() 方法,然后走 sThreadLocal.get(),但是!這個 sThreadLocal 是靜態(tài)類,也就是所有 looper 對象共享的啊,get 的時候豈不所有線程得到的都是同一份 looper ??? 其實這就是 ThreadLocal 的妙用了,先回答你,在不同線程里 sThreadLocal.get() 的并不是同一個結(jié)果,而是線程各自的 looper 哦!之后會新開文章詳細介紹的。)
Looper 在創(chuàng)建 handler 時的作用
主線程的 looper 哪兒來的了知道了,那么回到第一個疑問:為什么沒有 looper,handler 就建立不了呢?
我們看看 Handler 的源碼吧,其構(gòu)造方法有重載,不過最終都輾轉(zhuǎn)到其中一個
public class Handler {
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
...
public Handler(Callback callback, boolean async) {
...
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 的成員里就有 mLooper!而且在 Handler 的構(gòu)造方法里,其對 mLooper 進行了初始化和檢查。
初始化時,拿的是當前所在線程的 looper:Looper.myLooper()。檢查時,如果發(fā)現(xiàn)當前線程 Looper 為空,就會報出我們之前見的"Can't create handler inside thread that has not called Looper.prepare()"異常!
同時我們看到了 Handler 的成員里還有個 Callback 成員,還有個 MessageQueue 成員,前者先留個伏筆,后者是 MessageQueue,熟悉嗎?翻看上面的 Looper 源碼,MessageQueue 本身是 Looper 的成員,這里的 handler 在構(gòu)造方法中,經(jīng)由 mLooper,也得到了 mQueue 的引用。
事實上,mLooper 作為 Handler 的必需成員,其自身成員 mQueue 也擔負著重要功能!我們馬上講到。
目前,為什么沒 Looper 就不能創(chuàng)建 Handler 子線程也算是明白了。
mQueue 在 Handler 里的作用
整理一下思路,還記得子線程發(fā)送 message 給 mHandler 的操作嗎?當時包裝好消息盒子,最后一步是干什么來著?
mHandler.sendMessage(message);
沒錯,就這么一句,子線程自己好像沒干什么,包裝好的消息盒子就發(fā)到 mHandler 那里了,然后又到 mHandler 的 handleMessage() 方法里了。這么說,是 mHandler 找各子線程一個個搜集消息盒子然后直接處理?子線程不確定,它想搞清楚自己的消息盒子是怎么流轉(zhuǎn)的,于是決定去 Handler 的 sendMessage(message) 源碼里看個究竟。
mHandler.sendMessage(message) 方法幾經(jīng)輾轉(zhuǎn),最后到了 Handler 的這個方法里
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);
}
可以看到,子線程的消息,連同發(fā)送時間,都被記錄走了,等等,我們看到了什么?mQueue!接著看 enqueueMessage(queue, msg, uptimeMillis),看 mQueue 在這里干什么
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
代碼最后,程序由 Handler 的方法,走進了 MessageQueue 的 enqueueMessage(msg, uptimeMillis) 方法,看來消息盒子都進 mQueue 里去了!
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;
}
可以看出,在 mQueue 內(nèi)部,Message 作為 Node 以鏈表的形式,按時間順序一個一個地鏈接了起來,同時可以看見其內(nèi)部操作都有同步鎖加持,保證了多線程下傳遞消息的安全。
子線程明白了,消息盒子從自己內(nèi)部發(fā)給 mHandler 后,并不是直接就被處理了,而是悄悄排在了它內(nèi)部的 mQueue 里,mHandler 應(yīng)該就是以這種方式才有條不紊地將各個消息處理的吧。
嗯,至此,子線程明白了自己的消息盒子去了哪里,又以怎樣的形式排列著,全靠的是 mHandler 的 mQueue!
至于 mHandler 內(nèi)部的處理方式,子線程不在意,畢竟消息的處理自己可說不上話。
Looper 在 Handler 處理 Message 時的作用
再總結(jié)一下吧,一條條子線程,有消息就包一個盒子,發(fā)給主線程的 handler,在 handler 內(nèi)部,盒子按時間順序鏈接在 mQueue 里,現(xiàn)在的疑問是,這些盒子是如何一個個送到我們重寫的 handleMessage() 方法里的呢。
按道理,盒子要被一個個取出,我們就得看見 mQueue 鏈表遍歷的操作,mQueue 是 Handler 成員,看看 Handler 源碼有沒有遍歷操作看了一遍,似乎沒有,別忘了,mQueue 也是 Looper 的成員,再去 Looper 源碼看看,這時,一個叫 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;
...
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
看見沒!mQueue 就是在這里被遍歷,將 Message 各個取出的!很好,取出后走到
msg.target.dispatchMessage(msg);
嗯,這應(yīng)該就是 Message 的處理過程了。
至此,我們知道,mQueue 中排列的消息,是通過 Looper 的 loop() 方法遍歷取出并交由 mHandler 處理的。
再回看 ActivityThread 的 main() 方法,果然當時其創(chuàng)建完主線程的 MainLooper,緊接著就 loop 了起來,動作真快啊!回想起某個傻傻的子線程,上來就設(shè)立 handler 失敗不說,等準備了 looper 再設(shè)立 handler,最后還忘了 loop(),就算有線程給你發(fā)消息你也分發(fā)處理不起來呀,圖樣啊~不過,這個子線程愿意學習先進技術(shù),并有獲取其他線程消息,自己處理的想法,敢想敢干,以后的文章肯定可以見到他發(fā)光發(fā)熱的一天!
等等,先別回憶了,這里又有疑問啦,兩個:
1.處理 Message 的確實應(yīng)該是 mHandler 我們知道,但你這里的處理者是 msg.target 啊,這倆是一個東西嗎?
2.處理方法名字對不上啊,你這里是 dispatchMessage(msg) ,我們新建的 mHandler 重寫的方法叫 handleMessage(Message msg) 啊
先看第一個問題:
通過查看 Message 的源碼,我們了解到其含有一個 Handler 類型的成員,不出意外,這個 msg.target 應(yīng)該就是我們的 mHandler 了。那什么時候設(shè)置的這個 target 的呢?往回看 Handler 的 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) 方法,第一句 :
msg.target = this;
this 是什么,當然就是我們的 mHandler 啦,所以沒毛病,msg.target 就是我們的 mHandler!
再看第二個問題:
這里消息處理的方法叫 dispatchMessage(msg) ,而我們新建的 mHandler 重寫的方法叫 handleMessage(Message msg) ,不多說,看看前者的源碼:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看出,Message 的處理方法有三個走向
走向一:
被處理的 Message 如果自帶 callback 的話,就走 handleCallback(Message message),嚯,消息盒子還能帶著回調(diào)去讓 Handle 處理吶。再看看其內(nèi)部實現(xiàn):
private static void handleCallback(Message message) {
message.callback.run();
}
直接運行所攜帶的 callback 的 run() 方法了,等于說,子線程帶著自己定義的處理方式,直接找 Handler 讓它照著辦,臉可真夠大的……
并且我們應(yīng)該可以得知,Message 自帶的 callback 是 Runnable 類型的。
至于當時發(fā)送 Message 時怎么加上個 Runnable 的,讀者請先翻翻源碼自己看一下,文章最后會講,到時再來驗證。
走向一說完,感覺這個走向應(yīng)該不是我們重寫所實現(xiàn)的……
走向二:
如果 mCallback 不為空,就走 mCallback.handleMessage(msg)
怎么又是一個 callback!先別急,此 mCallback 非彼 callback,之前的 callback 屬于 Message的成員,還是 Runnable類型。這里的 mCallback呢,我們回看一下上面的 Handler 源碼處,還記得我們當時留的伏筆嗎
public interface Callback {
public boolean handleMessage(Message msg);
}
原來,這個 mCallback 是 Handler 的成員,而且類型也不是 Runnable,而是一個要求實現(xiàn) handleMessage(Message msg) 方法的接口,該方法也正是該走向的真正處理方式。等于說,我們新建 mHandler 的時候,不是以匿名類的方式重寫 handleMessage(Message msg),而是給一個普通 Handler 對象設(shè)置一個 Callback 成員,這個成員是個接口,自己要 handleMessage(Message msg)方法。
和走向一一樣,怎么給一個普通的 mHandler 設(shè)置 Callback,也翻一翻 Handler 源碼的構(gòu)造函數(shù)去試試吧。
不過,這里雖然名字也是 handleMessage(Message msg),但也不是我們重寫所產(chǎn)生的效果……
走向三:
如果 message 既沒有自帶 callback,mHandler 也沒有設(shè)置 Callback 成員,或者有 Callback 成員但處理結(jié)果返回 false,那就走向了最終的 handleMessage(Message msg) 方法。
這個也叫 handleMessage(Message msg)!?。≌以创a看一下
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(Message msg) {
}
子類要接收 Message 必須實現(xiàn)的方法!沒錯了,這就是我們原程序里的重寫方式,我們當時寫的,就是走向三。
最后,我們將走向一和走向二的寫法也展示一下吧。
走向一:你可能沒有找到給 message 設(shè)置 Runnable 類型的 callback 的操作,不過看下面的程序
public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
...
private Handler mHandler = new Handler();
...
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
new Thread(new Runnable() {
@Override
public void run() {
...
try {
...
//方式一
runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText(response.toString());
}
});
//方式二
mHandler.post(new Runnable() {
@Override
public void run() {
mTextView.setText(response.toString());
}
});
//方式三,利用方式二
mTextView.post(new Runnable() {
@Override
public void run() {
mTextView.setText(response.toString());
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
以上三種常見方式,翻閱源碼,可以知道其內(nèi)部都是通過將 Runnable 實現(xiàn)類包裝給一個新建的 message 作 callback,然后發(fā)送給主線程的 handler,也就是走向一的方式(讀者可以親手去跟進證實一下)。
注意,此時的 handler 只需簡單的一個聲明創(chuàng)建,畢竟處理方式被受理的 message 指定了要走自帶的 callback 的 run() 方法,那自己執(zhí)行就是,不需要再重寫或添加什么處理方式。
走向二:你應(yīng)該寫得出來吧
public class HandlerActivity extends AppCompatActivity implements View.OnClickListener {
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 200:
mTextView.setText((String) msg.obj);
break;
}
return true;
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
...
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button:
new Thread(new Runnable() {
@Override
public void run() {
...
try {
...
Message message = Message.obtain();
message.what = 200;
message.obj = response.toString();
mHandler.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
break;
}
}
}
和原程序走向三的寫法相比,只有新建 mHandler 一處不同,就是并非實現(xiàn) handleMessage(Message msg),而是將一個實現(xiàn)了 handleMessage(Message msg) 方法的 Callback 實現(xiàn)類作為參數(shù)傳入到 Handler 的帶參構(gòu)造方法里。
到此,我們的異步消息處理 Handler 篇算是講完咯~~~~