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

时间: 2024-10-17 13:50:33

DroidPlugin源码分析安装和卸载的相关文章

DroidPlugin源码分析插件运行环境初始化

从DroidPlugin的官方文档中我们知道. 2 在AndroidManifest.xml中使用插件的com.morgoo.droidplugin.PluginApplication: 或者在自定义的Application的onCreate()函数中,调用PluginHelper.getInstance().applicationOnCreate(getBaseContext()); 在Application的attachBaseContext()函数中,调用 PluginHelper.get

插件开发之360 DroidPlugin源码分析(五)Service预注册占坑

请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52264977 在了解系统的activity,service,broadcastReceiver的启动过程后,今天将分析下360 DroidPlugin是如何预注册占坑的?本篇文章主要分析Service预注册占坑,Service占了坑后又是什么时候开始瞒天过海欺骗AMS的?先看下Agenda: AndroidMainfest.xml中概览 Service中关键方法被h

插件开发之360 DroidPlugin源码分析(四)Activity预注册占坑

请尊重分享成果,转载请注明出处: http://blog.csdn.net/hejjunlin/article/details/52258434 在了解系统的activity,service,broadcastReceiver的启动过程后,今天将分析下360 DroidPlugin是如何预注册占坑的?本篇文章主要分析Activity预注册占坑,Activity占了坑后又是什么时候开始瞒天过海欺骗AMS的?先看下Agenda: AndroidMainfest.xml中概览 Activity中关键方

DroidPlugin源码分析插件进程管理以及预注册Activity,Service,ContentProvide的选择

在360对DroidPlugin的特点介绍中有云: 插件的四大组件完全不需要在Host程序中注册,支持Service.Activity.BroadcastReceiver.ContentProvider四大组件. 实现了进程管理,插件的空进程会被及时回收,占用内存低. 之所以支持Service,Activity,ContentProvider三大组件,是因为DroidPlugin在AndroidManifest文件中预先注册了8个运行插件的进程,每个进程预注册Service一个, Content

memcached源码分析-----安装、调试以及如何阅读memcached源码

        转载请注明出处:http://blog.csdn.net/luotuo44/article/details/42639131 安装: 安装memcached之前要先安装Libevent.现在假定Libevent安装在/usr/local/libevent目录了. 因为memcached安装后不像Libevent那样,有一堆头文件和库文件.安装后的memcached不是用来编程而直接用来运行的.所以不需要在/usr/local目录下专门为memcached建立一个目录.直接把mem

Linux源码编译安装和卸载

Linux下正常的编译安装/卸载 源码的安装一般由3个步骤组成: 配置(configure) 编译(make) 安装(make install). configure文件是一个可执行的脚本文件,它有很多选项,在待安装的源码目录下使用命令./configure –help可以输出详细的选项列表. 其中--prefix选项是配置安装目录,如果不配置该选项,安装后可执行文件默认放在/usr /local/bin,库文件默认放在/usr/local/lib,配置文件默认放在/usr/local/etc,

centos 7 源码包安装、卸载nginx

1.源码包安装之前,首页安装依赖包 yum -y install gcc gcc-c++ make libtool zlib zlib-devel openssl openssl-devel pcre pcre-devel 2.去nginx官网去下载稳定版安装包 wget http://nginx.org/download/nginx-1.14.2.tar.gz 3.解压缩 tar -zxvf nginx-1.14.2.tar.gz 3.编译安装 cd nginx-1.14.2 ./config

DroidPlugin源码分析服务与静态广播的处理214r3rff4

上一篇文章分析过DroidPlugin对Activity的处理过程,不得不为对DroidPlugin的工程师们钦佩不已,那么是不是Service可以像Activity的处理过程一样来处理呢?前面讲过每一个代理进程只是预定义了一个Service,如果某一个插件中有多个Service,那岂不是某一个时刻只能有一个Service运行呢?由此可以判定可能Service的处理和Activity不一样. 一方面:平时使用Activity主要是用于展示界面和用户交互,Activity的生命周期可能受用户控制,

DroidPlugin源码分析处理Activity的启动211ew4dfr

正常情况下启动一个Activity,首先需要在AndroidManifest文件中声明,其次需要把该应用安装到手机系统中. 而插件apk是没有正在安装到手机系统中的,也就按照正常的启动流程插件Activity是不能启动的.另外插件apk的类需要加载进来是需要指定ClassLoader.前面的文章也大概讲过,当启动一个插件Activity时,先是用预定义的代理Activity替换目标Activity(及插件Activity)去启动,当AMS处理完回调到应用空间时(及回到运行Activity的进程空