看完這一篇,再也不怕面試官問到IntentService的原理
IntentService是什麼
在內部封裝了 Handler、消息隊列的一個Service子類,適合在後台執行一系列串列依次執行的耗時非同步任務,方便了我們的日常coding(普通的Service則是需要另外創建子執行緒和控制任務執行順序)
IntentService的缺點
-
IntentService,一次只可處理一個任務請求,不可並行,接受到的所有任務請求都會在同一個工作執行緒執行
-
IntentService is subject to all the background execution limits imposed with Android 8.0 (API level 26).
翻譯:IntentService受到Android 8.0(API級別26)施加的所有[後台執行限制]的約束。
IntentService的未來
!This class was deprecated in API level 30.
IntentService is subject to all the background execution limits imposed with Android 8.0 (API level 26).
Consider using WorkManager or JobIntentService, which uses jobs instead of services when running on Android 8.0 or higher.
官方在最新說明中,提到 IntentService 類將會在API Level 30,也即Android 11中,被廢棄掉。作為一個從API Level 3就加入的非同步工具,如今官方建議使用JetPack組件中的WorkManager或者JobIntentService類代替它。
IntentService怎麼用
IntentService的使用,一般都需要子類繼承IntentService,然後重寫onHandleIntent()內部邏輯
因為IntentService本質上還是一個Service,所以需要先在註冊清單中註冊上Service以及需要外部手動開啟。
<service
android:name = ".MyIntentService">
AndroidStudio可以通過File-new-Service(IntentService),創建IntentService,IDE會幫我們自動在註冊清單註冊這個IntentService,為我們的IntentService子類提供了模板實現方法,我們可以在上面省事地修改。
下面為了方便演示,我使用官方提供的IntentService程式碼模板進行修改和操作:(程式碼有點長有點渣,請見諒)
//MyIntentService.java
public class MyIntentService extends IntentService {
//用以區分 Intent 的Action名
private static final String ACTION_FOO = "action.FOO";
private static final String ACTION_BAZ = "action.BAZ";
//給Intent傳遞參數取參的常量值
private static final String EXTRA_PARAM1 = "extra.PARAM1";
private static final String EXTRA_PARAM2 = "extra.PARAM2";
/**
* IntentService構造方法:傳入的參數name是作為內部的工作執行緒名的組成部分
*/
public MyIntentService() {
super("MyIntentService");
Log.i("MyIntentService" , "===created===");
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
if(intent != null){
Log.i("MyIntentService","Action "+intent.getAction()+" startId: "+startId);
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i("MyIntentService","===onDestroyed===");
}
/**
* 提供給外界調用,啟動任務Foo的方法
* 如果IntentService已經在運行,任務將會進入任務(消息)隊列等待排隊
* @param context 調用者Context
* @param param1 任務參數1
* @param param2 任務參數2
*/
public static void startActionFoo(Context context, String param1, String param2) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_FOO);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2, param2);
context.startService(intent);
}
/**
* 提供給外界調用,啟動任務Baz的方法
* 如果IntentService已經在運行,任務將會進入任務(消息)隊列等待排隊
* @param context 調用者Context
* @param param1 任務參數1
* @param param2 任務參數2
*/
public static void startActionBaz(Context context, String param1, String param2) {
Intent intent = new Intent(context, MyIntentService.class);
intent.setAction(ACTION_BAZ);
intent.putExtra(EXTRA_PARAM1, param1);
intent.putExtra(EXTRA_PARAM2, param2);
context.startService(intent);
}
/**
* IntentService被啟動後,會回調此方法
* onHandleIntent內部根據收到的不同Intent執行不同的操作
* @param intent 任務意圖
*/
@Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (ACTION_FOO.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionFoo(param1, param2);
} else if (ACTION_BAZ.equals(action)) {
final String param1 = intent.getStringExtra(EXTRA_PARAM1);
final String param2 = intent.getStringExtra(EXTRA_PARAM2);
handleActionBaz(param1, param2);
}
Log.i("MyIntentService","Action "+action+" completed");
}
}
/**
* 會在後台的工作執行緒上執行(耗時)任務Foo
* @param param1 任務參數1
* @param param2 任務參數2
*/
private void handleActionFoo(String param1, String param2) {
Log.i("MyIntentService","handleActionFoo : "+ Thread.currentThread().getName() +
" " + param1 + " "+ param2 );
}
/**
* 會在後台的工作執行緒上執行(耗時)任務Baz
* @param param1 任務參數1
* @param param2 任務參數2
*/
private void handleActionBaz(String param1, String param2) {
Log.i("MyIntentService","handleActionBaz : "+ Thread.currentThread().getName() +
" " + param1 + " "+ param2 );
}
}
外部如何啟用IntentService
可以通過調用context.startService(new Intent(context, MyIntentService.class))
或者調用MyIntentService
的靜態方法startActionFoo
/startActionBaz
啟動,如果你仔細看其實這兩種方式本質上都是相同的程式碼邏輯。
你可能會問:我平時最常用的bindService()
哪去了?這個問題,請接著往下看。
DEMO運行結果
在Activity里,連續調用開啟了Intentservice,特別地前兩次是相同的Intent和參數
MyIntentService.startActionFoo(this,"DMingO's" ,"blog");
MyIntentService.startActionFoo(this,"DMingO's" ,"blog");
MyIntentService.startActionBaz(this,"DMingO's" ,"Github");
從運行結果可以看出:
- IntentService會按照先後順序給Action編號遞增的startId,從1開始。
- 每啟動一次IntentService,
onStartCommand()
,onHandleIntent()
就會被回調一次,但IntentService構造方法只會被調用一次 - IntentService主要的操作邏輯都在
onHandleIntent()
中 - 在主執行緒啟動的IntentService,而onHandleIntent的操作是在指定了執行緒名的工作執行緒上執行的
- IntentService在所有的任務完成後會自動執行銷毀回調onDestroyed,而不用我們手動停止
IntentService的使用場景,很適合需要在後台執行一系列串列執行的耗時任務,不會影響到UI執行緒,且任務全部完成後會自動銷毀。
下面開始探究IntentService這種可以依次執行任務,任務完畢即銷毀的背後原理,👇
IntentService原理探究
IntentService的源碼行數其實不多,結合源碼分析,先從構造函數入手:
private String mName;
public IntentService(String name) {
//傳遞給父類--Service類
super();
mName = name;
}
IntentService
本質上還是一個Service
的子類,通過super()
調用父類構造器,給工作執行緒名變數賦值後,接著會開始Service的生命周期,IntentService
重寫了生命周期的第一步 onCreate()
接著看看IntentService
的onCreate
中有什麼名堂:
@Override
public void onCreate() {
super.onCreate();
//創建了一個本地 HandlerThread 的變數,結合mName進行命名,目的是為了獲取它的Looper和消息隊列
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
//獲取到HandlerThread的Looper,利用這個Looper創建
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
再來好好看下這個IntentService內部的Handler子類——ServiceHandler類:
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
//全部任務都調用stopSelf過後才會回調onDestroy(),退出工作執行緒的Looper循環
public void onDestroy() {
mServiceLooper.quit();
}
可以看到:當ServiceHandler
收到了來自HandlerThread
的Looper傳遞過來的Message時,首先會將消息的obj屬性強制轉換為Inetnt
類型,調用抽象方法onHandleIntent
。
由於ServiceHandler
的Looper
是來自HandlerThread
這個工作執行緒的,Looper與Handler的消息處理是直接掛鉤的,所以handleMessage(msg)
——> onHandleIntent(intent)
均是在工作執行緒上完成的。
msg.arg1
的值其實是這個任務Message的startId
,在onStartCommand
方法中可以發現它對開啟IntentService的任務都用startId
標記了順序,在構建Message對象時就被賦值給了它的arg1屬性了。
onHandleIntent()
執行完畢,stopSelf()
會根據指定 startId 來停止當前的任務。而 Service 如果被啟動多次,自然會有多個 startId ,只有當所有任務都被停止之後,才會調用 onDestory()
進行銷毀。這就是為什麼start了IntentService多次後,任務全部執行完成之後,IntentService才會自動銷毀的原因。
接下來繼續分析,重點來了,這個Message對象msg究竟是什麼地方被構建的。
從onStartCommand
方法入手:
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
onStartCommand
首先將外部想要執行的Intent和startId傳遞給了onStart(intent, startId)
調用,先跟進去看看 onStart
方法有什麼名堂 :
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
看來onStart
方法就是實際上IntentService機制的關鍵之處了,它根據每個 Intent 創建Message對象,完成了Message
對象的屬性賦值,還利用了mServiceHandler
發送消息。同時也解釋了為什麼我們看到每啟動一次IntentService
,onHandleIntent
就會被回調執行一次。也由此可見,Handler在Android中真的是太太太重要了。
肯定有好奇的同學會問,IntentService也是Service,能不能用 bind的方式啟動它呢?emmm可以是可以,但是最好不要這麼做。
IntentService在設計時,應該也想到bind方式啟動與IntentService任務完成自動銷毀的特點不太符合。這點可以從源碼可見一斑,用bind方式啟動,onBind會直接返回 null :
/**
* Unless you provide binding for your service, you don't need to implement this
* method, because the default implementation returns null.
* @see android.app.Service#onBind
*/
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
源碼分析總結
我們通過逐漸深入抽絲剝繭的方式分析了IntentService的源碼,最後可以簡單總結這個內部封裝了Handler和消息隊列的IntentService的原理:
IntentService的機制核心是Handler和消息隊列,每次我們調用 startService(new Intent)
,其實就是給 IntentService 添加一個任務。在IntentService的內部,第一次啟動時首先會 構建IntentService對象,開始初始化工作:通過HandlerThread獲取到一個工作執行緒的Looper,用來構建它的核心Handler。然後每當有一個任務被添加進來,內部就會創建一個附帶著Intent的Message對象,使用IntentService 內部的Handler發送Message。Looper從消息隊列中循環地取出Message傳遞給這個Handler,Handler就會在工作執行緒上依次處理這些消息任務的Intent。