IntentService的踩坑与分析

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继承ServiceService继承ContextWrapperContextWrapper又继承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调用中发生了异常。这个packageInfoLoadApk类型,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方法中。

到此,所有的疑问都已经明晰了。

最后,还是要吐槽下这个构造方法的问题。让我们再一次去看了一遍源码~~

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值