Android插件化-Service篇

一、说明

  Android插件化相关文章是根据包建强大佬的《Android插件开发指南》书籍学习而来,只是后续的代码部分均是根据Android P源码所实现。想要了解Activity插件化的实现方式可直接跳转到Activity篇,当然也建议从该篇文章开始阅读,这有助于更好理解Android中的插件化。

二、Service的使用

  在Android中Service主要是用于执行后台任务,比如应用在后台通过Service播放音乐或者进行文件下载,更或者说是启动一个前台Service以达到保活目的等等。那么具体来说我们怎么启动一个Service呢,在Android中则是提供了如下两种方式:

1、startService

  如果是启动Service则会执行Service中的onCreate以及onStartCommand两个方法,再次调用该方法则只会执行onStartCommand方法。其对应的停止Service则是方法stopService。除此之外通过该方式启动的Serive是属于其所在的进程,即在其他组件中我们也可以调用stopService方法停止该Service。

2、bindService

  如果是启动Service则会执行Service中的onCreate以及onBind方法,后续不论再调用多少次该方法,onCreate以及onBind都不会再执行。其所对应的停止Service是方法unBindService。除此之外通过该方法启动的Service是属于其对应的Context的,如果对应的Context被销毁那么该Service也会随之被destory;比如在某一个Activity中通过该方法启动Service,如果该Activity被销毁了,那么启动的Service也会随之被销毁。

三、ActivityThread层Service启动源码解析

  在ActivityManager层启动Service的源码和启动Activity的源码大同小异,因此这里不做过多的解析,后续实现ActivityManager的hook也是复用了Activity篇中的实现,只是在过滤方法的时候新增了上述启动和停止Service的相关方法而已。话不多说直接步入主题。

  由于Service的生命周期并没有Activity那么多,因此其源码实现部分也没有Activity那么复杂,再加上前面对Activity插件化的理解,理解Activity的源码简直不要太简单。

1、onCreate的调用

  在ActivityThread中对四大组件的生命周期方法调用,均是通过其内部类mH这个Handler进行分发的。而Service的生命周期源头onCreate方法则是命中了mH的CREATE_SERVICE(114)这个分支,最终调用到方法handleCreateService中。对应源码如下:

private void handleCreateService(CreateServiceData data) {
    .......
    //看到这里是不是很熟悉,没错这个方法的最终实现也是调用到了方法getPackageInfo中
    //在Activity篇中为了加载插件中的Activity,我们可是通过反射的方式调用到了getPackageInfo方法中以生成对应LoadedApk的缓存
    //这里其实也是一样
    LoadedApk packageInfo = getPackageInfoNoCheck(
            data.info.applicationInfo, data.compatInfo);
    Service service = null;
    try {
        //看到这里是不是更加熟悉了,没错加载Activity类的ClassLoader也是从这个LoadedApk中获取的
        //这里当然也是一样的,所以在后续Service插件化的实现中我们可以直接复用实现Activity插件中的相关代码
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        //这里的最终实现就是痛过对应的ClassLoader加载名字为data.info.name对应的Service了
        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);
        }
    }

    try {
        if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);

        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        context.setOuterContext(service);

        Application app = packageInfo.makeApplication(false, mInstrumentation);
        //初始化Service,并调用其onCreate方法
        service.attach(context, this, data.info.name, data.token, app,
                ActivityManager.getService());
        service.onCreate();
        //保存当前创建的Service对应,用于后续执行onStart、onBind、unBind以及onDestroy等方法
        mServices.put(data.token, service);
        try {
            ActivityManager.getService().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    } catch (Exception e) {
        if (!mInstrumentation.onException(service, e)) {
            throw new RuntimeException(
                "Unable to create service " + data.info.name
                + ": " + e.toString(), e);
        }
    }
}
复制代码

2、其他生命周期方法

  Service的其它相关生命期方法与ActivityThread.java源码中方法实现分别对应关系如下:

  onStartCommand:handleServiceArgs

  onBind:handleBindService

  onUnbind:handleUnbindService

  onDestroy:handleStopService

  看完源码实现之后你会发现,上述的几个生命周期方法所操作的Service实例对象都是在方法handleCreateService中所创建的对象。因此对于Service的插件化实现我们只需要拦截方法handleCreateService就足以。

三、Service插件化的实现

1、实现步骤

  有了上述源码的理解分析以及上一篇所实现Activity插件化的加持,对于Service插件化的实现那简直不要太简单。大致可以分为如下几个步骤:

  第一步复用Activity插件化中所实现的AMNHook以及ActivityThread中Handler的hook

  第二步就是在hook的AMN中对操作Service的相关方法startService、startService以及bindService三个方法进行拦截,并将要启动的Service替换成在宿主中所预埋的Service;

  第三步就是在所hook的Handler中对方法handleCreateService进行拦截了。

2、代码实现

  因为AMN以及对应Handler的hook在Activity篇中有做注解,所以这里就不做过多的赘述了, 我们直接跳到第二步的代码实现。

/**
 *
 * @param realIntent 上层应用所构造Intent对象
 * @param name 上层应用所调用的方法,没啥实际用处,只是为了打印
 */
private void hookServiceOperate(Intent realIntent, String name) {
    if (null != realIntent) {
        ComponentName pluginComponentName = realIntent.getComponent();
        if (null != pluginComponentName) {
            String pluginServiceName = pluginComponentName.getClassName();
            //获取要启动的插件Service所对应的宿主Service名字哦
            String hostServiceName = HostToPluginMapping.getHostService(pluginServiceName);
            if (!TextUtils.isEmpty(hostServiceName)) {
                Log.i(TAG, "current is hooking " + name);
                //将实际要启动的Service替换成宿主中Service以欺骗AMS
                ComponentName componentName = new ComponentName(pluginComponentName.getPackageName(), hostServiceName);
                realIntent.setComponent(componentName);
            }
        }
    }
}
复制代码

  看着是不是so easy,简单来说就是吧上层应用需要启动的插件Service替换成宿主中预埋的Service以欺骗AMS;

  最后就是在所hook的Handler中拦截方法handleCreateService了,对应的实现如下:

public void handleCreateService(Object object) {
    try {
        //首先获取从AMS传递过来的CreateServiceData对象中的ServiceInfo属性
        ServiceInfo serviceInfo = (ServiceInfo) RefInvoke.getFieldValue(RefInvoke.getField(object.getClass(), "info"), object);
        String hostServiceName = serviceInfo.name;
        String pluginServiceName = HostToPluginMapping.getPluginService(hostServiceName);
        if (TextUtils.isEmpty(pluginServiceName)) {
            Log.i(TAG, "not found host service,so no need replace");
            return;
        }
        String path = DePluginSP.getInstance(DePluginApplication.getContext()).getString(Constants.COPY_FILE_PATH, "");
        //接着就是复用Activity插件化实现中的生成LoadedApk对象,并将其中的ClassLoader替换成加载对应插件的ClassLoader了
        replaceClassloader(path);
        
        //接着就是将ServiceInfo中的ServiceName替换成对应插件的ServiceName了,用于后续加载插件对应的Service
        serviceInfo.name = pluginServiceName;
        serviceInfo.applicationInfo.packageName = mPathToPluginNameMap.get(path);
        Log.i(TAG, "replaced to plugin service success");
    } catch (Exception e) {
        Log.i(TAG, "handle create service failed");
    }
    ......
    //最后就是继续调用到ActivityThread中的handleCreateService方法生成对应的Service对象,并做初始化等动作
}
复制代码

3、mergeDex

  当然对于插件Service类的加载其实不用生成ClassLoader这么麻烦,我们可以将插件对应的dex文件通过mergeDex的方式添加到BaseDexClassLoader的pathList中,这样就能够直接通过当前应用默认的ClassLoader进行加载了,这也是Android中热修复实现的方式之一。对应的实现如下:

public static void mergeDex(ClassLoader classLoader, File apkFile, File optDexfile) {
    try {
        Object pathListObj = RefInvoke.getFieldValue(RefInvoke.getField(BaseDexClassLoader.class, "pathList"), classLoader);
        if (null == pathListObj) {
            Log.i(TAG, "get path list failed");
            return;
        }
        Field dexElementsField = RefInvoke.getField(pathListObj.getClass(), "dexElements");
        Object[] elements = (Object[]) RefInvoke.getFieldValue(dexElementsField, pathListObj);
        if (null == elements) {
            Log.i(TAG, "get elements failed");
            return;
        }
        int length = elements.length;
        Class<?> elementCls = elements.getClass().getComponentType();
        Object[] newElemets = (Object[]) Array.newInstance(elementCls, length + 1);
        
        //ClassLoader在遍历Dex的查找Class的时候是从前往后依次进行遍历的,只要找到了对应的Class就直接停止了
        //因此通过mergeDex的方式实现热修复就是将已经修复的Class对应的Dex插到Element数组到首位就好了
        Object elementObj = RefInvoke.createObject(elementCls, new Class[]{DexFile.class, File.class}, new Object[]{DexFile.loadDex(apkFile.getCanonicalPath(), optDexfile.getAbsolutePath(), 0), apkFile});
        newElemets[0] = elementObj;
        System.arraycopy(elements, 0, newElemets, 1, length);
        RefInvoke.setFieldValue(dexElementsField, pathListObj, newElemets);
        Log.i(TAG, "merge dex success");
    } catch (Exception e) {
        Log.e(TAG, "mergeDex failed " + e);
    }
}
复制代码

四、总结

  所以,在有了Activity插件化的实现之后,Service的插件化实现也就简单的两步就实现了。

  有查看源码的可直接跳转到 github,另外想要更详细学习的同学可阅读包建强大佬的《Android插件开发指南》书籍

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享