美文网首页Android开发安卓开发博客Android开发经验谈
移动架构08-手写DroidPlugin插件化框架

移动架构08-手写DroidPlugin插件化框架

作者: 最爱的火 | 来源:发表于2018-07-26 12:59 被阅读7次

    移动架构08-手写DroidPlugin插件化框架

    一、插件化介绍

    插件化就是一种让插件(第三方的APP)运行在宿主(自己的APP)中的技术。

    插件本身是一个APP,安装以后就可以运行在Android系统中。使用插件化技术,不会把插件安装在Android系统中,而是安装在宿主中,由宿主管理所有的插件,相当于插件是宿主的一个模块。

    插件化的好处:

    1. 集成海量插件。插件是第三方开发的,我们只需要遵循一定的规范,把插件集成到宿主中,就可以把插件当成自己的模块使用。
    2. 安全。插件是由第三方开发的,如果出现崩溃,不会导致宿主APP崩溃。因为每个插件都是运行在独立的进程中。
    3. 减小APK体积。在宿主APP中,我们只为插件提供了入口,只需要很少的代码就可以集成插件。并且插件APK文件并不包含在宿主APK中,只有当使用插件时,才会下载插件APK文件,安装到宿主中。所以插件基本不会增加宿主APK的体积。

    插件化实现方案分为两种:插桩式和Hook式。

    1.插桩式

    典型代表:DynamicLoadApk

    优点:稳定;

    缺点:需要使用『that』而不是『this』,所有activity都需要继承自proxyAvtivity(proxyAvtivity负责管理所有activity的生命周期)。

    2.Hook式

    典型代表:DroidPlugin

    特点:功能强大,使用简单。

    Hook的思想:拦截系统消息,替换执行内容,也就是偷梁换柱。

    Hook的难点:找到可以Hook的点。

    下面,我就仿照DroidPlugin框架,来实现一个小型插件化框架。

    二、手写插件化框架

    DroidPlugin框架的核心思想可以分两点:1.添加Hook点;2.进程管理。

    1.添加Hook点

    添加Hook点是为了让插件组件运行在宿主中。

    插件的组件是安装在宿主中,而启动插件组件还是系统来执行。系统会检查组件是否在清单文件注册,然后获取对应的calss文件创建组件。

    所以添加Hook点,就是要跳过清单文件检查(插件的组件不可能注册在宿主的清单文化中),并加载插件的class文件。

    下面,我们从Activity的启动流程来看,怎么添加Hook点。

    1.1Activity的启动流程

    我们先来看下系统是怎么启动一个Activity的?

    第一步,我们调用Context.startActivity();

    第二步,ActivityManager调用startActivity();

    第三步,PMS验证Acitvity是否在清单文件中注册,然后发送消息给ActivityThread。

    第四步,ActivityThread的mH处理PMS发送的消息,如果消息为100,就调用luanchActivity();

    第五步,ActivityThread验证ApplicaitonInfo的包名Pname1,如果与上一次获取的包名Pname2不一致,就调用PackageManager.getPackageInfo()获取包名Pname3。如果Pname3不为null,就使用Pname3作为包名,如果Pname3为null,就使用Pname1作为包名。

    第六步,根据第五步获取的包名,从ActivityThread.mPackages获取loadedApk对象,根据loadedAPK获取CalssLoader对象,使用CalssLoader对象创建Activity。

    第七步,调用Activity的生命周期方法。

    1.2找出Hook点

    首先,我们要跳过清单文件检查。思路是,在PMS验证Acitvity时,使用一个代理Activity(已经在清单文件中注册的Activity)来代替,验证后,再换回原来的Activity。

    步骤如下:

    1. Hook第二步,也就是Hook系统的ActivityManager。将Activity替换成代理Activity,并保存真实的Activity。
    2. Hook第四步,也就是Hook当前ActivityThread的mH。将代理Activity替换成真实的Activity。

    然后,我们要加载插件的class文件。思路是,先将插件APK生成loadedApk对象,然后插入ActivityThread.mPackages中,然后将ApplicaitonInfo的包名修改为插件的包名,然后获取插件的loadedApk对象来创建Activity。

    步骤如下:

    1. Hook第四步,也就是Hook当前ActivityThread的mH。将ApplicaitonInfo的包名修改为插件的包名
    2. Hook第五步,也就是Hook当前ActivityThread的sPackageManager。将PackageManager.getPackageInfo()的返回值改为null。
    3. Hook第六步,也就是Hook系统的ClassLoader对象。将插件APK的loadedApk对象插入ActivityThread.mPackages中。

    2.插件管理

    插件管理做两件事:1.进程管理;2.插件APK解析。

    2.1进程管理

    一个插件运行在宿主中,为了不想宿主和其它插件的运行,需要运行在单独的代理进程中。一个Activity有四种启动模式,如果是单例的启动模式,就需要多个相同启动模式的代理Activity,否则就不能同时打开多个单例的插件Activity。

    所以,进程管理就是选择合适的代理进程和代理组件。实现分为两步:占坑和进程维护。

    首先是占坑。占坑就是在清单文件中注册多个代理进程和代理Activity,并且区分进程。

    步骤如下:

    1. 在清单文件中注册多个进程的Activity,Standard的代理Activity只要1个,其他模式的代理Activity需要多个。
    2. 为了识别代理进程,进程名统一以PluginP开头。为了识别代理组件,使用统一的action和category。
    3. 在清单文件中,预注册多个权限。避免插件APP得不到权限,而导致崩溃。

    然后是进程维护。进程维护就是管理所有的代理进程和代理组件,为插件提供可用的代理组件。

    步骤如下:

    1. 通过action和category查询所有的代理进程和组件,保存在StaticProcessList中;
    2. 维护正在运行的进程和组件。通过代理进程名标识一个正在运行的进程,通过包名表示代理进程运行的插件,将所有正在运行的代理进程和组件,保存在RunningProcessList中。
    3. 选择代理组件。打开插件组件时,需要选择代理组件。选择代理组件有三个原则:1.相同插件的组件使用相同的进程;2.插件组件的启动模式要和代理组件的一致;3.单例的代理组件只能代理运行一个插件组件。

    2.2插件APK解析

    插件APK解析分为两步:1.安装;2.解析。

    系统使用PMS服务来安装、解析APK文件,我们模仿系统使用一个自定义的PMS来安装、解析APK文件。

    首先是安装。安装就是插件APK文件放到指定目录下,我们统一放到/data/user/0/宿主包名/Plugin/插件包名/apk/下。

    然后是解析。解析就是获取插件APK的包信息和注册的组件信息等。使用自定义的PackageParser来解析,需要做版本兼容。

    下面,就说一下怎么具体实现。

    三、添加Hook点

    Hook点有4个:

    1. HookActivityManager:Hook系统的ActivityManager;
    2. HookMH:Hook当前ActivityThread的mH;
    3. HookPackageManager:Hook当前ActivityThread的sPackageManager;
    4. HookClassLoader:Hook系统的ClassLoader对象

    Hook就是拦截系统消息,替换执行内容。所以我们把拦截和替换分开来说。

    1.HookActivityManager

    首先,Hook系统的ActivityManager,拦截它的startActivity(),将跳转目标设为代理组件,从而跳过PMS检查。
    步骤如下:

    1. 通过反射获取系统的ActivityManagerNative的gDefault属性;
    2. 通过反射gDefault,获取IactivityManager属性
    3. 使用动态代理的IactivityManager对象,替换gDefault的IactivityManager属性
    try {
        /**
         * 通过反射获取系统的ActivityManagerNative的gDefault属性
         */
        Class<?> ActivityManagerNativecls = Class.forName("android.app.ActivityManagerNative");
        Field gDefault = ActivityManagerNativecls.getDeclaredField("gDefault");
        gDefault.setAccessible(true);
        //得到ActivityManagerNative的gDefault属性
        Object defaltValue = gDefault.get(null);
    
        //mInstance对象
        Class<?> SingletonClass = Class.forName("android.util.Singleton");
        Field mInstance = SingletonClass.getDeclaredField("mInstance");
        //还原 IactivityManager对象  系统对象
        mInstance.setAccessible(true);
        Object iActivityManagerObject = mInstance.get(defaltValue);
    
        //保存系统对象
        setRealObj(iActivityManagerObject);
    
        Class<?> IActivityManagerIntercept = Class.forName("android.app.IActivityManager");
        //动态代理iActivityManagerObject,对其进行扩展,增加意图替换的逻辑
        Object oldIactivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader()
                , new Class[]{IActivityManagerIntercept}
                , this);
    
        //使用动态代理的IactivityManager对象,替换gDefault的IactivityManager属性
        mInstance.set(defaltValue, oldIactivityManager);
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    然后,使用动态代理,来修改startActivity的参数,即修改Intent的ComponentName。

    Intent intent = null;
    //从startActivity方法的参数中查找Intent参数的索引
    int index = findFirstIntentIndexInArgs(args);
    if (args != null && args.length > 1 && index >= 0) {
        intent = (Intent) args[index];
    }
    
    Intent newIntent = new Intent();
    //修改跳转目标为代理组件,用于跳过PMS检查
    ComponentName componentName = selectProxyActivity(intent);
    newIntent.setComponent(componentName);
    //保存真实的意图
    newIntent.putExtra(Env.EXTRA_TARGET_INTENT, intent);
    args[index] = newIntent;
    

    2.HookMH

    首先,Hook当前ActivityThread的mH,用来修改luanchActiity消息的回调,因为之前将跳转目标设为代理组件,所以现在需要将跳转目标还原。

    注意:开启新进程打开插件时,需要对新进程的ActivityThread的mH进行Hook。

    try {
        Class<?> forName = Class.forName("android.app.ActivityThread");
        Field currentActivityThreadField = forName.getDeclaredField("sCurrentActivityThread");
        currentActivityThreadField.setAccessible(true);
        //获取系统的ActivityTread的sCurrentActivityThread属性
        Object activityThreadObj = currentActivityThreadField.get(null);
    
        Field handlerField = forName.getDeclaredField("mH");
        handlerField.setAccessible(true);
        //获取sCurrentActivityThread的mH对象
        Handler mH = (Handler) handlerField.get(activityThreadObj);
    
        Field callbackField = Handler.class.getDeclaredField("mCallback");
        callbackField.setAccessible(true);
        //使用自定义的mCallback对象替换mH的mCallback属性
        callbackField.set(mH, new HandlerMH(mH));
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    然后,使用自定义的Callback来替换ActivityThread的mH的Callback,用来修改luanchActiity消息的回调。开启新进程打开插件时,即时修改了applicationInfo.packageName,系统也会从宿主APK中查找组件,这样就会导致找不到插件的组件而崩溃。为了避免崩溃,设定第一次进行Hook时,即新建进程打开插件时,直接打开代理组件。

    try {
        Field intentField = obj.getClass().getDeclaredField("intent");
        intentField.setAccessible(true);
        //代理意图
        Intent proxyIntent = (Intent) intentField.get(obj);
        //真实意图
        Intent oldIntent = proxyIntent.getParcelableExtra(Env.EXTRA_TARGET_INTENT);
    
        //如果是第一次拦截,就退出
        if (times++ <= 1) {
            return;
        }
    
        //开启代理组件的进程时,初始自定义AMS中的代理进程的信息
        onOpenPluginProcess(proxyIntent, oldIntent);
    
        if (oldIntent != null) {
            //还原真实的意图
            proxyIntent.setComponent(oldIntent.getComponent());
            /**
             * 获取activityInfo对象
             */
            Field activityInfoField = obj.getClass().getDeclaredField("activityInfo");
            activityInfoField.setAccessible(true);
            ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(obj);
            /**
             * 修改activityInfo的包名。如果是宿主的Activity,则不需要修改包名;如果是插件的Activity,就需要修改为插件的包名。
             * 因为使用loadedApk插入的方式加载插件的类时,会生成新的loadedApk对象,这个时候就需要根据插件的包名,从ActivityThrea中查找插件的loadedApk对象。
             */
            activityInfo.applicationInfo.packageName = proxyIntent.getComponent().getPackageName();
    
            //加载插件APK的loadedAPK。因为插件APK没有安装到系统中,是由自定义的PMS管理的,所以需要通把插件APK的loadedAPK对象插入到宿主中,即把插件的类插入到宿主中。
            PluginManager.preLoadApk(oldIntent.getComponent());
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    3.HookPackageManager

    首先,Hook当前ActivityThread的sPackageManager,修改它的getPackageInfo()。系统调用handleLuachActivity()时,会通过IPackageManage.getPackageInfo()检查Activity的包名。如果IPackageManage.getPackageInfo()返回的包名为null,则使用activityInfo.applicationInfo.packageName为Activitry的包名;如果IPackageManage.getPackageInfo()返回的包名不为null,则与activityInfo.applicationInfo.packageName比较,如果不同,就会报错。

    现在我们需要设置Activity的包名为插件的包名,就需要拦截IPackageManage.getPackageInfo(),让IPackageManage.getPackageInfo()返回的包名为null。

    注意:开启新进程打开插件时,需要对新进程的ActivityThread的mInstrumentation进行Hook。

    try {
        /**
         * 获取系统的ActivityThread对象
         */
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);
    
        /**
         * 获取ActivityThread的sPackageManager对象
         */
        Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
        sPackageManagerField.setAccessible(true);
        Object sPackageManager = sPackageManagerField.get(currentActivityThread);
    
        //保存系统对象
        setRealObj(sPackageManager);
    
        /**
         * 使用代理的IPackageManager对象,替换ActivityThread的sPackageManager对象
         */
        Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
        Object mPackageManager = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader()
                , new Class[]{iPackageManagerInterface}, this);
        sPackageManagerField.set(currentActivityThread, mPackageManager);
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    然后,使用动态代理,修改getPackageInfo().

    PackageInfo packageInfo = new PackageInfo();
    return packageInfo;
    

    4.HookClassLoader

    用来Hook系统的ClassLoader对象,由于不能直接修改ClassLoader对象,所以修改ClassLoader的上级对象loadedAPK,将插件APK的loadedPAK对象插入宿主的ActivityThread的mPackages中,然后从插件的loadedAPK中获取calssLoader,从而实现替换系统ClassLoader对象。因为插件APK没有安装到系统中,是由自定义的PMS管理的,所以需要通把插件APK的loadedAPK对象插入到宿主中,即把插件的类插入到宿主中。

    try {
        /**
         * 获取系统的activityThread对象
         */
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);
    
        /**
         * 获取ActivityThread的mPackages对象
         * ActivityThread的mPackages对象是用来保存loadedApk对象,加载插件类就是把插件的loadedApk对象插到mPackages中
         */
        Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
        mPackagesField.setAccessible(true);
        Map mPackages = (Map) mPackagesField.get(currentActivityThread);
    
        /**
         * 生成插件的loadedApk对象
         * 使用ActivityThread的getPackageInfoNoCheck方法生成loadedApk对象,需要两个参数ApplicationInfo和CompatibilityInfo
         */
        Class<?> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
        //得到getPackageInfoNoCheck方法
        Method getPackageInfoNoCheckMethod = activityThreadClass.getDeclaredMethod(
                "getPackageInfoNoCheck", ApplicationInfo.class, compatibilityInfoClass);
        //生成默认的CompatibilityInfo对象
        Field defaultCompatibilityInfoField = compatibilityInfoClass.getDeclaredField("DEFAULT_COMPATIBILITY_INFO");
        Object defaultCompatibilityInfo = defaultCompatibilityInfoField.get(null);
        //生成插件的ApplicationInfo对象
        ApplicationInfo applicationInfo = PluginManager.getInstance().getApplicationInfo(component, 0);
        //生成插件的loadedApk对象
        loadedAPK = getPackageInfoNoCheckMethod.invoke(currentActivityThread, applicationInfo, defaultCompatibilityInfo);
    
        /**
         * 设置插件的loadedApk的mClassLoader对象
         */
        String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(PluginManager.getContext(), component.getPackageName());
        String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(PluginManager.getContext(), component.getPackageName());
        ClassLoader classLoader = new MyClassLoader(applicationInfo.publicSourceDir, optimizedDirectory, libraryPath, PluginManager.getContext().getClassLoader());
        Field mClassLoaderField = loadedAPK.getClass().getDeclaredField("mClassLoader");
        mClassLoaderField.setAccessible(true);
        mClassLoaderField.set(loadedAPK, classLoader);
    
        /**
         * 把插件的loadedApk对象插入ActivityThread的mPackages中
         */
        WeakReference weakReference = new WeakReference(loadedAPK);
        mPackages.put(component.getPackageName(), weakReference);
        sPluginLoadedApkCache.put(component.getPackageName(), loadedAPK);
    } catch (Exception e) {
        e.printStackTrace();
    }
    

    生成loadedAPK对象用到的ApplicationInfo,是通过自定义的PMS生成的,我们在下面再讲。

    四、插件管理

    插件管理是运行在远程服务中,需要通过aidl进行通信。其实系统的PMS也是一个aidl接口,所以我们仿照系统来实现。

    1.进程管理

    首先是占坑,在清单文件中预注册多个代理进程和代理组件。

    ...
    <!--自定义PMS的远程服务,用来管理插件的安装。进程名以PluginP开头。-->
    <service
        android:name=".pm.MPackageManagerService"
        android:process=":PluginService" />
    
    
    <!--使用占坑的方式,加载插件APK的组件。即预注册多个代理组件,供插件使用。
    由于Activity有四种启动模式,所以需要注册多个进程多种模式的代理组件。
    设置代理组件的进程名时,统一以PluginP开头。
    代理组件,统一设置category为"gsw.toolpluggable.plugin",方便解析插件时判断是否是插件的组件。-->
    
    <!--第1个进程-->
    <activity
        android:name=".activity.ActivityMode$P01$Standard01"
        android:allowTaskReparenting="true"
        android:excludeFromRecents="true"
        android:exported="false"
        android:hardwareAccelerated="true"
    
        android:launchMode="standard"
        android:process=":PluginP01">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="gsw.toolpluggable.plugin" />
        </intent-filter>
        <meta-data
            android:name="com.morgoo.droidplugin.ACTIVITY_STUB_INDEX"
            android:value="1" />
    </activity>
    ...
    

    然后是进程维护。在PMS服务启动时,就查找清单文件中注册的所有的代理进程和组件。

    /**
     * 初始化items
     * 通过IntentFilter,从清单文件中查找代理组件的存档信息,并添加到items中。
     * 代理组件的action统一为Intent.ACTION_MAIN,Category统一为Env.CATEGORY_ACTIVITY_PROXY_STUB。
     *
     * @param context PMS服务的上下文
     */
    public void onCreate(Context context) {
        //根据代理组件的IntentFilter设置Intent
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Env.CATEGORY_ACTIVITY_PROXY_STUB);
        //设置宿主APK的包名
        intent.setPackage(context.getPackageName());
    
        /**
         * 从宿主的PackageManager中Activity和BroadcastReceiver的存档信息
         */
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> activities = pm.queryIntentActivities(intent, PackageManager.GET_META_DATA);
        for (ResolveInfo activity : activities) {
            addActivityInfo(activity.activityInfo);
        }
    
        /**
         * 从宿主的PackageManager件中Service的存档信息
         */
        List<ResolveInfo> services = pm.queryIntentServices(intent, 0);
        for (ResolveInfo service : services) {
            addServiceInfo(service.serviceInfo);
        }
        ...
    }
    

    然后,是选择代理组件。选择出可用的代理组件后,需要保存在mRunningProcessList中。

    public ActivityInfo selectStubActivityInfo(int callingPid, int callingUid, ActivityInfo targetInfo) {
        //获取插件Activity的同包Activity的运行进程,如果获取到了,就代表当前插件已经开启进程了
        String stubPlugin = mRunningProcessList.getStubProcessByTarget(targetInfo);
        //如果当前插件已经开启进程了,就选择可用的代理Activity
        if (stubPlugin != null) {
            //获取插件进程在清单文件中注册的所有代理Activity
            List<ActivityInfo> stunInfos = mStaticProcessList.getActivityInfoForProcessName(stubPlugin);
            for (ActivityInfo activityInfo : stunInfos) {
                //先找出与插件Activity的启动模式一致的代理Activity
                if (activityInfo.launchMode == targetInfo.launchMode) {
                    //如果启动模式是Standard,就直接返回该代理Activity
                    if (activityInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
                        mRunningProcessList.setTargetProcessName(activityInfo, targetInfo);
                        mRunningProcessList.addActivityInfo(callingPid, callingUid, activityInfo, targetInfo);
                        return activityInfo;
                        //如果启动模式是单例,就返回没有运行的代理Activity
                    } else if (!mRunningProcessList.isStubInfoUsed(activityInfo, targetInfo, stubPlugin)) {
                        mRunningProcessList.setTargetProcessName(activityInfo, targetInfo);
                        mRunningProcessList.addActivityInfo(callingPid, callingUid, activityInfo, targetInfo);
                        return activityInfo;
                    }
                }
            }
            return null;
        }
    
        /**
         * 如果当前插件没有开启进程,就需要新开进程
         */
        //获取清单文件中注册的所有代理进程名
        List<String> processNames = mStaticProcessList.getProcessNames();
        for (String stubProcessName : processNames) {
            //获取插件进程在清单文件中注册的所有代理Activity
            List<ActivityInfo> stubInfos = mStaticProcessList.getActivityInfoForProcessName(stubProcessName);
            //如果当前进程没有运行,就设置它的目标进程名和包名,并查找可用的代理ActivityInfo
            if (!mRunningProcessList.isProcessRunning(stubProcessName)) {
                for (ActivityInfo stubInfo : stubInfos) {
                    if (stubInfo.launchMode == targetInfo.launchMode) {
                        if (stubInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
                            mRunningProcessList.setTargetProcessName(stubInfo, targetInfo);
                            mRunningProcessList.addActivityInfo(callingPid, callingUid, stubInfo, targetInfo);
                            return stubInfo;
                        } else if (!mRunningProcessList.isStubInfoUsed(stubInfo, targetInfo, stubProcessName)) {
                            mRunningProcessList.setTargetProcessName(stubInfo, targetInfo);
                            mRunningProcessList.addActivityInfo(callingPid, callingUid, stubInfo, targetInfo);
                            return stubInfo;
                        }
                    }
                }
                //如果当前进程已经运行,但是并没有代理运行任何插件,就重新设置它的目标进程名和包名,并查找可用的代理ActivityInfo
            } else if (mRunningProcessList.isProcessRunning(stubProcessName) && mRunningProcessList.isPkgEmpty(stubProcessName)) {
                for (ActivityInfo stubInfo : stubInfos) {
                    if (stubInfo.launchMode == targetInfo.launchMode) {
                        if (stubInfo.launchMode == ActivityInfo.LAUNCH_MULTIPLE) {
                            mRunningProcessList.setTargetProcessName(stubInfo, targetInfo);
                            mRunningProcessList.addActivityInfo(callingPid, callingUid, stubInfo, targetInfo);
                            return stubInfo;
                        } else if (!mRunningProcessList.isStubInfoUsed(stubInfo, targetInfo, stubProcessName)) {
                            mRunningProcessList.setTargetProcessName(stubInfo, targetInfo);
                            mRunningProcessList.addActivityInfo(callingPid, callingUid, stubInfo, targetInfo);
                            return stubInfo;
                        }
                    }
                }
            }
        }
        return null;
    }
    

    2.插件APK解析

    首先是安装。安装就是安装在宿主的指定目录下,自定义的PMS服务每次启动时,从指定查找所有的APK文件,就是插件APK,交给自定义的PackageParser解析。

    然后是解析。使用自定义PackageParser,用来将插件APK文件转化为Pacjage对象。仿照系统的PackageParser实现,本质上是反射系统的PackageParser,实现自定义PackageParser的所有功能。 由于Android各个版本的PackageParser的现实不同,所以要做版本兼容。我们以API21为标准版本,其他版本基于API21做修改。

    解析其实是交给系统来做,我们要做的是通过反射来调用系统方法,实现PackageParser的所有方法。

    //生成插件APK的包信息
    public PackageInfo generatePackageInfo(
            int gids[], int flags, long firstInstallTime, long lastUpdateTime,
            HashSet<String> grantedPermissions) throws Exception {
        /*public static PackageInfo generatePackageInfo(PackageParser.Package p,
            int gids[], int flags, long firstInstallTime, long lastUpdateTime,
            HashSet<String> grantedPermissions, PackageUserState state, int userId) */
        try {
            Method method = MethodUtils.getAccessibleMethod(sPackageParserClass, "generatePackageInfo",
                    mPackage.getClass(),
                    int[].class, int.class, long.class, long.class, Set.class, sPackageUserStateClass, int.class);
            return (PackageInfo) method.invoke(null, mPackage, gids, flags, firstInstallTime, lastUpdateTime, grantedPermissions, mDefaultPackageUserState, mUserId);
        } catch (NoSuchMethodException e) {
            Log.i(TAG, "get generatePackageInfo 1 fail", e);
        }
        ...
     }
    

    最后

    代码地址:https://gitee.com/yanhuo2008/Common

    移动架构专题:https://www.jianshu.com/nb/25128604

    喜欢请点赞,谢谢!

    相关文章

      网友评论

        本文标题:移动架构08-手写DroidPlugin插件化框架

        本文链接:https://www.haomeiwen.com/subject/qyqbmftx.html