DroidPlugin源码分析安装和卸载

插件其实是Apk安装包,如果要使用必须先要安装和解析,以便知道插件Apk的相关信息。而从Demo中我们知道插件的安装和卸载是通过调用PluginManager的installPackage()和deletePackage()来实现的。就先从PluginManager.installPackage()开始分析插件Apk的安装过程。
第一步:PluginManager. getInstance().installPackage(apkPath,flag);
此函数中只是调用了mPluginManager.installPackage(filepath, flags);
PluginManager中mPluginManager代码如下:

    public void onServiceConnected(final ComponentName componentName, final IBinder iBinder) {
        mPluginManager = IPluginManager.Stub.asInterface(iBinder);
    …
}

mPluginManager:是一个PluginManagerService中IPluginManagerImpl的代理对象。
第二步: IPluginManagerImpl.installPackage(String filepath, int flags)
真正安装插件就是在这个函数中处理的,这个函数通过flags 判断分为替换安装和第一次安装,以及异常处理,三个部分。替换安装和第一次安装过程基本类似,异常处理也比较简单,所以接下来我就主要分析第一次安装的过程。

    public int installPackage(String filepath, int flags) throws RemoteException {
        String apkfile = null;
        try {//A 中解释如下
            PackageManager pm = mContext.getPackageManager();
            PackageInfo info = pm.getPackageArchiveInfo(filepath, 0);
            if (info == null) {
                return PackageManagerCompat.INSTALL_FAILED_INVALID_APK;
            }
            apkfile = PluginDirHelper.getPluginApkFile(mContext, info.packageName);
            if ((flags & PackageManagerCompat.INSTALL_REPLACE_EXISTING) != 0) {
            //替换安装过程..
} else {
                if (mPluginCache.containsKey(info.packageName)) {//B 中解释如下
                    return PackageManagerCompat.INSTALL_FAILED_ALREADY_EXISTS;
                } else {
                    forceStopPackage(info.packageName); //C 中解释如下
                    new File(apkfile).delete();
                    Utils.copyFile(filepath, apkfile);
                    PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile)); //D 中解释如下
                    parser.collectCertificates(0);
                    PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
                    if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) {
                        for (String requestedPermission : pkgInfo.requestedPermissions) {
                            boolean b = false;
                            try {
                                b = pm.getPermissionInfo(requestedPermission, 0) != null;
                            } catch (NameNotFoundException e) {
                            }
                            if (!mHostRequestedPermission.contains(requestedPermission) && b) {
                                Log.e(TAG, "No Permission %s", requestedPermission);
                                new File(apkfile).delete();
                                return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION;
                            }
                        }
                    }
                    saveSignatures(pkgInfo); //D 中解释如下
                    copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0));
                    dexOpt(mContext, apkfile, parser);
                    mPluginCache.put(parser.getPackageName(), parser);
                    mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
                    sendInstalledBroadcast(info.packageName);
                    return PackageManagerCompat.INSTALL_SUCCEEDED;
                }
            }
        } catch (Exception e) {
            if (apkfile != null) {
                new File(apkfile).delete();
            }
            handleException(e);
            return PackageManagerCompat.INSTALL_FAILED_INTERNAL_ERROR;
        }
}

mPluginCache:Map类型,以包名为key PluginPackageParser为Value。他保存了已经安装的插件。

PluginClassLoader: 继承自DexClassLoader:
关于类加载器的解释:Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校检、转换解析和初始化的,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。(更加详细的介绍大家可以从《深入了解Java虚拟机》中去了解,这里主要介绍插件加载流程)
在说DexClassLoader 先说Android系统另外一个类加载器:
PathClassLoader:Android官方文档的介绍:
Provides a simple ClassLoader implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).
一个简单的类加载器主要用于操作系统本地目录和文件列表,特别强调不能尝试加载网络类。Android系统用这个类主要用于加载系统应用和安装的而应用。
怎么理解官方文段这段话呢,先看构造函数:
PathClassLoader(String dexPath, String libraryPath, ClassLoader parent)
PathClassLoader构造函数是没有指定optimizedDirectory存放Dex优化文件路径的,通过了解系统源码我们发现如下:具体分析可查看罗哥的Android应用程序安装过程源代码分析:这篇文章详细了解。
Android的安装的应用优化dex文件后的目录都是固定存放在/data/dalvik-cache目录下面的。
因此PathClassLoader只能加载系统和安装的应用,不能加载从网络下载的Class或者未安装到系统的APP,而我们的插件,是没有安装到系统的,所以我们插件中的所有java类是不能通过PathClassLoader来加载和使用的。

DexClassLoader:官方文档的介绍:
A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.
This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:
File dexOutputDir = context.getDir(“dex”, 0);

Do not cache optimized classes on external storage. External storage does not provide access controls necessary to protect your application from code injection attacks.
这个加载器可以加载包含classes.dex实体的jar和Apk文件,能够用于执行没有安装的Android应用,只需要指定优化类的路径,另外这个路径不能是外部存储目录,以保护程序遭到注入攻击。
到这里大家应该明白为啥我们的插件Apk需要通过PluginClassLoader来加载了。
不多说直接看看DexClassLoader构造函数吧:
DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
dexPath: 插件Apk的路径
optimizedDirectory: 优化后Dex文件存放目录
libraryPath:应用程序应用的库文件存放目录
parent: 父类加载器

简单理解了类加载器和Android系统提供的两个类加载器DexClassLoader和PatchClassLoader,以及插件Apk为什么要用DexClassLoader在加载插件Apk之后,继续分析函数的实现。
A 获取PackageManager调用系统接口,判断是否可以正常解析插件Apk文件,如果不能正常解析 那么返回的PackageInfo为空,那么直接返回安装失败的Code。
B 以调用系统解析出来的插件报名从mPluginCache查看,是否已经被安装,如果已经安装,直接返回已经安装的Code,结束插件的安装。
C 调用forceStopPackage()停止要安装插件的进程,这个函数其实是通过ActivityManagerService获取当前系统中正在运行的进程,然后在进程列表中查找插件包名是否已经运行在进程中,如果是这kill掉此进程。
删除/data/data/宿主进程报名/plugin/plugin包名/apk/base-1.apk文件,将要安装的插件apk复制到刚刚删除的Apk文件中。
D 创建PluginPackageParser 类对象parser完成插件Apk文件的解析工作。这个过程稍后分析,先继续分析完这个函数。
通过parser获取搜集插件Apk签名信息和需要的权限,并检查插件Apk所需要的权限是否已经在宿主进程权限声明,如果没有则结束插件安装,提示安装失败,并删除相关之前复制的插件Apk文件。(也就是说插件Apk文件,要声明的权限必须要在宿主进程中预先声明,才行。)
解析成功之后,保存插件Apk的签名。
E 将插件Apk文件中的Lib目录下的so包保存到/data/data/宿主进程报名/plugin/plugin包名/lib文件中。
调用dexOpt()通过PluginClassLoader解压Apk保存Apk dex文件和library文件到/data/data/宿主进程报名/plugin/plugin包名/lib和/data/data/宿主进程报名/plugin/plugin包名/dalvik-cache目录下面。
以插件包名为key,在D中创建PluginPackageParser 类对象parser为Value 保存在mPluginCache中。
调用mActivityManagerService.onPkgInstalle这个函数是空实现,mActivityManagerService是MyActivityManagerService类的实例,主要负责插件进程管理的,后面的文章会详细说明他是如何对加载插件进程进行管理的。
最后发送插件安装成功的广播,插件安装完成。
第三步: 前面说过创建PluginPackageParser类的对象parser对插件Apk进行解析。
PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile));
在分析PluginPackageParser构造函数之前先了解一下几个变量:
mHostContext: 保存宿主进程Context
mParser: PackageParser(插件自定义)类实例,他是一个兼容系统各个版本的PackageParser,在他内部包含系统定义的PackageParser(系统定义)实例。而这个系统定义的PackageParser主要工作就是解析AndroidManifest文件中的Activity,Service,BroadcastReceiver,ContentProvider,申明权限等等。
mActivityObjCache,mServiceObjCache,mProviderObjCache,mReceiversObjCache,mInstrumentationObjCache,mPermissionsObjCache,mPermissionGroupObjCache,mRequestedPermissionsCache: 这几个变量主要保存解析插件Apk后Package对象内部对应的Activity,Service, Provider,Receiver等对应的数据。

mActivityIntentFilterCache,mServiceIntentFilterCache,mProviderIntentFilterCache,mReceiverIntentFilterCache: 这几个变量主要保存四大组件ComponentName对应的IntentFilter。

mActivityInfoCache,mServiceInfoCache,mProviderInfoCache,mReceiversInfoCache,mInstrumentationInfoCache,mPermissionGroupInfoCache,mPermissionsInfoCache:这几个变量是以ComponentName为key以ActivityInfo,ServiceInfo,ProviderInfo,InstrumentationInfo,PermissionGroupInfo,PermissionInfo对象为Value保存四大组件的相关信息。
之所以要保存这些信息,其实是效仿PackageManagerService,PackageManagerService不仅承担安装解析AndroidManifest还保存了文件中定义四大组件等相关的信息,并提供了这些信息的查询,而我们的插件并没有安装到系统中,是无法通过PackageManagerService中查到的,这个时候我们需要Hook PackageManagerService这样需要查询或者获取插件AndroidManifest文件中相关信息是,能方便的查询。
mPluginFile: 插件Apk文件路径。
mPackageName:解析后获得插件Apk的包名。
mHostPackageInfo: 宿主进程的PackageInfo实例。
了解这些以后,接下来分析解析过程就简单了PluginPackageParser构造函数部分代码如下:

 public PluginPackageParser(Context hostContext, File pluginFile) throws Exception {
        mHostContext = hostContext;
        mPluginFile = pluginFile;
        mParser = PackageParser.newPluginParser(hostContext);
        mParser.parsePackage(pluginFile, 0);
        mPackageName = mParser.getPackageName();
        mHostPackageInfo = mHostContext.getPackageManager().getPackageInfo(mHostContext.getPackageName(), 0);
//获取保存Activity
        List datas = mParser.getActivities();
        for (Object data : datas) {
            ComponentName componentName = new ComponentName(mPackageName, mParser.readNameFromComponent(data));
            synchronized (mActivityObjCache) {
                mActivityObjCache.put(componentName, data);
            }
            synchronized (mActivityInfoCache) {
                ActivityInfo value = mParser.generateActivityInfo(data, 0);
                fixApplicationInfo(value.applicationInfo);
                if (TextUtils.isEmpty(value.processName)) {
                    value.processName = value.packageName;
                }
                mActivityInfoCache.put(componentName, value);
            }

            List<IntentFilter> filters = mParser.readIntentFilterFromComponent(data);
            synchronized (mActivityIntentFilterCache) {
                mActivityIntentFilterCache.remove(componentName);
                mActivityIntentFilterCache.put(componentName, new ArrayList<IntentFilter>(filters));
            }
        }
//获取保存Service
//获取保存ContentProvide
//获取保存 Receiver
//获取保存Instrumentation
//获取保存Permissions
//获取保存PermissionGroups
//获取保存PermissionGroups
}

构造函数中先保存宿主进程Context,插件Apk路径,根据当前SDK版本获取与之对应的PackageParser实例mParser,然后通过mParser.parsePackage()解析插件Apk AndroidMainfest文件,获取相关信息并保存。
在mParser.parsePackage()函数内部,只是调用系统的PackageParser对象的parsePackage()函数,来获取插件Apk的Package实例。
具体如何解析可以查看系统源码,也比较简单,对应AndroidManifest文件的不同标签调用不同的函数来解析对应的数据。
到此,插件的安装过程就分析完了。
总结:插件的安装过程其实就是,
1 把插件Apk文件保存在宿主进程:/data/data/宿主进程报名/plugin/plugin包名/apk/base-1.apk下面。
2 通过PluginPackageParser解析插件Apk AndroidManifest文件,保存插件Apk 四大组件以及权限等信息,来方便查询。
3 PluginClassLoader保存优化后的Dex文件,加载插件Apk的类。

接下来继续看插件Apk的卸载过程:
通过前面安装的分析,实际上插件Apk的卸载是通过调用IPluginManagerImpl的deletePackage来实现的。

    public int deletePackage(String packageName, int flags) {
        try {
            if (mPluginCache.containsKey(packageName)) {
                forceStopPackage(packageName);
                PluginPackageParser parser;
                synchronized (mPluginCache) {
                    parser = mPluginCache.remove(packageName);
                }
                Utils.deleteDir(PluginDirHelper.makePluginBaseDir(mContext, packageName));
                mActivityManagerService.onPkgDeleted(mPluginCache, parser, packageName);
                mSignatureCache.remove(packageName);
                sendUninstalledBroadcast(packageName);
                return PackageManagerCompat.DELETE_SUCCEEDED;
            }
        } catch (Exception e) {
            handleException(e);
        }
        return PackageManagerCompat.DELETE_FAILED_INTERNAL_ERROR;
    }

这个函数主要工作如下:
1 从mPluginCache中通过要卸载的插件Apk包名查看是否已经安装并缓存相关信息。如果在mPluginCache存在对应包的PluginPackageParser类实例,接下来就是停止包运行的所在进程,然后把包对应的PluginPackageParser从缓存中删除。
2 删除宿主进程安装包对应的目录(/data/data/宿主进程报名/plugin/plugin包名)中的文件,并移除插件包对应的签名。然后发送卸载成功广播。
到此插件Apk的安装和卸载过程已经完成。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值