VirtualApk源码分享-ContentProvider插件化

综合技术 2018-05-20

android通过ContentProvider可以实现进程间的数据共享,例如APP通过MediaProvider可以访问多媒体数据库的内容。通常我们在Activity通过getContentResolver().query来跨进程访问数据库,ContentImpl.getContentResolver会返回ContentResolver对象,

ContentImpl.getContentResolver

mContentResolver的具体类型为ApplicationContentResolver,它继承自ContentResolver;在获取到ContentResolver通过其query方法可以跨进程查询数据库,ContentResolver的query方法如下:

ContentResolver.query

首先获取了unstableProvider对象,获取经过:ContentResolver.acquireUnstableProvider-->ApplicationContentResolver.acquireUnstableProvider

ApplicationContentResolver.acquireUnstableProvider

ApplicationContentResolver.acquireUnstableProvider调用了mMainThread.acquireProvider来获得IContentProvider,该对象可以远程访问对方进程的ContentProvider,mMainThread就是当前进程的ActivityThread类。

ActivityThread. acquireProvider

acquireExistingProvider函数如下:

acquireExistingProvider

acquireExistingProvider查询当前进程是否已经保存过该ContentProvider的副本,副本的存在可以提高多次访问同一个ContentProvider的性能,如果不存在副本,ActivityThread.acquireProvider就会调用AMS.getContentProvider函数来进行查找。查找过程如下:AMS.getContentProvider-->AMS.getContentProviderImpl。

getContentProviderImpl函数很长,大概的流程如下:

1、mProviderMap.getProviderByName(name, userId)查找当前是否已经plublish,如果没有,进入第2步

2、 调用AppGlobals.getPackageManager().resolveContentProvider()获取ProviderInfo,其实就是调用PMS.resolveContentProvider,在APK安装的时候,PMS会解析AndroidManifest.xml文件。并将APK的ContentProvider信息保存成ProviderInfo。获取大屏ProviderInfo后进入第3步,

3、创建ContentProviderRecord对象,通过getProcessRecordLocked获取ContentProviderRecord对应的进程是否启动,如果进程已经启动,就将ContentProviderRecord保存到ContentProvider所属进程的pubProviders中,并调用AppliationThread.scheduleInstallProvider(会调用ActivityThread.installContentProviders)进行ContentProvider的安装。

getProcessRecordLocked

如果ContentProvider对应的进程没有启动,就会调用startProcessLocked启动进程,创建进程就是通过Zygote创建,并运行进程的入口类ActivtyThread.main。startProcessLocked是异步的,所以无法立即确定进程是否启动完成。所以getContentProviderImpl函数中实现了如下代码来等待ContentProvider所属的进程创建并完成ContentProvider的安装:

getContentProviderImpl等待进程完成Provider的安装

那么ContentProvider是什么时机安装的呢,答案就是ActivityThread.handleBindApplication:

ActivityThread.handleBindApplication

进程创建后会运行Application,这里调用了installContentProviders完成ContentProvider的安装,注意安装ContentProvider发生在Application的onCreate之前。

ActivityThread.installContentProviders

ContentProvider安装完成后调用AMS.publishContentProvider,该操作将当前进程的ContentProvider保存到了AMS,这样有其余进程想访问这个ContentProvider的时候,AMS就可以直接返回,不需要再次安装了。

所以说,一个ContentProvider在安装完成后会有两个副本,AMS中一个副本(不同进程访问同一个ContentProvider时可以复用AMS中的对象),访问这个ContentProvider的APP进程中有一个副本(同一个进程的多次访问同一个ContentProvider可以重复使用这个副本)

在了解了ContentProvider的运行流程后,如何对其进行插件化呢?这里有如下思路:

1、能否自己解析插件的内部的ContentProvider信息,然后安装到自己进程内部呢?即安装到ActivityThread的mProviderMap中。

这样做只能保证当前进程能够访问这个ContentProvider,其他进程则无法访问,要解决ContentProvider的共享必须有经过AMS的getContentProvider过程,而且要保证ProviderInfo在PMS已经存在,这显然是不可能的。

2、代理转发,在宿主APP中拦截ContentProvider的操作,然后将操作转给宿主占坑的ContentProvider,然后再由占坑ContentProvider进行转发,调用插件中具体的ContentProvider对象。没错,VirtualApk就是这么搞的。

占坑的ContentProvider

占坑的ContentProvider已经有了,那么接下来该怎么做呢?这里分为两个方面:

1、插件内部访问插件ContentProvider

插件内部使用的Context是PluginContext,VirtualApk复写了getContentResolver函数,返回了自己实现的PluginContentResolver。PluginContentResolver内部重写了acquireProvider、acquireExistingProvider、acquireUnstableProvider函数,这里以acquireUnstableProvider为例:

acquireUnstableProvider

mPluginManager.resolveContentProvider判断是否是查询插件的ContentProvider,如果是就使用Hook过对象:

hook的IContentProvider对象

hookIContentProviderAsNeed完成hook工作:

hookIContentProviderAsNeeded

hookIContentProviderAsNeeded替换了ActivityThread中已经安装好的auth对应的IContentProvider,改用自己的IContentProviderProxy。

mContext.getContentResolver().call(uri,"wakeup",null,null);十分重要,uri="content://packageName.VirtualAPK.Provider",他完成了RemoteContentProvider的安装,这样在ActivityThread中就保存了RemoteContentProvider对应的IContentProvider对象,然后给这个对象设置代理即可。这样当当前进程再访问RemoteContentProvider时就直接使用这个代理。

IContentProviderProxy.invoke

invoke函数首先将访问插件的Uri转到宿主占坑Uri:

wrapperUri

Uri的个数变成了content://host_authority/plugin_authority,其中host_authority表示宿主占坑ContentProvider对应的Auth,plugin_authority代表了实际要启动的插件ContentProvider的一些信息。

这样就插件内部在获取插件ContentProvider时的请求就转到了宿主ContentProvider中。

2、插件外部访问插件ContentProvider

插件外部访问插件ContentProvider,首先需要调用PluginContentResolver.wrapperUri将对插件访问的URI转为content://host_authority/plugin_authority格式,然后再由占坑ContentProvider进行转发。

在完成URI转换后,所有请求都转给了宿主占坑ContentProvider,下面就需要进行代理转发:

RemoteContentProvider.query

RemoteContentProvider.getContentProvider

getContentProvider通过反射创建出插件ContentProvider对象,最后调用ContentProvider.query完成查询。

您可能感兴趣的

Android IntentService使用介绍以及源码解析... 版权声明:本文出自汪磊的博客,转载请务必注明出处。 一、IntentService概述及使用举例 IntentService 内部实现机制用到了HandlerThread,如果对HandlerThread不了解的话建议先看上篇文章: Android HandlerThread使用介绍以...
友盟接入微信、QQ、微博三方登录 现如今,谁家的应用还没个第三方登录呢?不得接入个微信、QQ、微博登录吗?我做App接入登录不下于三遍了,可以说是驾轻就熟了吧。直接上手。 友盟接入官方文档 实现步骤: 1. 添加jar和res 下载 友盟集成工具.jar 文件,运行jar文件,选中需要接入第三方平台。 ...
安卓逆向分析之apktool与dex2jar等工具的使用... 记得自己刚学安卓的时候看到那些知名App的界面都做的那么好看,如腾讯QQ,就很想知道这么漂亮的界面布局layout是怎么做的,那个时候知道apk本质就是一个zip压缩包文件,因此会用解压缩的软件把apk解压出来,虽然解压缩之后可以得到那些精美的图片素材,但是layout布局文件打开内容是乱码...
Android忽略文件 我常用的gitignore列表 https://github.com/seeways/MyIgnore # Built application files *.apk *.ap_# Files for the ART/Dalvik VM *.dex# Java ...
教你如何使用CountDownTimer定时器(短信验证码倒计时)... 效果图镇楼 倒计时 短信验证 今天我们讲解一下短信验证码的倒计时功能如何处理。如有需要制作弹框的小伙伴也可先了解一下 http://blog.csdn.net/qq_36621990/article/...