组件化开发就是将一个app分成多个模块,每个模块都是一个组件(Module),开发的过程中我们可以让这些组件相互依赖或者单独调试部分组件等,但是最终发布的时候是将这些组件合并统一成一个apk,这就是组件化开发。
插件化开发和组件化开发略有不用,插件化开发时将整个app拆分成很多模块,这些模块包括一个宿主和多个插件,每个模块都是一个apk(组件化的每个模块是个lib),最终打包的时候将宿主apk和插件apk分开或者联合打包。
做插件化真正的目的:是为了去适应并行开发,是为了解耦各个模块,是为了避免模块之间的交叉依赖,是为了加快编译速度,从而提高并行开发效率。
加入一个app工程只有一个组件,随着app业务的壮大模块越来越多,代码量超10万是很正常的,这个时候我们会遇到以下问题
热修复,热修复其实也是动态加载原理
换肤,使用动态的资源加载可以实现换肤功能
实现免安装的功能
还可以通过hook系统的一些类做一些你想做的坏事。
插件化最早出现是因为65535问题出现的,用于查分多个dex并动态加载dex来防止65535问题
现在很多公司用插件化来做模块的动态加载,这样既能实现web端一样的随时更新,还能减少apk包的体积,其实就是加载一个未安装的apk。
四个月前(2015年8月初) , BunnyBlue开源了一个名为OpenAtlas的插件化框架, 这个插件化的来源可能并非所有人知晓, 我在这里概括一下:
2013年, 朱碧军同学在阿里的一次技术沙龙中分享了一个插件化框架: Atlas ,这个框架用在淘宝的Android客户端,没有开源,沙龙中也没有对该插件化的具体实现做讲解.(阿里技术沙龙)
15年, BunnyBlue同学在研究淘宝客户端时发现,Atlas框架没有混淆的十分彻底(在com.android.atlas下),于是花了一些时间对其中代码的实现进行了逆向,并在8月初开源了这一成果,并取名为OpenAtlas.
后来,由于涉及到一些代码来源方面的问题, OpenAtlas项目关闭,后改名为:ACCD(bunnyblue/ACDD · GitHub).
/build-tools//aapt
ClassLoader
机制,但是对于Android来说,并不是说类加载进来就可以用了,很多组件都是有“生命”的
资源加载方案大家使用的原理都差不多,都是用AssetManager
的隐藏方法addAssetPath
;但是,不同插件的资源如何管理?是公用一套资源还是插件独立资源?共用资源如何避免资源冲突?对于资源加载,有的方案共用一套资源并采用资源分段机制解决冲突(要么修改aapt
要么添加编译插件);有的方案选择独立资源,不同插件管理自己的资源。
目前国内开源的较成熟的插件方案有DL和DroidPlugin;但是DL方案仅仅对Frameworl的表层做了处理,严重依赖that
语法,编写插件代码和主程序代码需单独区分;而DroidPlugin通过Hook增强了Framework层的很多系统服务,开发插件就跟开发独立app差不多;就拿Activity生命周期的管理来说,DL的代理方式就像是牵线木偶,插件只不过是操纵傀儡而已;而DroidPlugin则是借尸还魂,插件是有血有肉的系统管理的真正组件;DroidPlugin Hook了系统几乎所有的Sevice,欺骗了大部分的系统API;掌握这个Hook过程需要掌握很多系统原理,因此学习DroidPlugin对于整个Android FrameWork层大有裨益。
接下来的一系列文章将以DroidPlugin为例讲解插件框架的原理,揭开插件化的神秘面纱;同时还能帮助深入理解Android Framewrok;主要内容如下:
需要学习的一些类:
1.ActivityThread
2.Instrumentation
3.ActivityManager
4.ActivityManagerServiche
5.PackageManagerServic
6.ServiceManager
7.ActivityManagerNative
8.ApplicationThread
如果你不清楚ActivityManager
,ActivityManagerService
以及ActivityManagerNative
之间的关系;建议先仔细阅读我之前关于Binder的文章 Binder学习指南。
流程
1.Activity的启动流程
2.service的启动加载
CLassLoader机制,JAVA虚拟机的原理!
public static void attachContext() throws Exception{ // 先获取到当前的ActivityThread对象 Class> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); currentActivityThreadMethod.setAccessible(true); //currentActivityThread是一个static函数所以可以直接invoke,不需要带实例参数 Object currentActivityThread = currentActivityThreadMethod.invoke(null); // 拿到原始的 mInstrumentation字段 Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation"); mInstrumentationField.setAccessible(true); Instrumentation mInstrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread); // 创建代理对象 Instrumentation evilInstrumentation = new EvilInstrumentation(mInstrumentation); // 偷梁换柱 mInstrumentationField.set(currentActivityThread, evilInstrumentation); }
mInstrumentation
这个字段为我们的代理对象,我们先实现这个代理对象,
Instrumentation
是一个类,没办法,我们只有手动写静态代理类,覆盖掉原始的方法即可
public class EvilInstrumentation extends Instrumentation { private static final String TAG = "EvilInstrumentation"; // ActivityThread中原始的对象, 保存起来 Instrumentation mBase; public EvilInstrumentation(Instrumentation base) { mBase = base; } public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { // Hook之前, XXX到此一游! Log.d(TAG, "\n执行了startActivity, 参数如下: \n" + "who = [" + who + "], " + "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " + "\ntarget = [" + target + "], \nintent = [" + intent + "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]"); // 开始调用原始的方法, 调不调用随你,但是不调用的话, 所有的startActivity都失效了. // 由于这个方法是隐藏的,因此需要使用反射调用;首先找到这个方法 try { Method execStartActivity = Instrumentation.class.getDeclaredMethod( "execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class); execStartActivity.setAccessible(true); return (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, target, intent, requestCode, options); } catch (Exception e) { // 某该死的rom修改了 需要手动适配 throw new RuntimeException("do not support!!! pls adapt it"); } }
// 招代理 women = (Shopping) Proxy.newProxyInstance(Shopping.class.getClassLoader(), women.getClass().getInterfaces(), new ShoppingHandler(women));
Log.d(TAG, "\n执行了startActivity, 参数如下: \n" + "who = [" + who + "], " + "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " + "\ntarget = [" + target + "], \nintent = [" + intent + "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");
ActivityManager activityManager=(ActivityManager)this.getSystemService(Context.ACTIVITY_SERVICE);
@Override public Object getSystemService(String name) { return SystemServiceRegistry.getSystemService(this, name); }
public static Object getSystemService(ContextImpl ctx, String name) { ServiceFetcher> fetcher = SYSTEM_SERVICE_FETCHERS.get(name); return fetcher != null ? fetcher.getService(ctx) : null; }
tatic { registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class, new CachedServiceFetcher() { @Override public AccessibilityManager createService(ContextImpl ctx) { return AccessibilityManager.getInstance(ctx); }}); registerService(Context.CAPTIONING_SERVICE, CaptioningManager.class, new CachedServiceFetcher () { @Override public CaptioningManager createService(ContextImpl ctx) { return new CaptioningManager(ctx); }});
registerService(Context.POWER_SERVICE, PowerManager.class, new CachedServiceFetcher() { @Override public PowerManager createService(ContextImpl ctx) { IBinder b = ServiceManager.getService(Context.POWER_SERVICE); IPowerManager service = IPowerManager.Stub.asInterface(b); if (service == null) { Log.wtf(TAG, "Failed to get power manager service."); } return new PowerManager(ctx.getOuterContext(), service, ctx.mMainThread.getHandler()); }});
IBinder b = ServiceManager.getService("service_name"); // 获取原始的IBinder对象 IXXInterface in = IXXInterface.Stub.asInterface(b); // 转换为Service接口
public class BinderHookHelper { public static void hookClipboardService() throws Exception { final String CLIPBOARD_SERVICE = "clipboard"; // 下面这一段的意思实际就是: ServiceManager.getService("clipboard"); // 只不过 ServiceManager这个类是@hide的 Class> serviceManager = Class.forName("android.os.ServiceManager"); Method getService = serviceManager.getDeclaredMethod("getService", String.class); // ServiceManager里面管理的原始的Clipboard Binder对象 // 一般来说这是一个Binder代理对象 IBinder rawBinder = (IBinder) getService.invoke(null, CLIPBOARD_SERVICE); // Hook 掉这个Binder代理对象的 queryLocalInterface 方法 // 然后在 queryLocalInterface 返回一个IInterface对象, hook掉我们感兴趣的方法即可. IBinder hookedBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(), new Class>[] { IBinder.class }, new BinderProxyHookHandler(rawBinder)); // 把这个hook过的Binder代理对象放进ServiceManager的cache里面 // 以后查询的时候 会优先查询缓存里面的Binder, 这样就会使用被我们修改过的Binder了 Field cacheField = serviceManager.getDeclaredField("sCache"); cacheField.setAccessible(true); Map, IBinder> cache = (Map) cacheField.get(null); cache.put(CLIPBOARD_SERVICE, hookedBinder); }
public class BinderProxyHookHandler implements InvocationHandler { private static final String TAG = "BinderProxyHookHandler"; // 绝大部分情况下,这是一个BinderProxy对象 // 只有当Service和我们在同一个进程的时候才是Binder本地对象 // 这个基本不可能 IBinder base; Class> stub; Class> iinterface; public BinderProxyHookHandler(IBinder base) { this.base = base; try { this.stub = Class.forName("android.content.IClipboard$Stub"); this.iinterface = Class.forName("android.content.IClipboard"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if ("queryLocalInterface".equals(method.getName())) { Log.d(TAG, "hook queryLocalInterface"); // 这里直接返回真正被Hook掉的Service接口 // 这里的 queryLocalInterface 就不是原本的意思了 // 我们肯定不会真的返回一个本地接口, 因为我们接管了 asInterface方法的作用 // 因此必须是一个完整的 asInterface 过的 IInterface对象, 既要处理本地对象,也要处理代理对象 // 这只是一个Hook点而已, 它原始的含义已经被我们重定义了; 因为我们会永远确保这个方法不返回null // 让 IClipboard.Stub.asInterface 永远走到if语句的else分支里面 return Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
public class BinderHookHandler implements InvocationHandler { private static final String TAG = "BinderHookHandler"; // 原始的Service对象 (IInterface) Object base; public BinderHookHandler(IBinder base, Class> stubClass) { try { Method asInterfaceMethod = stubClass.getDeclaredMethod("asInterface", IBinder.class); // IClipboard.Stub.asInterface(base); this.base = asInterfaceMethod.invoke(null, base); } catch (Exception e) { throw new RuntimeException("hooked failed!"); } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 把剪切版的内容替换为 "you are hooked" if ("getPrimaryClip".equals(method.getName())) { Log.d(TAG, "hook getPrimaryClip"); return ClipData.newPlainText(null, "you are hooked"); } // 欺骗系统,使之认为剪切版上一直有内容 if ("hasPrimaryClip".equals(method.getName())) { return true; } return method.invoke(base, args);
AMS
正如名字所说,管理所有的“活动”,整个系统的Activity堆栈,Activity生命周期回调都是由AMS所在的系统进程system_server帮开发者完成的;Android的Framework层帮忙完成了诸如生命周期管理等
AMS
与ActivityThread
之间对于Activity的生命周期的交互,并没有直接使用Activity对象进行交互,而是使用一个token来标识,
这个token是binder对象,因此可以方便地跨进程传递。Activity里面有一个成员变量mToken
代表的就是它,token可以唯一地标识一个Activity对象,它在Activity的attach
方法里面初始化;
在AMS
处理Activity的任务栈的时候,使用这个token标记Activity
hook,又叫钩子,通常是指对一些方法进行拦截
大致思路:
1.找到需要Hook方法的系统类
2.利用代理模式来代理系统类的运行拦截我们需要拦截的方法
3.使用反射的方法把这个系统类替换成你的代理类