ContentProvider插件化解决方案
--摘自《android插件化开发指南》
1.当要传输的数据量大小不超过1M的时候,使用Binder;数据量超过1M时,Binder就搞不定了,需要ContentProvider
2.ContentProvider就是一个数据库引擎,向外界提供了CRUD的API
ContentProvider插件化
将静态Provider手动安装到宿主app中,把它们放在宿主的ContentProvider列表中,就可以使用了
/**
* 由于应用程序使用的ClassLoader为PathClassLoader
* 最终继承自 BaseDexClassLoader
* 查看源码得知,这个BaseDexClassLoader加载代码根据一个叫做
* dexElements的数组进行, 因此我们把包含代码的dex文件插入这个数组
* 系统的classLoader就能帮助我们找到这个类
*
* 这个类用来进行对于BaseDexClassLoader的Hook
* 类名太长, 不要吐槽.
* @author weishu
* @date 16/3/28
*/
//***第一步:宿主app和插件app的dex合并到一起
public final class BaseDexClassLoaderHookHelper { public static void patchClassLoader(ClassLoader cl, File apkFile, File optDexFile)
throws IllegalAccessException, NoSuchMethodException, IOException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// 获取 BaseDexClassLoader : pathList
Object pathListObj = RefInvoke.getFieldObject(DexClassLoader.class.getSuperclass(), cl, "pathList"); // 获取 PathList: Element[] dexElements
Object[] dexElements = (Object[]) RefInvoke.getFieldObject(pathListObj, "dexElements"); // Element 类型
Class<?> elementClass = dexElements.getClass().getComponentType(); // 创建一个数组, 用来替换原始的数组
Object[] newElements = (Object[]) Array.newInstance(elementClass, dexElements.length + 1); // 构造插件Element(File file, boolean isDirectory, File zip, DexFile dexFile) 这个构造函数
Class[] p1 = {File.class, boolean.class, File.class, DexFile.class};
Object[] v1 = {apkFile, false, apkFile, DexFile.loadDex(apkFile.getCanonicalPath(), optDexFile.getAbsolutePath(), 0)};
Object o = RefInvoke.createObject(elementClass, p1, v1); Object[] toAddElementArray = new Object[] { o };
// 把原始的elements复制进去
System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
// 插件的那个element复制进去
System.arraycopy(toAddElementArray, 0, newElements, dexElements.length, toAddElementArray.length); // 替换
RefInvoke.setFieldObject(pathListObj, "dexElements", newElements);
}
}
public class ProviderHelper { /**
* 解析Apk文件中的 <provider>, 并存储起来
* 主要是调用PackageParser类的generateProviderInfo方法
*
* @param apkFile 插件对应的apk文件
* @throws Exception 解析出错或者反射调用出错, 均会抛出异常
*/
public static List<ProviderInfo> parseProviders(File apkFile) throws Exception { //获取PackageParser对象实例
Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
Object packageParser = packageParserClass.newInstance(); // 首先调用parsePackage获取到apk对象对应的Package对象
Class[] p1 = {File.class, int.class};
Object[] v1 = {apkFile, PackageManager.GET_PROVIDERS};
Object packageObj = RefInvoke.invokeInstanceMethod(packageParser, "parsePackage",p1, v1); // 读取Package对象里面的services字段
// 接下来要做的就是根据这个List<Provider> 获取到Provider对应的ProviderInfo
List providers = (List) RefInvoke.getFieldObject(packageObj, "providers"); // 调用generateProviderInfo 方法, 把PackageParser.Provider转换成ProviderInfo //准备generateProviderInfo方法所需要的参数
Class<?> packageParser$ProviderClass = Class.forName("android.content.pm.PackageParser$Provider");
Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
Object defaultUserState = packageUserStateClass.newInstance();
int userId = (Integer) RefInvoke.invokeStaticMethod("android.os.UserHandle", "getCallingUserId");
Class[] p2 = {packageParser$ProviderClass, int.class, packageUserStateClass, int.class}; List<ProviderInfo> ret = new ArrayList<>();
// 解析出intent对应的Provider组件
for (Object provider : providers) {
Object[] v2 = {provider, 0, defaultUserState, userId};
//***第二步:把得到的Package对象转换为我们需要的ProviderInfo类型对象***
ProviderInfo info = (ProviderInfo) RefInvoke.invokeInstanceMethod(packageParser, "generateProviderInfo",p2, v2);
ret.add(info);
} return ret;
} /**
* 在进程内部安装provider, 也就是调用 ActivityThread.installContentProviders方法
*
* @param context you know
* @param apkFile
* @throws Exception
*/
public static void installProviders(Context context, File apkFile) throws Exception {
List<ProviderInfo> providerInfos = parseProviders(apkFile);
//***第三步:把插件ContentProvider的packageName设置为当前apk的packageName
for (ProviderInfo providerInfo : providerInfos) {
providerInfo.applicationInfo.packageName = context.getPackageName();
}
//***第四步:把这些插件ContentProvider安装到宿主App中
Object currentActivityThread = RefInvoke.getStaticFieldObject("android.app.ActivityThread", "sCurrentActivityThread"); Class[] p1 = {Context.class, List.class};
Object[] v1 = {context, providerInfos}; RefInvoke.invokeInstanceMethod(currentActivityThread, "installContentProviders", p1, v1);
}
}
Hook的时机很重要,越早越好,不然外部app调用插件的ContentProvider就要等很久了
/**
* 一定需要Application,并且在attachBaseContext里面Hook
* 因为provider的初始化非常早,比Application的onCreate还要早
* 在别的地方hook都晚了。
*
* @author weishu
* @date 16/3/29
*/
public class UPFApplication extends Application { @Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base); try {
File apkFile = getFileStreamPath("plugin2.apk");
if (!apkFile.exists()) {
Utils.extractAssets(base, "plugin2.apk");
} File odexFile = getFileStreamPath("plugin2.odex"); // Hook ClassLoader, 让插件中的类能够被成功加载
BaseDexClassLoaderHookHelper.patchClassLoader(getClassLoader(), apkFile, odexFile); //安装插件中的Providers
ProviderHelper.installProviders(base, getFileStreamPath("plugin2.apk"));
} catch (Exception e) {
throw new RuntimeException("hook failed", e);
}
}
}
ContentProvider转发机制
在当前app中定义一个StubContentProvider作为中转,让外界app调用当前app的StubContentProvider,再调用插件里的ContentProvider
/**
* 为了使得插件的ContentProvder提供给外部使用,我们需要一个StubProvider做中转;
* 如果外部程序需要使用插件系统中插件的ContentProvider,不能直接查询原来的那个uri
* 我们对uri做一些手脚,使得插件系统能识别这个uri;
*
* 这里的处理方式如下:
*
* 原始查询插件的URI应该为:
* content://host_auth/plugin_auth/path/query
* 例子 content://baobao222/jianqiang
*
* 如果需要查询插件,替换为:
*
* content://plugin_auth/path/query
* 例子 content://jianqiang
*
* 也就是,我们把插件ContentProvider的信息放在URI的path中保存起来;
* 然后在StubProvider中做分发。
*
* @param raw 外部查询我们使用的URI
* @return 插件真正的URI
*/
private Uri getRealUri(Uri raw) {
String rawAuth = raw.getAuthority();
if (!AUTHORITY.equals(rawAuth)) {
Log.w(TAG, "rawAuth:" + rawAuth);
} String uriString = raw.toString();
uriString = uriString.replaceAll(rawAuth + '/', "");
Uri newUri = Uri.parse(uriString);
Log.i(TAG, "realUri:" + newUri);
return newUri;
}
这是ContentProvider独有的URI机制,而且是简单的字符串,所以很适合这种转发机制
欢迎关注我的微信公众号:安卓圈
ContentProvider插件化解决方案的更多相关文章
- BroadcastReceiver插件化解决方案
--摘自<android插件化开发指南> 1.静态广播和动态广播仅区别于注册方式的不同.静态广播的注册信息保存在PMS中,动态广播的注册信息保存在AMS中 2.发送广播,也就是Contex ...
- Service插件化解决方案
--摘自<android插件化开发指南> 1.ActivityThread最终是通过Instrumentation启动一个Activity的.而ActivityThread启动Servic ...
- Activity插件化解决方案
--摘自<android插件化开发指南> 1.宿主App加载插件中的类 2.最简单的插件化方案就是在宿主的androidmanifest.xml中申明插件中的四大组件 把插件dex合并到宿 ...
- Android插件化的兼容性(中):Android P的适配
Android系统的每次版本升级,都会对原有代码进行重构,这就为插件化带来了麻烦. Android P对插件化的影响,主要体现在两方面,一是它重构了H类中Activity相关的逻辑,另一个是它重构了I ...
- 包建强的培训课程(10):Android插件化从入门到精通
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);@import url(/c ...
- Android 插件化开发(四):插件化实现方案
在经过上面铺垫后,我们可以尝试整体实现一下插件化了.这里我们先介绍一下最简单的实现插件化的方案. 一.最简单的插件化实现方案 最简单的插件化实现方案,对四大组件都是适用的,技术面涉及如下: 1). 合 ...
- 《Android插件化开发指南》面世
本书在京东购买地址:https://item.jd.com/31178047689.html 本书Q群:389329264 (一)这是一本什么书 如果只把本书当作纯粹介绍Android插件化技术的书籍 ...
- Android插件化的兼容性(上):Android O的适配
首先声明,<Android插件化开发指南>这本书所介绍的Android底层是基于Android6.0(API level 23)的,而本书介绍的各种插件化解决方案,以及配套的70多个例子, ...
- android 插件化框架VitualAPK
推荐阅读: 滴滴Booster移动App质量优化框架-学习之旅 一 Android 模块Api化演练 不一样视角的Glide剖析(一) LeakCanary 与 鹅场Matrix ResourceCa ...
随机推荐
- Javascript杂!
JavaScript 标准参考教程(alpha) javascript中的 Object.defineProperty()和defineProperties JS压缩混淆 ---- 雅虎YUI 在线 ...
- 2018 github热门项目
github流行的几个项目,我们来学习一下. 1. developer-roadmap-chinese image.png 项目简介:2018年web程序员路线中文版, 这个仓库里包含了一些前端,后端 ...
- swift 学习- 13 -- 下标
// 下标 可以定义在 类, 结构体, 和 枚举 中, 是访问集合, 列表或 序列中元素的快捷方式, 可以使用下标的索引, 设置 和 获取值, 而不需要再调用对应的存取方法, 举例来说, 用下标访问一 ...
- 使用Eclipse进行Makefile项目
最近在MCU on Eclipse网站上看到Erich Styger所写的一篇有关在Eclipse中使用Makefile创建项目的文章,文章讲解清晰明了非常不错,所以呢没人将其翻译过来供各位同仁参考. ...
- SQL Server2008从入门到精通pdf
下载地址:网盘下载 内容介绍 编辑 <SQL Server 从入门到精通>从初学者的角度出发,通过通俗易懂的语言.丰富多彩的实例,详细地介绍了SQLServer2008开发应该掌握的各方面 ...
- Confluence 6 使用 Velocity 宏
当编辑自定义 Decorator 模板文件的时候,有一些宏可被用来定义页面中复杂或者多变的内容,例如菜单,链接等.你可以插入这些宏到你的模板中.更多的信息,请参考Working With Decora ...
- Guideline 5.2.1 - Legal - Intellectual Property 解决方案
最近在上架公司公司项目的时候遇到这个问题什么5.2.1 然后去了解发现最近不少人都遇到了这个问题.先说一下 我上架的APP是一个医疗的APP然后说需要什么医疗资质,估计是账号的公司资质不够吧.后面和苹 ...
- java多线程快速入门(十八)
Lock锁是JDK1.5之后推出的并发包里面的关键字(注意捕获异常,释放锁) Lock与synchronized的区别 Lock锁可以人为的释放锁(相当于汽车中的手动挡) synchronized当线 ...
- json的转换操作
toJSON 把JS对象{ 'x': 2, 'y': 3 }转为JSON对象格式的字符串 不能转化字符串 比如"{ 'x': 2, 'y': 3 }" 可以转格式不标准的jso ...
- 小学生都看得懂的C语言入门(6): 字符串
1.字符用 char 表示 #include<stdio.h> int main() { char c; char d; c=; d='; if (c==d){ printf(" ...