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的安装和卸载过程已经完成。

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
分享截屏已经是很多游戏应用必备的功能了,找到了一个国内的插件,虽然用起来还行,但是,还是想吐槽下,跟老外的插件比,真的有差距啊有差距啊有差距啊,啊啊啊。 ShareSDK的官方网站:http://www.mob.com/,使用插件需要注册账号获得key,不过,至少现在是免费的。 我的Unity版本是5.3,xcode版本是7.2,ShareSDK版本是2.7.4 Unity sdk下载地址:https://github.com/MobClub/New-Unity-For-ShareSDK 新建一个简单的工
正常情况下启动一个Activity,首先需要在AndroidManifest文件中声明,其次需要把该应用安装到手机系统中。 而插件apk是没有正在安装到手机系统中的,也就按照正常的启动流程插件Activity是不能启动的。另外插件apk的类需要加载进来是需要指定ClassLoader。前面的文章也大概讲过,当启动一个插件Activity时,先是用预定义的代理Activity替换目标Activity(及插件Activity)去启动,当AMS处理完回调到应用空间时(及回到运行Activity的进程空间时)再用
从DroidPlugin的官方文档中我们知道。 2 在AndroidManifest.xml中使用插件的com.morgoo.droidplugin.PluginApplication: 或者在自定义的Application的onCreate()函数中,调用PluginHelper.getInstance().applicationOnCreate(getBaseContext()); 在Application的attachBaseContext()函数中,调用 PluginHelper.getInsta

字母雨的实现 - 2016-07-23 14:07:06

有段时间没写博文了,前段时间比较忙,这几天闲下来,想着写点东西,脑袋一下就闪过以前学习Android的时候见到的别人实现的黑客帝国的字母雨效果,当时对于小菜鸟的自己,那叫一个膜拜啊,时隔几年,自己实现一下,算是对以前的自己一个交代吧。 先看效果: 一、实现原理 在实现过程中,主要考虑整个界面由若干个字母组成的子母线条组成,这样的话把固定数量的字母封装成一个字母线条,而每个字母又封装成一个对象,这样的话,就形成了如下组成效果: 字母对象--》字母线条对象--》界面效果 每个字母都应该知道自己的位置坐标,自己

DroidPlugin源码分析Hook过程 - 2016-07-23 14:07:06

插件运行环境初始化过程中我们知道,Hook的初始化是在PluginHelper的initPlugin函数中通过调用PluginProcessManager.installHook来实现的。而在分析DroidPlugin Hook过程之前需要先简单了解一下Java的动态代理。 Java动态代理与之相关的一个类Proxy,一个接口InvocationHandler,一个函数invoke他们之间的关系。就通过DroidPlugin 的BinderHook类的部分代码来解释一下他们的关系; abstract cl
大概半年之前,看过鸿洋大神的一篇博客 Android 自定义控件玩转字体变色 打造炫酷ViewPager指示器 他说大概想了32秒就知道了实现思路,这深深的刺痛了我。最近又看了一遍,决定做点什么 我要自定义的控件是一个盖世英雄, 它不仅仅是一个 Loading控件 ,同时还支持 进度条 (ProgressBar) 功能 。 它会在你需要的时候出现, 它支持 left , top , right , bottom 四个方向加载(变色),最重要的是,它可以是 文字 ,也可以是 图片 ,能够满足开发者一切需求。
首先为权限: uses-permission android:name="android.permission.INTERNET" / uses-permission android:name="com.android.vending.BILLING" / uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" / uses-permission android:name="android.permission.AC
Day01 html与css基础入门 1.html的常见标签和实战 1.1 a标签 a href=#> 1.2 img标签 img src="plmm.jpg" width="100px" height="200px" alt="图片信息丢失!"/// alt属性的值表示当图片找不到时显示的文字信息 1.3 列表标签 ol type="I" start="1" li我是天才1号/li li我是天才2号/li li我是天才3号/li li我是天才4号/li/olul type="circle" li我是逗逼

EventBus 源码分析 - 2016-07-23 14:07:10

0. 前言 EventBus 是一款针对Android优化的发布/订阅事件总线。主要功能是替代 Intent , Handler , BroadCast 在 Fragment , Activity , Service ,线程之间传递消息。优点是开销小,代码更优雅,以及将发送者和接收者解耦。此文将对最新的 EventBus 3.0 的源码进行简要的分析。 1. 用法回顾 EventBus 3.0 的用法较之前的版本有所变化,它使用了最近较为流行的注解形式取代以前的 onEvent 开头作为方法名,但使用步骤

Qt之资源系统 - 2016-07-23 14:07:10

简述 Qt 的资源系统用于存储应用程序的可执行二进制文件,它采用平台无关的机制。当你的程序总需要这样的一系列文件(图标、翻译文件等)并且不想冒丢失某些文件的风险时,这就显得十分有用。 资源系统基于 qmake、rcc(Qt 资源编译器) 和 QFile 之间的紧密合作。 简述 资源集合文件qrc 外部二进制资源 内编译资源 压缩 在程序中使用资源 在库中使用资源 更多参考 资源集合文件(.qrc) 与程序相关的资源在被指定在一个 .qrc 文件中,其基于 XML 的文件格式列出了磁盘上的文件,可以为它们指