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甩挫,子線程碰都碰不得這個問題贴硫,讀者可以暫且這么理解:學(xué)習(xí)并發(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ā)處理不起來呀,圖樣啊~不過偎痛,這個子線程愿意學(xué)習(xí)先進技術(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)1ⅰ5虐!找源碼看一下
/**
* 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 篇算是講完咯~~~~