簡介
首先我們先來了解HandlerThread和IntentService是什麼,以及為什麼要將這兩者放在一起分析。
HandlerThread:
HandlerThread 其實是Handler + Thread + Looper的組合,它本質上是一個Thread,因為它繼承了Thread。相比普通的Thread,它不會堵塞,因為他內部通過Looper實現了訊息迴圈機制,保證了多個任務的序列執行。缺點是效率比較低,因為,序列執行比起並行執行,效率肯定會比較較低。
IntentService:
IntentService是繼承並處理非同步請求的一個類,其本質上是一個Service,因為他繼承了Service,所以開啟IntentService和普通的Service一致。但是他和普通的IntentService不同之處在於,他可以處理非同步任務,在任務處理完成之後會自動結束Service。另外我們可以啟動IntentService多次,而每一個耗時任務會已工作佇列的方式在IntentService的onHandleIntent回撥方法中執行,並且是序列執行的。
好了在瞭解HandlerThread和IntentService分別是什麼之後,我們來解決第二個問題,那就是為什麼我要將2者放在一起分析?其實IntentService的內部是通過HandlerThread和Handler來實現非同步操作的,當我們瞭解了HandlerThread的使用和原理之後,再去理解IntentService就會容易的多。好的,下面讓我們開始HandlerThread的原始碼之旅。
#HandlerThread的使用和原理
###HandlerThread的使用
這裡我們要實現一個每隔5s更新TextView中的值的一個demo,原始碼如下:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTv;
private Button mBtn;
HandlerThread mHandlerThread;
Handler mThreadHandler;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
mBtn.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
initThread();
}
});
}
private void initView() {
mTv = (TextView) findViewById(R.id.tv);
mBtn = (Button) findViewById(R.id.btn);
}
private void initThread() {
//建立一個HandlerThread並開啟執行緒
mHandlerThread = new HandlerThread("update-msg");
mHandlerThread.start();
//從mHandlerThread中得到Looper並建立Handler
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
@Override public void handleMessage(Message msg) {
Log.v(TAG, "currentThread===>" + Thread.currentThread() + " what====>" + msg.what);
try {
update();
} catch (Exception e) {
e.printStackTrace();
}
mThreadHandler.sendEmptyMessage(200);
}
};
mThreadHandler.sendEmptyMessage(200);
}
private void update() throws Exception {
Thread.sleep(3000);
runOnUiThread(new Runnable() {
@Override public void run() {
String result = "每隔3s更新一次:";
result += Math.random();
mTv.setText(result);
}
});
}
}
複製程式碼
輸出的日誌如下:
currentThread===>Thread[update-msg,5,main] what====>200
從日誌我們可以看出handleMessage執行在我們建立的HandlerThread(“update-msg”)之下。我們有理由懷疑這跟我們傳入的mHandlerThread.getLooper()有關。我們的mThreadHandler 是在UI執行緒中建立的,按理來說handleMessage應該執行在UI執行緒中才對。瞭解Handler原理的都知道,handleMessage方法是在Handler的dispatchMessage方法中被呼叫的且dispatchMessage方法沒有進行執行緒切換。所以執行緒切換應該發生在dispatchMessage被呼叫的地方,那dispatchMessage是在哪被呼叫的呢?我們發現Loop的loop()方法中呼叫了dispatchMessage()方法。(這裡我就不貼Handler和Loop相關的程式碼,感興趣的可以參考我以前的文章:Handler的原理)而且我們還發現了loop()方法的註釋如下:
意思是loop()方法執行在Loop被繫結的執行緒中。
那Loop又是在什麼時候被繫結的呢?
就是這2個方法對Loop進行了繫結。那這個sThreadLocal又是什麼鬼?它到底有什麼用?別急,我們去看下它建立的地方:
它其實就是一個ThreadLocal,關於ThreadLocal的原理,大家可以參考:ThreadLocal原始碼深入分析
在這裡我簡單的說下,其實ThreadLocal的作用,就是通過Thread中的threadLocals:ThreadLocalMap變數將我們通過ThreadLocal#set方法傳進來的資料跟Thread進行繫結,從而保證了訪問到的變數屬於當前執行緒,每個執行緒都儲存有一個變數副本,每個執行緒的變數都不同,而同一個執行緒在任何時候訪問這個本地變數的結果都是一致的。當此執行緒結束生命週期時,所有的執行緒本地例項都會被GC。ThreadLocal相當於提供了一種執行緒隔離,將變數與執行緒相繫結。ThreadLocal通常定義為private static型別。
通過上面的分析,我們已經知道了Handler#handleMessage方法會執行在Loop說繫結的執行緒上,驗證了我們一開始的猜想。這裡Loop是從我們建立的HandlerThread中得到的,而HandlerThread其實就是一個執行緒,所以Loop繫結在了新建立的HandlerThread上。但是我們並不滿足於此,我們得進一步看看HandlerThread和普通的Thread到底有什麼不一樣。
HandlerThread的原始碼解析
HandlerThread的程式碼量其實並不多,它繼承於Thread,主要的方法其實就是run方法
@Override
public void run() {
mTid = Process.myTid();
//建立Loop並繫結當前執行緒
Looper.prepare();
//關鍵程式碼
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
//設定執行緒優先順序
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
複製程式碼
短短的幾行程式碼確幾乎實現了我們想要的所有功能,我們來看關鍵程式碼run方法中的synchronized 程式碼塊其實對應於getLooper方法中的synchronized程式碼塊,這樣做的目的是為了確保,我們通過getLoop()方法得到的Loop物件一定是被初始化後的Loop。當Loop被初始化以後會呼叫抽象方法onLooperPrepared(),他一般被用於在開啟佇列迴圈之前做一些初始化的操作,然後執行任務佇列。
總結
HandlerThread的原理已經分析完了,我們來總結一下它的特點:
1.HandlerThread它就是一個執行緒,和開啟普通的執行緒得到操作一致
2.HandlerThread需要搭配Handler使用,單獨使用的意義不大
3.HandlerThread會將通過handleMessage傳遞進來的任務進行序列執行,這是由messageQueue的特性決定的,從而也說明了HandlerThread效率相比並行操作會比較低
IntentService的使用和原理
分析完HandlerThread之後我們來分析一下IntentService的使用和原理,老規矩我們先看怎麼使用。
IntentService的使用
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService");
Log.v(TAG,
"MyIntentService===>MyIntentService()" + " currentThread==>" + Thread.currentThread());
}
/**
* Creates an IntentService. Invoked by your subclass`s constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public MyIntentService(String name) {
super(name);
Log.v(TAG,
"MyIntentService===>MyIntentService(name)" + " currentThread==>" + Thread.currentThread());
}
@Override public void onCreate() {
super.onCreate();
Log.v(TAG, "MyIntentService===>onCreate" + " currentThread==>" + Thread.currentThread());
}
@Override protected void onHandleIntent(@Nullable Intent intent) {
Log.v(TAG, "MyIntentService===>onHandleIntent" + " currentThread==>" + Thread.currentThread());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
Log.v(TAG, "MyIntentService===>onStartCommand" + " currentThread==>" + Thread.currentThread());
return super.onStartCommand(intent, flags, startId);
}
}
複製程式碼
呼叫服務和我們普通的Service一致
輸出的日誌如下
MyIntentService===>MyIntentService() currentThread==>Thread[main,5,main]
MyIntentService===>onCreate currentThread==>Thread[main,5,main]
MyIntentService===>onStartCommand currentThread==>Thread[main,5,main]
MyIntentService===>onHandleIntent currentThread==>Thread[IntentService[MyIntentService],5,main]
從中我們可以看出onHandleIntent方法是執行在子執行緒中的,更有意思的是,當我們在onHandleIntent 方法中執行延遲操作時,列印的日誌如下描述:
1、當服務沒執行完時又點選了開啟服務的操作,此時,onStartCommand方法會立即執行,而onHandleIntent方法會在上一個任務執行完以後再去執行onHandleIntent方法。
2、當服務已經執行完被自動結束以後,再去呼叫service,輸出的日誌和第一次輸出的日誌一致。
可能我說的比較抽象,大家自取去操作一遍就會發現我所說的有意思的地方。從上面的日誌輸出,我們可以得出以下結論:
1、IntentService在任務執行完以後會自動結束
2、IntentService接收的任務是序列執行的,並且互不干擾
3、IntentService的生命週期和普通的Service一致,只不過多了一個onHandleIntent回撥方法,並且它是序列回撥的,等待上一個任務執行完以後才會再次被呼叫
但是為什麼會這樣呢?大家有沒有想過。當然,所有的答案都隱藏在原始碼裡,讓我們一起去揭開他神祕的面紗吧。
IntentService原始碼解析
首先我們先來看下IntentService的幾個成員變數,如下圖所示:
關於Loop和Handler我們都很熟悉了,前者是遍歷訊息佇列的訊息泵後者則是處理Handler傳送過來的訊息的。下面我來看下他們初始化得到地方。
Loop初始化
原來他們都是在Service的onCreate回撥方法中被初始化的。
通過上文HandlerThread的分析,我們知道ServiceHandler的handleMessage方法會執行在mServiceLooper繫結的指定執行緒上。這裡這也就驗證了我們上文日誌的輸出。
下面我們來解決另外一個問題,也就是IntentService的生命週期函式的執行情況。
請看下面的程式碼:
我們都知道當服務被啟動以後,再次呼叫服務的時候都會回撥onStartCommand方法,onStartCommand又呼叫了onStart方法,而onStart方法中只是通過Handler傳送一個非同步訊息,然後ServiceHandler的handleMessage收到訊息以後呼叫了onHandleIntent,這也就驗證了上文的日誌輸出。
下面我們來重點分析一下Service的stopSelf()方法,他有兩個過載方法,一個有參,一個無參,那他們之間有什麼不同呢?
我們還是通過原始碼來看一下吧。
可以看到無參方法只是簡單的呼叫了有參方法,並傳入了一個-1的引數。所以我們只有直接分析有參的方法就可以。
由於Android sdk並沒有開放ActivityManageProxy(我們知道ActivityManage在客戶端得到代理是ActivityManageProxy)的程式碼,所以我們只能通過查詢相關資料來解決我們的疑惑。
最終我在官網上得到的答案如下:
簡單來說就是stopSelf中的startId對應於onStartCommand中的startId,當stopSelf(startId)中的startId等於onStartCommand中的最後一個進來的startId的時候,就代表訊息佇列中沒有更多的訊息需要處理了,所以執行完當前的訊息以後,會去執行Service的stop操作。
總結
關於IntentService的分析到這就告一段落了,其實IntentService就是基於HandlerThread機制來實現的,它允許我們在onHandleIntent回撥方法中執行非同步操作。同時要注意他的生命週期回撥函式的差異。下面貼上官網上關於IntentService類的介紹,幫助大家理解。