为了账号安全,请及时绑定邮箱和手机立即绑定

IntentService源码解析

标签:
Android

相关文章链接:

Handler消息源码流程分析(含手写笔记)

HandlerThread线程间通信 源码解析

IntentService源码解析


上一篇我们分析了Android中的线程间通信HandlerThread的原理.HandlerThread充分的利用Handler的通信机制和消息队列。本篇将分析IntentService的作用和原理。

**警告:**本篇的源码可能过于枯燥和乏味,中间涉及到一次采坑,错误的分析。最后纠正回来了。我觉得此处是比较有意义的。读者对着源码的同时细读本篇可能更好一点。

目录

  • IntentService简单介绍

  • 源码分析

  • 续错误纠正


IntentService

IntentService继承自Service本质上就是一个服务。但它内部拥有一个完整的HandlerThread。可以这样说IntentService=Service+HandlerThread。

我们先来说下它的常见用法。将复杂耗时操作交由IntentService来处理,你可以通过Intent的方式启动它,然后实现onHandleIntent方法,在onHandleIntent的任何操作将属于内部HandlerThread的子线程。

代码如下:

继承一个IntentService

public class CustomIntentService extends IntentService { public CustomIntentService() {  super("CustomIntentService"); }    @Override
    protected void onHandleIntent(Intent intent) {        try {            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }        String msg = intent.getStringExtra("msg");        Log.d("IntentService", "耗时操作" + msg);
    }    @Override
    public void onDestroy() {        Log.d("IntentService", "onDestroy" );        super.onDestroy();
    }
}

启动它,并附带一个消息

 Intent intent = new Intent(MainActivity.this, CustomIntentService.class);
                intent.putExtra("msg","hello");
                startService(intent);

这里我们启动了CustomIntentService并附带了一个hello过去。此时onHandleIntent方法会接受到我们这个Intent,并模拟耗时后打印日志。 注意两点

  • 1.这里的Intent是间接性传递过去的,按通常的思路这个Intent是在主线程中,但是这里并不是主线程。后面我们会从源码上来分析。

  • 2.我们知道IntentService的特性会执行完任务后自动销毁。但有一种情况,如果我们快速调用两次startService会如何?下面是快速调用两次的日志。

07-20 21:10:57.490 5895-5977IntentService: 耗时操作hello
07-20 21:11:02.480 5895-5977IntentService: 耗时操作hello
07-20 21:11:02.480 5895-5895IntentService: onDestroy

显然,并不是说每次执行都会销毁掉,当第二条消息过来的时候它并没有销毁,而是做完后才销毁。但是这是为什么呢?先卖个关子,我们带着疑问去看源码吧。

源码分析:

注:此处源码删除了一些不影响阅读的注释和方法

public abstract class IntentService extends Service {    private volatile Looper mServiceLooper;    private volatile ServiceHandler mServiceHandler;    private String mName;    private boolean mRedelivery;    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);
        }
    }    @Override
    public void onCreate() {        super.onCreate();        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();
        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }    @Override
    public void onStart(@Nullable Intent intent, int startId) {        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }    protected abstract void onHandleIntent(@Nullable Intent intent);
}
  • 1.IntentService 内部总共有四个成员变量,我们只需要关注mServiceLoopermServiceHandler即可。

  • 2.首先我们看onCreate方法,它创建了一个HandlerThread并向ServiceHandler传递了一个Looper对象。

  • 3.ServiceHandlerhandleMessage内逻辑很简单,它调用了onHandleIntent这个虚拟方法(抽象方法)。并从中取出msg.obj。可以看到它就是一个Intent,接着调用了stopSelf方法携带了msg.arg1(starId),来终止服务。

  • 4.onStart方法会率先接受到我们启动服务的Intent对象,他将该对象最终使用mServiceHandler发送给HandlerThread内部的Looper,交由子线程来处理这个消息,所以我们需要重写onHandleIntent来实现自己的需求。 HandlerThread原理可参考:HandlerThread线程间通信 源码解析

自此流程就梳理完了,现在我们回到前面提到的问题,当快速两次启动IntentService时,他发生了什么。 1.由内部的 hander接受到第一条消息,在onHandleIntent里阻塞,立刻第二条消息进入。 2.第一条消息的StopSelf方法被调用。此时第二条消息还在处理中。 3.StopSelf方法携带了startId调用了。ActivityManagerstopServiceToken来停止服务,我们接着来看一下源码。

源码路径**/core/android/app/ActivityManagerNative.java**

   public boolean stopServiceToken(ComponentName className, IBinder token,            int startId) throws RemoteException {        Parcel data = Parcel.obtain();        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IActivityManager.descriptor);        ComponentName.writeToParcel(className, data);
        data.writeStrongBinder(token);
        data.writeInt(startId);
        mRemote.transact(STOP_SERVICE_TOKEN_TRANSACTION, data, reply, 0);
        reply.readException();        boolean res = reply.readInt() != 0;
        data.recycle();
        reply.recycle();        return res;
    }

可以看到入参里携带了ComponentName BinderStartId,前面没有讲到,这里补充一下,StartId 是每次启动服务时都会携带过来的一个标记,它用来表示该服务在终止以前被启动了多少次。而后stopServiceToken方法将startId和和Binder一并写入了Parcel对象内。很抱歉,分析到这里翻车了,我没法再根据调试跟进去,断点下了是一把红叉。如果有大神知道这里如何动态调这块,还请告诉我一声,感激不尽。(或者我弄错了这里根本不是这样调的。)

不过这里已经大致能说明通过Binder传递了消息 mRemote.transact(STOP_SERVICE_TOKEN_TRANSACTION, data, reply, 0);来暂停服务。mRemote就是ActivityManagerNative(错误)我们再顺着思路找到了onTransact方法里的case语句

    case STOP_SERVICE_TOKEN_TRANSACTION: {
            data.enforceInterface(IActivityManager.descriptor);            ComponentName className = ComponentName.readFromParcel(data);            IBinder token = data.readStrongBinder();            int startId = data.readInt();            boolean res = stopServiceToken(className, token, startId);
            reply.writeNoException();
            reply.writeInt(res ? 1 : 0);            return true;
        }

~~非常有意思的是我们可以发现stopServiceToken方法是一个递归调用。此时我更加懵了。没有找到return的点,这将会是死循环。~~是不是思路错误了。

但是通过上层日志看出来,当IntentService里有消息存在时它不会结束掉。一定是IntentService内部消息全部被消耗后才会结束。

今天先到这里,我们来日弄明白了底层如何实现的后再战。


续错误纠正

前面的问题我们今天找到答案了。实际上面讲到的mRemote并非ActivityManagerNative,而是代理对象。以至于我误认为他们在递归调用。这是不对的。mRemote实际是ActivityManagerProxy,他是一个本地代理对象,而经过transact调用实际运行的是远端的ActivityManagerService中,这里牵扯到了AMSActivityManager通信流程,我们后续再单独分析。先把IntentService给弄明白。 源码地址(需要翻墙):ActivityManagerService

我们发现在ActivityManagerService里调用的stopServiceToken调用了mServices.stopServiceTokenLocked方法。

  @Override
    public boolean stopServiceToken(ComponentName className, IBinder token,            int startId) {        synchronized(this) {            return mServices.stopServiceTokenLocked(className, token, startId);
        }
    }

这里的mServicesActiveServices,我们跟进去看。

 ServiceLookupResult res =retrieveServiceLocked(service, resolvedType, callingPackage,
                    callingPid, callingUid, userId, true, callerFg, false);ServiceRecord r = res.record; boolean stopServiceTokenLocked(ComponentName className, IBinder token,            int startId) {        if (r != null) {            if (startId >= 0) {                ServiceRecord.StartItem si = r.findDeliveredStart(startId, false);                if (si != null) {                    while (r.deliveredStarts.size() > 0) {                        ServiceRecord.StartItem cur = r.deliveredStarts.remove(0);
                        cur.removeUriPermissionsLocked();                        if (cur == si) {                            break;
                        }
                    }
                }                if (r.getLastStartId() != startId) {                    return false;
                }                if (r.deliveredStarts.size() > 0) {                    Slog.w(TAG, "stopServiceToken startId " + startId                            + " is last, but have " + r.deliveredStarts.size()                            + " remaining args");
                }
            }            synchronized (r.stats.getBatteryStats()) {
                r.stats.stopRunningLocked();
            }
            r.startRequested = false;            if (r.tracker != null) {
                r.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(),                        SystemClock.uptimeMillis());
            }
            r.callStart = false;            final long origId = Binder.clearCallingIdentity();
            bringDownServiceIfNeededLocked(r, false, false);            Binder.restoreCallingIdentity(origId);            return true;
        }        return false;
    }

我们可以看到最关键的if (r.getLastStartId() != startId)不是最后一个startId就直接return false。否则将执行r.stats.stopRunningLocked();来终止。自此这个问题终于真相大白了。

回过头了再理一遍。我粗略的画了一张图。顺序从上往下看。

IntentService的源码本身并不复杂,读者不要被我深究stopSelf方法牵扯到AMS里给绕晕了。AMS这块后续我会单独做文章分析,这一块特别重要。

如果本篇看得比较云雾头疼,可以先去熟悉下面两篇。 HandlerThread线程间通信 源码解析 Handler消息源码流程分析(含手写笔记)

下一篇,我们将分析ThreadPoolExecutor线程池。

本文参考:


如何下次找到我?

原文链接:http://www.apkbus.com/blog-889706-68420.html

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消