摘要:什么樣的對象容易找到靜態變量和單例。在一個進程之內,靜態變量和單例變量是相對不容易發生變化的,因此非常容易定位,而普通的對象則要么無法標志,要么容易改變。
前言
為了實現 App 的快速迭代更新,基于 H5 Hybrid 的解決方案有很多,由于 webview 本身的性能問題,也隨之出現了很多基于 JS 引擎實現的原生渲染的方案,例如 React Native、weex 等,而國內一線大廠基本上主要還是 Android 插件化解決大部分的更新問題,對于部分是采用 webview 或者 React Native 這種方案,而對于 Android 插件化采用的技術對于 Android Framewrok 的理解要求很高,真正實現落地的方案都還是有難度,對于非 Android Native 開發的人員更是有技術門檻。插件化可以很好的解決 Android 運行的一些問題,本文站在學習者的角度去嘗試理解插件化到底解決了什么問題。
插件化框架如下是主流的插件化框架之間的對比:
特性 | DynamicLoadApk | DynamicAPK | Small | DroidPlugin | VirtualAPK |
---|---|---|---|---|---|
支持四大組件 | 只支持 Activity | 只支持 Activity | 只支持 Activity | 全支持 | 全支持 |
無需在宿主 manifest 中預注冊 | √ | × | √ | √ | √ |
插件可以依賴宿主 | √ | √ | √ | × | √ |
支持 PendingIntent | × | × | × | √ | √ |
Android 特性支持 | 大部分 | 大部分 | 大部分 | 幾乎全部 | 幾乎全部 |
兼容性適配 | 一般 | 一般 | 中等 | 高 | 高 |
插件構建 | 無 | 部署 aapt | Gradle 插件 | 無 | Gradle 插件 |
代理模式是為一個對象提供一個代用品或占位符,以便控制對它的訪問。使用代理可以屏蔽內部實現細節,后續內部有變動對于外部調用者來說是封閉的,符合開放-封閉原則。用戶可以放心地請求代理,他只關心是否能得到想要的結果。在任何使用本體的地方都可以替換成使用代理,從而實現實現和調用松耦合。
不用代理模式:
使用代理模式:
靜態代理例如我們有兩個接口:
// 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 對象的操作,如下:
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 按照這種方式我們仍然需要建立一個類去處理,這也是靜態代理的局限性。如果寫一個代理類就能對上面兩個都能代理就好了,動態代理就解決了這個問題。
動態代理在 java 的動態代理機制中,有兩個重要的類或接口,一個是 InvocationHandler(Interface)、另一個則是 Proxy(Class),這一個類和接口是實現我們動態代理所必須用到的。
動態代理的步驟:
寫一個 InvocationHandler 的實現類,并實現 invoke 方法,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 方法來進行調用。
使用 Proxy 類的 newProxyInstance 方法生成一個代理對象。例如: 生成 Subject1 的代理對象,注意第三個參數中要將一個實體對象傳入。
/** * @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。下面我們 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= 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 大神的文章在學習過程記錄的內容。
Activity 啟動過程上述例子中我們只是完成了一個最基礎的 Hook 功能,然而大部分插件化框架提供了十分豐富的功能,例如:插件化支持首先要解決的一點就是插件里的 Activity 并未在宿主程序的 AndroidMainfest.xml 注冊。常規方法肯定無法直接啟動插件的 Activity,這個時候就需要去了解 Activity 的啟動流程。
完整的流程如下:
注: 可以在 http://androidxref.com/ 在線查看 Android 源碼。
上圖列出的是啟動一個 Activity 的主要過程,具體步驟如下:
Activity 調用 startActivity,實際會調用 Instrumentation 類的 execStartActivity 方法,Instrumentation 是系統用來監控 Activity 運行的一個類,Activity 的整個生命周期都有它的影子。
通過跨進程的 Binder 調用,進入到 ActivityManagerService 中,其內部會處理 Activity 棧。之后又通過跨進程調用進入到需要調用的 Activity 所在的進程中。
ApplicationThread 是一個 Binder 對象,其運行在 Binder 線程池中,內部包含一個 H 類,該類繼承于類 Handler。ApplicationThread 將啟動需要調用的 Activity 的信息通過 H 對象發送給主線程。
主線程拿到需要調用的 Activity 的信息后,調用 Instrumentation 類的 newActivity 方法,其內通過 ClassLoader 創建 Activity 實例。
下面介紹如何通過 hook 的方式啟動插件中的 Activity,需要解決以下兩個問題:
插件中的 Activity 沒有在 AndroidManifest 中注冊,如何繞過檢測。
如何構造 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
參考Android 博客周刊專題之#插件化開發#
VirtualAPK Wiki
DroidPlugin Wiki
understand-plugin-framework
Android 插件化原理解析——Hook 機制之動態代理
Android 源碼分析-Activity 的啟動過程
APP 的啟動過程
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71647.html
閱讀 3350·2021-11-04 16:10
閱讀 3846·2021-09-29 09:43
閱讀 2692·2021-09-24 10:24
閱讀 3337·2021-09-01 10:46
閱讀 2503·2019-08-30 15:54
閱讀 585·2019-08-30 13:19
閱讀 3232·2019-08-29 17:19
閱讀 1049·2019-08-29 16:40