上篇中我們講解了線程,進程以及Thread和Runnable之間的區別,那么這一篇我們來講解下Android應用的消息處理機制,之后才能夠更深刻的了解為什么多線程能夠解決UI縣城阻塞的問題。
Android 的消息處理機制
說到Android消息處理機制有的人或許有些概念模糊,那么Handler、Looper、MessageQueue,大家應該比較面熟吧。
- UI線程
我們知道在Android應用啟動時,會默認啟動一個(主)UI線程,這個線程會關聯一個消息隊列所有的操作都會被封裝成消息交給主線程來處理。為了保證主線程不會主動退出,就要將抓取消息的操作放在一個死循環中,這樣我們的程序就不會主動退出并保持運行狀態。
那么和Handler 、 Looper 、Message有啥關系?其實Looper負責的就是創建一個MessageQueue,然后進入一個無限循環體不斷從該MessageQueue中讀取消息,而消息的創建者就是一個或多個Handler 。
上面雖然是原理但是并不是多好理解,我們呢打個比方:
男主:BOY
女主:Girl
當Boy和Girl 結婚后(APP啟動了),那么Boy就要開始干活了(APP開啟UI線程),這時候Boy就要進入瘋狂工作模式了 (無限循環-Looper),而我們的Girl(Handler的一個實例)會把各種類型的賬單記下來(將Message寫入MessageQueue),但凡Boy接收到(Looper獲取)賬單(Message)就進行處理,如果是月初Girl沒有給你霍霍,那么Boy就會自己攢錢了(阻塞Looper)等到月底賬單來臨再去處理。
舉例講完后我們來看看Handler、Looper、MessageQueue的概念。
- Handler:
簡單說Handler用于同一個進程的線程間通信,另外一個作用,就是能統一處理消息的回調。這樣一個Handler發出消息又確保消息處理也是自己來做,這樣的設計非常的贊。 - Looper:
無限循環不退出的線程,Looper的另外一部分工作就是在循環代碼中會不斷從消息隊列挨個拿出消息給主線程處理。 - MessageQueue
MessageQueue 存在的原因很簡單,就是同一線程在同一時間只能處理一個消息,同一線程代碼執行是不具有并發性,所以需要隊列來保存消息和安排每個消息的處理順序。
這里就不帶大家去看源代碼了。
下面我峨嵋你通過一個例子來看下消息處理機制是怎么解決網絡加載時阻塞UI線程問題的。
案例中我們讓下載類 sleep 7秒,并且下載前和下載后都要對UI中的Text進行修改。
MainActivity.class
/**
* Created by 泅渡者
* Created on 2017/10/27.
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private final Handler mHandler = new MyHandler(this);
public static TextView tv_download;
private Message message;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv_download = findViewById(R.id.tv_download);
tv_download.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Download download = new Download();
Thread thread = new Thread(download,"下載");
thread.start();
}
});
}
private static class MyHandler extends Handler {
private final WeakReference<Activity> mActivity;
public MyHandler(Activity activity) {
mActivity = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
if (mActivity.get() == null) {
return;
}
tv_download.setText("下載中。。。");
break;
case 2:
if (mActivity.get() == null) {
return;
}
tv_download.setText("下載完成");
break;
default:
return;
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
class Download implements Runnable{
@Override
public void run() {
try {
message= mHandler.obtainMessage();
message.what = 1;
mHandler.sendMessage(message);
Thread.sleep(7000);
message= mHandler.obtainMessage();
message.what = 2;
mHandler.sendMessage(message);
} catch (InterruptedException e) {
Log.e(TAG,e.toString());
}
}
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.bsoft.multithread.MainActivity">
<TextView
android:id="@+id/tv_download"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="下載"
/>
</RelativeLayout>
運行效果如下:
我們看下面代碼:
message= mHandler.obtainMessage();
message.what = 1;
mHandler.sendMessage(message);
Thread.sleep(7000);
message= mHandler.obtainMessage();
message.what = 2;
mHandler.sendMessage(message);
這里我們在子線程不能操作UI線程,這個大家都知道,我們說一下obtainMessage()。
- obtainmessage()是從消息池中拿來一個msg 不需要另開辟空間。
- new Message()需要重新申請,效率低。
- obtianmessage可以循環利用。
這里還有一個比較重要的話題,就是由Handler導致Activity 的內存泄露。
Handler內存泄漏解決辦法
Handler也是造成內存泄露的一個重要的源頭,主要Handler屬于TLS(Thread Local Storage)變量,生命周期和Activity是不一致的,Handler引用Activity會存在內存泄露。
我們一般用法是否是這樣的呢?
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case 1:
//TODO
break;
default:
return;
}
}
};
------------------------------------------------------------------------------
Message message = mHandler.obtainMessage();
message.what = 1;
mHandler.sendMessageDelayed(message,6000);
但是程序會提示
This Handler class should be static or leaks might occur (anonymous android.os.Handler)
意思:此處理程序類應該是靜態的或可能發生泄漏 (匿名 android.os.Handler)
是什么導致的呢 ?
生命周期
Handler 的生命周期與Activity 不一致,當Android應用啟動的時候,會先創建一個UI主線程的Looper對象,Looper實現了一個簡單的消息隊列,一個一個的處理里面的Message對象。主線程Looper對象在整個應用生命周期中存在,當在主線程中初始化Handler時,該Handler和Looper的消息隊列關聯(沒有關聯會報錯的)。發送到消息隊列的Message會引用發送該消息的Handler對象,這樣系統可以調用 Handler#handleMessage(Message) 來分發處理該消息。handler 引用 Activity 阻止了GC對Acivity的回收
在Java中,非靜態(匿名)內部類會默認隱性引用外部類對象。而靜態內部類不會引用外部類對象。如果外部類是Activity,則會引起Activity泄露 ,當Activity finish后,延時消息會繼續存在主線程消息隊列中1分鐘,然后處理消息。而該消息引用了Activity的Handler對象,然后這個Handler又引用了這個Activity。這些引用對象會保持到該消息被處理完,這樣就導致該Activity對象無法被回收,從而導致了上面說的 Activity泄露。如何避免?
使用顯形的引用:1.靜態內部類 2. 外部類
使用弱引用 : WeakReference
還要在程序銷毀時進行remove();
@Override
public void onDestroy() {
mHandler.removeCallbacksAndMessages(null);
}
上述案例就是應用弱引用,可能大家覺得寫的代碼比較多,不怕我們有辦法。我們按照圖的指示來創建自己的Live Templates:
OK 今天的就到這里。