IntentService是一个轻量级的执行异步任务的Service,它提供了一种任务队列消费的模式来处理任务,并支持以Intent
来传递数据,与此同时,他还会在任务结束后,停止自身,一般用来在Service
中执行耗时任务。
使用
我们先看下他怎么使用,看下类注释的说明:
* IntentService is a base class for {@link Service}s that handle asynchronous
* requests (expressed as {@link Intent}s) on demand. Clients send requests
* through {@link android.content.Context#startService(Intent)} calls; the
* service is started as needed, handles each Intent in turn using a worker
* thread, and stops itself when it runs out of work.
很简单,创建IntentService
的子类,并实现onHandleIntent
方法
public class IntentServiceImpl extends IntentService {
public IntentServiceImpl(String name) {
super(name);
}
@Override protected void onHandleIntent(@Nullable Intent intent) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然后在AndroidManifest.xml
中注册服务
<service android:name="com.github.frameworkaly.service.IntentServiceImpl" />
然后就可以按照启动普通服务的方式来启动这个服务了。
Intent intent = new Intent(this, IntentServiceImpl.class);
startService(intent);
似乎一切很完美。
启动,然后就崩溃了。
分析
java.lang.RuntimeException: Unable to instantiate service com.github.frameworkaly.service.IntentServiceImpl: java.lang.InstantiationException: java.lang.Class<com.github.frameworkaly.service.IntentServiceImpl> has no zero argument constructor
提示很明显,缺少无参数的构造方法。我们回头看下自定义的IntentServiceImpl
类,准备添加无参构造方法。
然而会有一个提示:
当然我们也看到了IDE的提示的解决办法,调用super()
方法。
但是在完全正常的编码过程后,应用框架层竟然拒绝这种调用方式。对于这种使用体验,其实比较差。我们来看看为什么出现这种情况。
我们先看下父类IntentService
的构造方法
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public IntentService(String name) {
super();
mName = name;
}
只有一个带一个参数的构造方法。所以在子类声明无参构造方法时,因为无法匹配父类的构造器,会产生警告。
我们来看下继承关系
IntentService
继承Service
,Service
继承ContextWrapper
,ContextWrapper
又继承Context
,
可以看到Service
的构造器也是无参的,调用了父类ContextWrapper
的带参构造器。
public Service() {
super(null);
}
由于Context
是个顶级类,没有限定构造函数。作为Context
的包装类,ContextWrapper
进行了扩展,增加了一个带参数构造方法。
在使用普通Service
时,因为Service
自身具备无参构造函数,所以子类可以不用额外的创建构造器。但是由于IntentService
具备一个带参构造器,在创建类文件时,会默认生成一个带参数的构造器。这时,就必须手动添加一个无参的构造器,并且调用父类构造方法。这在IntentService
的构造方法中也有说明:
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public IntentService(String name) {
super();
mName = name;
}
这也就解释了为什么在继承IntentService
时,没有默认创建无参构造器了。为了消除警告或者不抛出异常。其实可以在IntentService
中再添加一个无参构造方法。
我们从源码看看这种方式的可行性。
从崩溃栈可以知道,异常是在ActivityThread.handleCreateService
中抛出的。看下代码块
......省略部分代码
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}
可看到是在packageInfo.getAppFactory().instantiateService
调用中发生了异常。这个packageInfo
是LoadApk
类型,getAppFactory()
返回的是一个AppComponentFactory
类型的对象(源码基于api 28),最终会调用到里面的instantiateServiceCompat
方法:
public @NonNull Service instantiateServiceCompat(@NonNull ClassLoader cl,
@NonNull String className, @Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
return (Service) cl.loadClass(className).getDeclaredConstructor().newInstance();
} catch (InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException("Couldn't call constructor", e);
}
}
可以看到最后是反射来创建Service
实例:
(Service) cl.loadClass(className).getDeclaredConstructor().newInstance()
在这里是尝试获取无参的构造方法。很显然,我们自定义的IntentServiceImpl
是没有无参构造方法的。所以也就是抛出了异常。
消息队列问题
刚开始说IntentService
是通过消息队列来实现的。我们来看下源码:
IntentService
的构造器调用了父类的构造器。我们知道服务的创建会是有应用程序和AMS通信最终完成的,并调用对应的生命周期方法。所以,我们接着看下IntentService
的生命周期方法。。
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
看到了我们熟悉的HandlerThread
了,IntentServiceImpl
在创建后,内部会创建一个线程并启动,同时还通过该线程的Looper
创建了Handler
。这就构造了一个完整的消息队列。
我们看下这个Handler是怎么处理消息的。
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);
}
}
调用了onHandleIntent
方法并停止自身。这个时候,就会调用我们之前实现的onHandleIntent
方法了,并且自身会向AMS发出停止的消息(需要告诉SystemServer
来进行一些注销操作)。
我们最后看下,是在哪里发送消息的。
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
是在onStart
方法中。
到此,所有的疑问都已经明晰了。
最后,还是要吐槽下这个构造方法的问题。让我们再一次去看了一遍源码~~