看完这一篇,再也不怕被问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);
}
如果还不了解 HandlerThread 是什么,可以瞧瞧👉看完这篇。再也不怕被问 HandlerThread 的原理
再来好好看下这个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。