为了实现 App 的快速迭代更新,基于 H5 Hybrid 的解决方案有不少,因为 webview 自己的性能问题,也随之出现了不少基于 JS 引擎实现的原生渲染的方案,例如 React Native、weex 等,而国内一线大厂基本上主要仍是 Android 插件化解决大部分的更新问题,对于部分是采用 webview 或者 React Native 这种方案,而对于 Android 插件化采用的技术对于 Android Framewrok 的理解要求很高,真正实现落地的方案都仍是有难度,对于非 Android Native 开发的人员更是有技术门槛。插件化能够很好的解决 Android 运行的一些问题,本文站在学习者的角度去尝试理解插件化到底解决了什么问题。php
以下是主流的插件化框架之间的对比:java
特性 | DynamicLoadApk | DynamicAPK | Small | DroidPlugin | VirtualAPK |
---|---|---|---|---|---|
支持四大组件 | 只支持 Activity | 只支持 Activity | 只支持 Activity | 全支持 | 全支持 |
无需在宿主 manifest 中预注册 | √ | × | √ | √ | √ |
插件能够依赖宿主 | √ | √ | √ | × | √ |
支持 PendingIntent | × | × | × | √ | √ |
Android 特性支持 | 大部分 | 大部分 | 大部分 | 几乎所有 | 几乎所有 |
兼容性适配 | 通常 | 通常 | 中等 | 高 | 高 |
插件构建 | 无 | 部署 aapt | Gradle 插件 | 无 | Gradle 插件 |
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。使用代理能够屏蔽内部实现细节,后续内部有变更对于外部调用者来讲是封闭的,符合开放-封闭原则。用户能够放心地请求代理,他只关心是否能获得想要的结果。在任何使用本体的地方均可以替换成使用代理,从而实现实现和调用松耦合。android
不用代理模式:git
使用代理模式:github
例如咱们有两个接口:web
// Subject1.java public interface Subject1 { void method1(); void method2(); } // Subject2.java public interface Subject2 { void method1(); void method2(); void method3(); }
咱们分别实现这两个接口:编程
// RealSubject1.java public class RealSubject1 implements Subject1 { @Override public void method1() { Logger.i(RealSubject1.class, "我是RealSubject1的方法1"); } @Override public void method2() { Logger.i(RealSubject1.class, "我是RealSubject1的方法2"); } } // RealSubject2.java public class RealSubject2 implements Subject2 { @Override public void method1() { Logger.i(RealSubject2.class, "我是RealSubject2的方法1"); } @Override public void method2() { Logger.i(RealSubject2.class, "我是RealSubject2的方法2"); } @Override public void method3() { Logger.i(RealSubject2.class, "我是RealSubject2的方法3"); } }
若是不使用代理模式,咱们通常会直接实例化 RealSubject1 和 RealSubject2 类对象。使用代理,咱们通常都须要创建一个代理类。在 Java 等语言中,代理和本体都须要显式地实现同一个接口,一方面接口保证了它们会拥 有一样的方法,另外一方面,面向接口编程迎合依赖倒置原则,经过接口进行向上转型,从而避开 编译器的类型检查,代理和本体未来能够被替换使用。数组
/** * 静态代理类(为了保持行为的一致性,代理类和委托类一般会实现相同的接口) * ProxySubject1.java */ public class ProxySubject1 implements Subject1 { private Subject1 subject1; public ProxySubject1(Subject1 subject1) { this.subject1 = subject1; } @Override public void method1() { Logger.i(ProxySubject1.class, "我是代理,我会在执行实体方法1以前先作一些预处理的工做"); subject1.method1(); } @Override public void method2() { Logger.i(ProxySubject1.class, "我是代理,我会在执行实体方法2以前先作一些预处理的工做"); subject1.method2(); } }
使用代理后咱们对 RealSubject1 的操做换成对 ProxySubject1 对象的操做,以下:weex
ProxySubject1 proxySubject1 = new ProxySubject1(new RealSubject1()); proxySubject1.method1(); proxySubject1.method2(); 结果: [ProxySubject1] : 我是代理,我会在执行实体方法1以前先作一些预处理的工做 [RealSubject1] : 我是RealSubject1的方法1 [ProxySubject1] : 我是代理,我会在执行实体方法2以前先作一些预处理的工做 [RealSubject1] : 我是RealSubject1的方法2 [ProxySubject2] : 我是代理,我会在执行实体方法1以前先作一些预处理的工做 [RealSubject2] : 我是RealSubject2的方法1 [ProxySubject2] : 我是代理,我会在执行实体方法2以前先作一些预处理的工做 [RealSubject2] : 我是RealSubject2的方法2
显然当咱们想代理 RealSubject2 按照这种方式咱们仍然须要创建一个类去处理,这也是静态代理的局限性。若是写一个代理类就能对上面两个都能代理就行了,动态代理就解决了这个问题。app
在 java 的动态代理机制中,有两个重要的类或接口,一个是 InvocationHandler(Interface)、另外一个则是 Proxy(Class),这一个类和接口是实现咱们动态代理所必须用到的。
动态代理的步骤:
return method.invoke(...);
。/** * @param proxy 指代咱们所代理的那个真实对象 * @param method 指代的是咱们所要调用真实对象的某个方法的Method对象 * @param args 指代的是调用真实对象某个方法时接受的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
每个动态代理类都必需要实现 InvocationHandler 这个接口,而且每一个代理类的实例都关联到了一个 handler,当咱们经过代理对象调用一个方法的时候,这个方法的调用就会被转发为由 InvocationHandler 这个接口的 invoke 方法来进行调用。
/** * @param loader 一个ClassLoader对象,定义了由哪一个ClassLoader对象来对生成的代理对象进行加载 * @param interfaces 一个Interface对象的数组,表示的是我将要给我须要代理的对象提供一组什么接口,若是我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了 * @param h 一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪个InvocationHandler对象上 */ public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
例如:
Proxy.newProxyInstance( Subject1.class.getClassLoader(), new Class[] {Subject1.class}, new DynamicProxyHandler(new RealSubject1()) );
Proxy 这个类的做用就是用来动态建立一个代理对象的类,它提供了许多的方法,可是咱们用的最多的就是 newProxyInstance 这个方法。
使用动态代理完成上述静态代理中的功能:
public class DynamicProxyHandler implements InvocationHandler { private Object object; public DynamicProxyHandler(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Logger.i(DynamicProxyHandler.class, "我正在动态代理[" + object.getClass().getSimpleName() + "]的[" + method.getName() + "]方法"); return method.invoke(object, args); } /** * 调用Proxy.newProxyInstance便可生成一个代理对象 * * @param object * @return */ public static Object newProxyInstance(Object object) { // 传入被代理对象的classloader实现的接口, 还有DynamicProxyHandler的对象便可。 return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new DynamicProxyHandler(object)); } }
动态代理调用以下:
Subject1 dynamicProxyHandler1 = (Subject1) DynamicProxyHandler.newProxyInstance(new RealSubject1()); dynamicProxyHandler1.method1(); dynamicProxyHandler1.method2();
上述咱们对一个方法的调用采用了动态代理的办法,若是咱们本身建立代理对象,而后把原始对象替换为咱们的代理对象,那么就能够在这个代理对象随心所欲了,修改参数,替换返回值,咱们称之为 Hook。下面咱们 Hook 掉 startActivity 这个方法,使得每次调用这个方法以前输出一条日志;固然,这个输入日志有点点弱,只是为了展现原理;只要你想,你想能够替换参数,拦截这个 startActivity 过程,使得调用它致使启动某个别的 Activity,混淆是非!
首先咱们得找到被 Hook 的对象,我称之为 Hook 点;什么样的对象比较好 Hook 呢?天然是容易找到的对象。什么样的对象容易找到?静态变量和单例。在一个进程以内,静态变量和单例变量是相对不容易发生变化的,所以很是容易定位,而普通的对象则要么没法标志,要么容易改变。咱们根据这个原则找到所谓的 Hook 点。
对于 startActivity 过程有两种方式:Context.startActivity 和 Activity.startActivity。这里暂不分析其中的区别,以 Activity.startActivity 为例说明整个过程的调用栈。
Activity 中的 startActivity 最终都是由 startActivityForResult 来实现的。
Activity#startActivityForResult:
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) { // 通常的 Activity 其 mParent 为 null,mParent 经常使用在 ActivityGroup 中,ActivityGroup 已废弃 if (mParent == null) { options = transferSpringboardActivityOptions(options); // 这里会启动新的Activity,核心功能都在 mMainThread.getApplicationThread() 中完成 Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity( this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); if (ar != null) { mMainThread.sendActivityResult( mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData()); } if (requestCode >= 0) { mStartedActivity = true; } cancelInputsAndStartExitTransition(options); } else { if (options != null) { mParent.startActivityFromChild(this, intent, requestCode, options); } else { // Note we want to go through this method for compatibility with // existing applications that may have overridden it. mParent.startActivityFromChild(this, intent, requestCode); } } }
能够发现,真正打开 activity 的实如今 Instrumentation 的 execStartActivity 方法中。
Instrumentation#execStartActivity:
public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { // 核心功能在这个whoThread中完成,其内部scheduleLaunchActivity方法用于完成activity的打开 IApplicationThread whoThread = (IApplicationThread) contextThread; Uri referrer = target != null ? target.onProvideReferrer() : null; if (referrer != null) { intent.putExtra(Intent.EXTRA_REFERRER, referrer); } if (mActivityMonitors != null) { synchronized (mSync) { final int N = mActivityMonitors.size(); for (int i=0; i<N; i++) { final ActivityMonitor am = mActivityMonitors.get(i); ActivityResult result = null; if (am.ignoreMatchingSpecificIntents()) { result = am.onStartActivity(intent); } if (result != null) { am.mHits++; return result; } else if (am.match(who, null, intent)) { am.mHits++; if (am.isBlocking()) { return requestCode >= 0 ? am.getResult() : null; } break; } } } } try { intent.migrateExtraStreamToClipData(); intent.prepareToLeaveProcess(who); // 这里才是真正打开 Activity 的地方,核心功能在 whoThread 中完成。 int result = ActivityManager.getService() .startActivity(whoThread, who.getBasePackageName(), intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); // 这个方法是专门抛异常的,它会对结果进行检查,若是没法打开activity, // 则抛出诸如ActivityNotFoundException相似的各类异常 checkStartActivityResult(result, intent); } catch (RemoteException e) { throw new RuntimeException("Failure from system", e); } return null; }
若是咱们想深刻了解 Activity 启动过程咱们须要接着 Android 源码看下去,可是对于本文中咱们初步了解 Hook 机制足以。
咱们的目的是替换掉系统默认逻辑,对于 Activity#startActivityForResult 的方法里面核心逻辑就是 mInstrumentation 属性的 execStartActivity 方法,而这里的 mInstrumentation 属性在 Activity 类中刚好是一个单例,在 Activity 类的 attach 方法里面被赋值,咱们能够在 attach 以后使用反射机制对 mInstrumentation 属性进行从新赋值。attach() 方法调用完成后,就天然而然的调用了 Activity 的 onCreate() 方法了。
咱们须要修改 mInstrumentation 这个字段为咱们的代理对象,咱们使用静态代理实现这个代理对象。这里咱们使用 EvilInstrumentation 做为代理对象。
public class EvilInstrumentation extends Instrumentation { private Instrumentation instrumentation; public EvilInstrumentation(Instrumentation instrumentation) { this.instrumentation = instrumentation; } public ActivityResult execStartActivity( Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { StringBuilder sb = new StringBuilder(); sb.append("who = [").append(who).append("], ") .append("contextThread = [").append(contextThread).append("], ") .append("token = [").append(token).append("], ") .append("target = [").append(target).append("], ") .append("intent = [").append(intent).append("], ") .append("requestCode = [").append(requestCode).append("], ") .append("options = [").append(options).append("]");; Logger.i(EvilInstrumentation.class, "执行了startActivity, 参数以下: " + sb.toString()); try { Method execStartActivity = Instrumentation.class.getDeclaredMethod( "execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class); return (ActivityResult) execStartActivity.invoke(instrumentation, who, contextThread, token, target, intent, requestCode, options); } catch (Exception e) { e.printStackTrace(); } return null; } }
采用反射直接修改 Activity 中的 mInstrumentation 属性,从而实现偷梁换柱——用代理对象替换原始对象。
// 拿到原始的 mInstrumentation字段 Field mInstrumentationField = Activity.class.getDeclaredField("mInstrumentation"); mInstrumentationField.setAccessible(true); // 建立代理对象 Instrumentation originalInstrumentation = (Instrumentation) mInstrumentationField.get(activity); mInstrumentationField.set(activity, new EvilInstrumentation(originalInstrumentation));
这段 Hook 的逻辑放在 Activity 的 onCreate 里面便可生效。
对于 Context 类的 startActivity 方法的 Hook 实现能够参考 weishu 大神的 Android 插件化原理解析——Hook 机制之动态代理,本文也是基于 weishu 大神的文章在学习过程记录的内容。
上述例子中咱们只是完成了一个最基础的 Hook 功能,然而大部分插件化框架提供了十分丰富的功能,例如:插件化支持首先要解决的一点就是插件里的 Activity 并未在宿主程序的 AndroidMainfest.xml 注册。常规方法确定没法直接启动插件的 Activity,这个时候就须要去了解 Activity 的启动流程。
完整的流程以下:
注: 能够在 http://androidxref.com/ 在线查看 Android 源码。
上图列出的是启动一个 Activity 的主要过程,具体步骤以下:
下面介绍如何经过 hook 的方式启动插件中的 Activity,须要解决如下两个问题:
咱们这里使用最简单的一种实现方式:先在 Manifest 中预埋 StubActivity,启动时 hook 上图第 1 步,将 Intent 替换成 StubActivity。
// StubActivity.java public class StubActivity extends Activity { public static final String TARGET_COMPONENT = "TARGET_COMPONENT"; }
咱们上面在 EvilInstrumentation 类里面实现了 execStartActivity 方法,如今咱们在这里再加一些额外的逻辑。
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { StringBuilder sb = new StringBuilder(); sb.append("who = [").append(who).append("], ") .append("contextThread = [").append(contextThread).append("], ") .append("token = [").append(token).append("], ") .append("target = [").append(target).append("], ") .append("intent = [").append(intent).append("], ") .append("requestCode = [").append(requestCode).append("], ") .append("options = [").append(options).append("]");; Logger.i(EvilInstrumentation.class, "执行了startActivity, 参数以下: " + sb.toString()); // 在此处先将 intent 本来的 Component 保存起来, 而后建立一个新的 intent。 // 使用 StubActivity 并替换掉本来的 Activity, 以达经过 AMS 验证的目的,而后等 AMS 验证经过后再将其还原。 Intent replaceIntent = new Intent(target, StubActivity.class); replaceIntent.putExtra(StubActivity.TARGET_COMPONENT, intent); intent = replaceIntent; try { Method execStartActivity = Instrumentation.class.getDeclaredMethod( "execStartActivity", Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class); return (ActivityResult) execStartActivity.invoke(instrumentation, who, contextThread, token, target, intent, requestCode, options); } catch (Exception e) { e.printStackTrace(); } return null; }
经过这种"移花接木"的方式绕过 AMS 验证,可是这里咱们并无完成对咱们本来须要真正打开的 Activity 的建立。这里咱们须要监听 Activity 的建立过程,而后在适当的适合将本来须要打开的 Activity 还原回来。
在 ActivityThread 类中有一个重要的消息处理的方法 sendMessage。
2644 private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) { 2645 if (DEBUG_MESSAGES) Slog.v( 2646 TAG, "SCHEDULE " + what + " " + mH.codeToString(what) 2647 + ": " + arg1 + " / " + obj); 2648 Message msg = Message.obtain(); 2649 msg.what = what; 2650 msg.obj = obj; 2651 msg.arg1 = arg1; 2652 msg.arg2 = arg2; 2653 if (async) { 2654 msg.setAsynchronous(true); 2655 } 2656 mH.sendMessage(msg); 2657 }
最终都会落实到 mH.sendMessage(msg);
的调用,继续追踪这个 mH 对象,咱们会发现是 H 对象的实例化对象。
final H mH = new H();
private class H extends Handler { public void handleMessage(Message msg) { 1585 if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what)); 1586 switch (msg.what) { 1587 case LAUNCH_ACTIVITY: { 1588 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart"); 1589 final ActivityClientRecord r = (ActivityClientRecord) msg.obj; 1590 1591 r.packageInfo = getPackageInfoNoCheck( 1592 r.activityInfo.applicationInfo, r.compatInfo); 1593 handleLaunchActivity(r, null, "LAUNCH_ACTIVITY"); 1594 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); 1595 } break; ... } }
咱们知道 Handler 消息机制用于同进程的线程间通讯, Handler 是工做线程向 UI 主线程发送消息,工做线程经过 mHandler 向其成员变量 MessageQueue 中添加新 Message,主线程一直处于 loop() 方法内,当收到新的 Message 时按照必定规则分发给相应的 handleMessage() 方法来处理。
相似于对上述 mInstrumentation 实例化对象 hook 同样,这里咱们能够对 mH 对象进行 hook。
/** * 将替换的activity在此时还原回来 */ public static void doHandlerHook() { try { Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Method currentActivityThread = activityThreadClass.getDeclaredMethod("currentActivityThread"); Object activityThread = currentActivityThread.invoke(null); Field mHField = activityThreadClass.getDeclaredField("mH"); mHField.setAccessible(true); Handler mH = (Handler) mHField.get(activityThread); Field mCallbackField = Handler.class.getDeclaredField("mCallback"); mCallbackField.setAccessible(true); mCallbackField.set(mH, new ActivityThreadHandlerCallback(mH)); } catch (Exception e) { e.printStackTrace(); } }
对于 Handler.Callback 的 hook 实现以下:
public class ActivityThreadHandlerCallback implements Handler.Callback { private Handler mBaseHandler; public ActivityThreadHandlerCallback(Handler mBaseHandler) { this.mBaseHandler = mBaseHandler; } @Override public boolean handleMessage(Message msg) { Logger.i(ActivityThreadHandlerCallback.class, "接受到消息了msg:" + msg); if (msg.what == 100) { try { Object obj = msg.obj; Field intentField = obj.getClass().getDeclaredField("intent"); intentField.setAccessible(true); Intent intent = (Intent) intentField.get(obj); Intent targetIntent = intent.getParcelableExtra(StubActivity.TARGET_COMPONENT); intent.setComponent(targetIntent.getComponent()); Log.e("intentField", targetIntent.toString()); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } mBaseHandler.handleMessage(msg); return true; } }
咱们设置在 handleMessage 里面还原咱们最开始替换的 Activity,至此咱们就实现了对于 startActivity 的完整 hook,可是这个过程当中仍然存在不少问题,咱们须要进一步去深刻探索才能去理解和更好实现插件化框架的内容。
本文学习案例地址:android-plugin-framework