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。