©wequick GalenLin All rights reserved FBI WARNING 本文件仅作为学习交流之用 请勿用以商业用途 ©wequick GalenLin All rights reserved
Small:插件化轻巧之道 林光亮
首页 0x00 诞生 0x01 轻 0x02 巧 0x03 TODO
0x00 诞生 诞生-头疼 模块1 模块2 模块3 模块4 模块5 模块6
两篇文章 诞生 分析支付宝客户端插件机制 @唐巧-猿题库 手机淘宝客户端架构探索实践 @于佳-阿里 模块1 模块2 模块3 模块4 模块5 模块6 分析支付宝客户端插件机制 @唐巧-猿题库 手机淘宝客户端架构探索实践 @于佳-阿里
基础架构 诞生 + Bundle Launcher 分析支付宝客户端插件机制 @唐巧-猿题库 手机淘宝客户端架构探索实践 @于佳-阿里 iOS HTML Android Bundle Launcher
开源探索 诞生 + Bundle Launcher Dynamic-Load-APK @任玉刚-百度 Direct-Load-APK Android Bundle Launcher + iOS HTML Dynamic-Load-APK @任玉刚-百度 Direct-Load-APK @罗迪-高中生
DLA架构 诞生 Host Dynamic-Load-APK @任玉刚-百度 Direct-Load-APK @罗迪-高中生 模块1 模块2 Android 模块1 模块2 模块3 Dynamic-Load-APK @任玉刚-百度 Host Direct-Load-APK @罗迪-高中生
诞生 不支持公共库 模块1 公共 模块 Host 模块3
诞生 公共并宿主 模块1 公共 模块 公共 模块 Host 模块3
DLA改造 诞生 Host 使用public.xml 锁定公共资源ID 打通宿主与插件的资源与代码共享 模块1 公共 模块 DLA 改进 模块3
重新探索 诞生 Gradle 1.3+ Unsupported Host 使用public.xml 锁定公共资源ID 模块1 模块3 公共 模块 DLA 改进 Host 使用public.xml 锁定公共资源ID 打通宿主与插件的资源与代码共享 Gradle 1.3+ Unsupported Android-Plugin-Framework @Limpoxe ACDD @Bunny Blue
aapt改造 诞生 aapt Android-Plugin-Framework @Limpoxe ACDD @Bunny Blue 模块1 0x7A 模块2 0x7B 模块3 0x7C Android-Plugin-Framework @Limpoxe aapt ACDD @Bunny Blue
诞生 Small诞生 模块1 0x7A 模块2 0x7B 模块3 0x7C aapt
轻 轻 A A’ B A C B 轻盈产出 @Compile-time 轻度Hook @Run-time
APK结构 B C 轻/轻盈产出 APK A A’ B AM.xml classes.dex resources.arsc res/* A 轻度Hook @Run-time A A’ B AM.xml 轻盈产出 @Compile-time A C B classes.dex APK resources.arsc res/*
APK拆解 轻/轻盈产出 AM$1 dex$1 arsc$1 res$1 AM.xml classes.dex resources.arsc 文本 文件(*.jar) 二进制 文件(*.xml/png) 拆分粒度/方案 AM$1 dex$1 arsc$1 res$1 AM.xml classes.dex resources.arsc res/* AM$2 dex$2 arsc$2 res$2
Arsc结构 轻/轻盈产出 resources.arsc 0000000: 0000010: 0000020: 0000030: 0000040: 0000050: 0000060: 0000070: 0000080: 0000090: 00000a0: 00000b0: 00000c0: 00000d0: 00000e0: 00000f0: 0000100: 0000110: 0000120: 0000130: 0200 0c00 4804 0000 0100 0000 0100 1c00 8c00 0000 0400 0000 0000 0000 0001 0000 2c00 0000 0000 0000 0000 0000 2500 0000 4800 0000 5700 0000 2222 7265 732f 6d69 706d 6170 2d68 6470 692d 7634 2f69 635f 6c61 756e 6368 6572 2e70 6e67 0020 2072 6573 2f6d 6970 6d61 702d 6864 7069 2d76 342f 6963 5f70 6c75 6769 6e2e 706e 6700 0c0c 4c65 6172 6e69 6e67 4172 7363 0006 0650 6c75 6769 6e00 0002 2001 b003 0000 7f00 0000 6e00 6500 7400 2e00 7700 6500 7100 7500 6900 6300 6b00 2e00 6100 7200 7300 6300 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ....H........... ................ ,...........%... H...W...""res/mi pmap-hdpi-v4/ic_ launcher.png. r es/mipmap-hdpi-v 4/ic_plugin.png. ..LearningArsc.. .Plugin... ..... ....n.e.t...w.e. q.u.i.c.k...a.r. s.c............. resources.arsc
Arsc区域高亮 轻/轻盈产出/arsc格式 0000000: 0000010: 0000020: 0000030: 0000040: 0000050: 0000060: 0000070: 0000080: 0000090: 00000a0: 00000b0: 00000c0: 00000d0: 00000e0: 00000f0: 0000100: 0000110: 0000120: 0000130: 0200 0c00 4804 0000 0100 0000 0100 1c00 8c00 0000 0400 0000 0000 0000 0001 0000 2c00 0000 0000 0000 0000 0000 2500 0000 4800 0000 5700 0000 2222 7265 732f 6d69 706d 6170 2d68 6470 692d 7634 2f69 635f 6c61 756e 6368 6572 2e70 6e67 0020 2072 6573 2f6d 6970 6d61 702d 6864 7069 2d76 342f 6963 5f70 6c75 6769 6e2e 706e 6700 0c0c 4c65 6172 6e69 6e67 4172 7363 0006 0650 6c75 6769 6e00 0002 2001 b003 0000 7f00 0000 6e00 6500 7400 2e00 7700 6500 7100 7500 6900 6300 6b00 2e00 6100 7200 7300 6300 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ....H........... ................ ,...........%... H...W...""res/mi pmap-hdpi-v4/ic_ launcher.png. r es/mipmap-hdpi-v 4/ic_plugin.png. ..LearningArsc.. .Plugin... ..... ....n.e.t...w.e. q.u.i.c.k...a.r. s.c.............
Arsc区段 轻/轻盈产出/arsc格式 0000000: 0000010: 0000020: 0000030: 0000040: 0000050: 0000060: 0000070: 0000080: 0000090: 00000a0: 00000b0: 00000c0: 00000d0: 00000e0: 00000f0: 0000100: 0000110: 0000120: 0000130: 0200 0c00 4804 0000 0100 0000 0100 1c00 8c00 0000 0400 0000 0000 0000 0001 0000 2c00 0000 0000 0000 0000 0000 2500 0000 4800 0000 5700 0000 2222 7265 732f 6d69 706d 6170 2d68 6470 692d 7634 2f69 635f 6c61 756e 6368 6572 2e70 6e67 0020 2072 6573 2f6d 6970 6d61 702d 6864 7069 2d76 342f 6963 5f70 6c75 6769 6e2e 706e 6700 0c0c 4c65 6172 6e69 6e67 4172 7363 0006 0650 6c75 6769 6e00 0002 2001 b003 0000 7f00 0000 6e00 6500 7400 2e00 7700 6500 7100 7500 6900 6300 6b00 2e00 6100 7200 7300 6300 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 ....H........... ................ ,...........%... H...W...""res/mi pmap-hdpi-v4/ic_ launcher.png. r es/mipmap-hdpi-v 4/ic_plugin.png. ..LearningArsc.. .Plugin... ..... ....n.e.t...w.e. q.u.i.c.k...a.r. s.c............. hex(LE) 小端代码 struct 数据结构 0200 ResTable_header 0100 ResStringPool_header 0002 ResTable_package 0202 ResTable_typeSpec 0102 ResTable_type
Arsc读取 轻/轻盈产出/arsc格式 1c00 8c00 0000 0400 0000 0000 0000 0001 0000 4800 0000 5700 0000 2222 7265 732f 6d69 706d 6170 2d68 6470 692d 7634 2f69 635f 6c61 756e 6368 6572 2e70 6e67 0020 2072 6573 2f6d 6970 6d61 702d 6864 7069 2d76 342f 6963 5f70 6c75 6769 6e2e 706e 6700 0c0c 4c65 6172 6e69 6e67 4172 7363 0006 0650 6c75 6769 6e00 0200 0c00 4804 0000 0100 0000 0100 hex(LE) 小端代码 struct 数据结构 0200 ResTable_header 0100 ResStringPool_header 0002 ResTable_package 0202 ResTable_typeSpec 0102 ResTable_type 2001 b003 0000 7f00 0000 6e00 6500 7400 2e00 7700 6500 7100 7500 6900 6300 6b00 2e00 6100 7200 7300 6300 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0002
Arsc/ID解析 轻/轻盈产出/arsc格式 资源ID 并非实际存在 hex(LE) 小端代码 struct 数据结构 0200 ResTable_header 0100 ResStringPool_header 0002 ResTable_package 0202 ResTable_typeSpec 0102 ResTable_type table: { package: { id: 0x7F, name: "net.wequick.arsc" }, strings: [ "res/mipmap-hdpi-v4/ic_launcher.png", "res/mipmap-hdpi-v4/ic_plugin.png", "LearningArsc", "Plugin" ], typeStrings: [ "attr", "mipmap", "string", "style" ], keyStrings: [ "ic_launcher", "ic_plugin", "app_name", "s_plugin", "AppTheme", "PluginTheme" typeSpecs: [ { types: [] }, { types: [ Configs@ic_launcher, Configs@ic_plugin ] }, { types: [ Configs@app_name, Configs@s_plugin ] }, { types: [ Configs@AppTheme, Configs@PluginTheme ] } ] } 7F 资源ID PP 包ID TT 类型ID NNNN 项目ID 01 02 03 04 0x 01 0000 0001 02 资源ID 并非实际存在 Configs@ic_launcher 03 04
分离方案1 轻/轻盈产出/arsc分离 想象中最简单的分离方式 存在问题:必须补齐资源(输出变大)、只能分离一个插件 host (0x7f) table: { package: { id: 0x7F, name: "net.wequick.arsc" }, strings: [ "res/mipmap-hdpi-v4/ic_launcher.png", "res/mipmap-hdpi-v4/ic_plugin.png", "LearningArsc", "Plugin" ], typeStrings: [ "attr", "mipmap", "string", "style" ], keyStrings: [ "ic_launcher", "ic_plugin", "app_name", "s_plugin", "AppTheme", "PluginTheme" typeSpecs: [ { types: [] }, { types: [ Configs@ic_launcher, Configs@ic_plugin ] }, { types: [ Configs@app_name, Configs@s_plugin ] }, { types: [ Configs@AppTheme, Configs@PluginTheme ] } ] } 01 02 03 04 0001 host (0x7f) |-- mipmap (02) | |-- ic_launcher (0000) | `-- values |-- strings.xml (03) | |-- app_name (0000) `-- syles.xml (04) |-- AppTheme (0000) plugin (0x7f) |-- mipmap (02) | |-- | `-- values |-- strings.xml (03) `-- syles.xml (04) |-- `-- ic_plugin (0001) `-- s_plugin (0001) `-- PluginTheme (0001) padding_mipmap_0000 资源ID PP 包ID TT 类型ID NNNN 项目ID 02 7F 0x 0000 padding_string_0000 padding_style_0000 存在问题:必须补齐资源(输出变大)、只能分离一个插件
分离方案2 轻/轻盈产出/arsc分离 实践中最极致的分离方式 host (0x7f) |-- mipmap (02) | |-- ic_launcher (0000) | `-- values |-- strings.xml (03) | |-- app_name (0000) `-- syles.xml (04) |-- AppTheme (0000) plugin ( |-- mipmap (02) | | | `-- ic_plugin ( `-- values |-- strings.xml (03) | `-- s_plugin ( `-- syles.xml (04) | `-- PluginTheme ( 0x7e ) 0000 0000 0000
融合 轻/轻盈产出 plugin ( |-- mipmap (02) | | | `-- ic_plugin ( `-- values | | | `-- ic_plugin ( `-- values |-- strings.xml (03) | `-- s_plugin ( `-- syles.xml (04) | `-- PluginTheme ( ) 0000 0x7e HOST 0x7e 0x7d 7c
轻度Hook 我重写一个「对象」的「方法」 轻/轻度Hook 我是一个Hook 让她忘了从前 0x7e HOST 方法A Hook A 方法B 我是一个Hook 我重写一个「对象」的「方法」 让她忘了从前 HOST 0x7e 0x7d 7c 一个对象
Hook条件 我重写一个「对象」的「方法」 轻/轻度Hook 我是一个Hook 让她忘了从前 但是首先 我要找到她 她「静态」的坐着 方法A Hook A 方法B 我是一个Hook 我重写一个「对象」的「方法」 让她忘了从前 但是首先 我要找到她 在我的「进程」里 她「静态」的坐着 她的方法向我「开放」 一个对象
Hook任务 轻/轻度Hook 但是首先 我要找到她 在 我的「进程」 里 她「静态」的坐着 她的方法向我「开放」 一个对象 方法A Hook A 方法B 但是首先 我要找到她 在 她「静态」的坐着 她的方法向我「开放」 com.user.galen 我的「进程」 里 启动 插件Activity
Activity启动过程 轻/轻度Hook Not Found 系统进程 我的「进程」 我的「进程」 system.process Intent解析 任务栈调度 Activity栈调度 我的「进程」 com.user.galen 启动 插件Activity 我的「进程」 com.user.galen 实际启动 插件Activity Not Found
伪装宿主 轻/轻度Hook Not Found 系统进程 我的「进程」 我的「进程」 system.process Intent解析 任务栈调度 Activity栈调度 我的「进程」 我的「进程」 com.user.galen 实际启动 插件Activity com.user.galen 启动 插件Activity Hook 伪装宿主 Not Found
还原插件 轻/轻度Hook 系统进程 我的「进程」 我的「进程」 system.process com.user.galen Intent解析 任务栈调度 Activity栈调度 我的「进程」 我的「进程」 com.user.galen com.user.galen 启动 插件Activity Hook 伪装宿主 Hook 还原插件 实际启动 插件Activity
还原解析 轻/轻度Hook 我的「进程」 系统进程 com.user.galen system.process Intent解析 任务栈调度 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { // 创建Activity if (r.activityInfo.targetActivity != null) { component = new ComponentName(r.activityInfo.packageName, r.activityInfo.targetActivity); } java.lang.ClassLoader cl = r.packageInfo.getClassLoader(); activity = mInstrumentation.newActivity( cl, component.getClassName(), r.intent); // 绑定Context Context appContext = createBaseContextForActivity(r, activity); activity.attach(appContext, this, getInstrumentation(), ...); // 设置主题 int theme = r.activityInfo.getThemeResource(); if (theme != 0) { activity.setTheme(theme); // 触发onCreate mInstrumentation.callActivityOnCreate(activity, r.state); 我的「进程」 com.user.galen 系统进程 system.process Intent解析 任务栈调度 Activity栈调度 启动 插件Activity Hook 伪装宿主 还原插件 Small Droid Plugin 实际启动 插件Activity Android-Plugin- Framework ACDD Dynamic-Load-APK Direct-Load-APK
Android-Plugin-Framework 轻/轻度Hook 方案对比 插件方案 代表框架 包数 类加载器 个数 资源管理器 Context 完全隔离 Droid Plugin 1/插件 Dynamic-Load-APK 1 1/插件Activity Direct-Load-APK 宿主插件 两两融合 Android-Plugin-Framework 除主题外 完全融合 ACDD Small
巧 巧 IDE友好 @Debug 模块变身 @Release
IDE友好 巧/IDE友好 支持创建插件模块 支持编译插件模块 支持插件模块间依赖 支持联合调试 模块变身 @Release IDE友好 @Debug 支持创建插件模块 支持编译插件模块 支持插件模块间依赖 支持联合调试
IDE多模块 巧/IDE友好 支持创建插件模块 支持编译插件模块 支持插件模块间依赖 支持联合调试 app.* web.* app lib.* lib.*
模块依赖 巧/IDE友好 app.* web.* app lib.* lib.* vendor lib.* vendor vendor
模块变身 巧/模块变身 模块是开发态,插件是目标态。 模块 开发态 目标态 转换难点 app.* Application模块 可以依赖其他模块、可以独立运行 带代码、资源的插件 AAR(代码/资源)剥离 lib.* Library模块 可以被app.*依赖 资源ID锁定 [other].* 可以独立运行 仅含assets的插件
AAR分离1 巧/模块变身 模块是开发态,插件是目标态。 模块 开发态 目标态 转换难点 app.* Application模块 dependencies { provided fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.2.1' compile 'com.android.support:design:23.2.1' } 巧/模块变身 AAR分离1 模块是开发态,插件是目标态。 模块 开发态 目标态 转换难点 app.* Application模块 可以依赖其他模块、可以独立运行 带代码、资源的插件 AAR(代码/资源)剥离 lib.* Library模块 可以被app.*依赖 资源ID锁定 [other].* 可以独立运行 仅含assets的插件 AAR
AAR分离2 巧/模块变身 classes.jar *.class classes.dex classes.dex res/* res/* dependencies { provided fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.2.1' compile 'com.android.support:design:23.2.1' } app.main `-- build/intermediates/exploded-aar `-- com.android.support/appcompat-v7/23.2.1 dex classes.jar *.class classes.dex classes.dex aapt Small res/* res/* arsc res/* arsc res/*
总结 巧/模块变身 总结 classes.jar res/* arsc classes.dex *.class 开发时聚合 编译时分离 AAR dependencies { provided fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.2.1' compile 'com.android.support:design:23.2.1' } app.main `-- build/intermediates/exploded-aar `-- com.android.support/appcompat-v7/23.2.1 classes.jar res/* aapt arsc Small dex classes.dex *.class IDE多模块 公共库依赖 开发时聚合 PP分段 AAR分离 编译时分离 并入宿主 轻度Hook 运行时融合
TODO 总结 TODO 开发时聚合 编译时分离 运行时融合 按需加载 iOS插件化App Store布局 IDE多模块 公共库依赖 插件间依赖关系 iOS插件化App Store布局 xib级别的warm swap IDE多模块 公共库依赖 开发时聚合 PP分段 AAR分离 编译时分离 并入宿主 轻度Hook 运行时融合
Small@wequick GalenLin