日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )

發布時間:2025/6/17 Android 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 ) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Android 插件化系列文章目錄

【Android 插件化】插件化簡介 ( 組件化與插件化 )
【Android 插件化】插件化原理 ( JVM 內存數據 | 類加載流程 )
【Android 插件化】插件化原理 ( 類加載器 )

【Android 插件化】“ 插樁式 “ 插件化框架 ( 原理與實現思路 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 類加載器創建 | 資源加載 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 注入上下文的使用 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 獲取插件入口 Activity 組件 | 加載插件 Resources 資源 )
【Android 插件化】“ 插樁式 “ 插件化框架 ( 運行應用 | 代碼整理 )

【Android 插件化】Hook 插件化框架 ( Hook 技術 | 代理模式 | 靜態代理 | 動態代理 )
【Android 插件化】Hook 插件化框架 ( Hook 實現思路 | Hook 按鈕點擊事件 )
【Android 插件化】Hook 插件化框架 ( Hook Activity 啟動過程 | 靜態代理 )


文章目錄

  • Android 插件化系列文章目錄
  • 前言
  • 一、分析 Activity 啟動源碼
    • 1、源碼分析
    • 2、涉及到的 Activity 相關代碼
  • 二、Hook Activity 啟動過程
    • 1、分析相關 類 / 成員 / 方法 結構
    • 2、反射獲取 Activity 中的 Instrumentation mInstrumentation 成員字段
    • 3、獲取 Activity 中的 Instrumentation mInstrumentation 成員字段值
    • 4、設置 Activity 中的 Instrumentation mInstrumentation 成員字段值
    • 5、代理類開發
  • 三、完整代碼示例
    • 1、主界面代碼示例
    • 2、代理類代碼示例
    • 3、跳轉的界面
    • 4、執行結果
  • 四、博客資源


前言

上一篇博客 【Android 插件化】Hook 插件化框架 ( Hook 實現思路 | Hook 按鈕點擊事件 ) 簡要介紹了 Hook 實現思路 , 以及使用靜態代理實現了 Hook 按鈕點擊事件 ;

在本博客中使用 Hook 技術進行 Hook 住 Activity 啟動過程 ;






一、分析 Activity 啟動源碼




1、源碼分析


在 " 宿主 " 模塊中 , 啟動 " 插件 " 模塊 , 調用的是 startActivity 方法 ;

如果要 Hook Activity 的啟動過程 , 必須熟悉啟動 Activity 的源碼 , 下面開始分析 調用 startActivity 方法的源碼邏輯 ;


在 Activity 中啟動另外一個 Activity , 調用 void startActivity(Intent intent) 方法 ,

@Overridepublic void startActivity(Intent intent) {this.startActivity(intent, null);}

在該 void startActivity(Intent intent) 方法中主要調用 void startActivity(Intent intent, @Nullable Bundle options) 重載方法 ;

@Overridepublic void startActivity(Intent intent, @Nullable Bundle options) {if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) {if (TextUtils.equals(getPackageName(),intent.resolveActivity(getPackageManager()).getPackageName())) {// Apply Autofill restore mechanism on the started activity by startActivity()final IBinder token =mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);// Remove restore ability from current activitymIntent.removeExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);mIntent.removeExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY);// Put restore tokenintent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);intent.putExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY, true);}}if (options != null) {startActivityForResult(intent, -1, options);} else {// Note we want to go through this call for compatibility with// applications that may have overridden the method.startActivityForResult(intent, -1);}}

在 void startActivity(Intent intent, @Nullable Bundle options) 中 , 最終調用了 void startActivityForResult(@RequiresPermission Intent intent, int requestCode) 方法 啟動 Activity ;

void startActivityForResult(@RequiresPermission Intent intent, int requestCode) 方法最終也是調用 void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) 重載方法 , 最后一個參數設置為 null ;

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {startActivityForResult(intent, requestCode, null);}public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {options = transferSpringboardActivityOptions(options);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) {// If this start is requesting a result, we can avoid making// the activity visible until the result is received. Setting// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the// activity hidden during this time, to avoid flickering.// This can only be done when a result is requested because// that guarantees we will get information back when the// activity is finished, no matter what happens to it.mStartedActivity = true;}cancelInputsAndStartExitTransition(options);// TODO Consider clearing/flushing other event sources and events for child windows.} 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);}}}

在 void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) 方法中 , 調用了 Instrumentation mInstrumentation 成員的 execStartActivity 方法 , 啟動 Activity ;

Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this,intent, requestCode, options);

通過 Hook 該 execStartActivity 方法 , 使用該方法啟動的 Activity 有完整的上下文環境 ;


2、涉及到的 Activity 相關代碼


Activity 相關完整代碼 :

public class Activity extends ContextThemeWrapperimplements LayoutInflater.Factory2,Window.Callback, KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks2,Window.OnWindowDismissedCallback,AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {@Overridepublic void startActivity(Intent intent) {this.startActivity(intent, null);}@Overridepublic void startActivity(Intent intent, @Nullable Bundle options) {if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) {if (TextUtils.equals(getPackageName(),intent.resolveActivity(getPackageManager()).getPackageName())) {// Apply Autofill restore mechanism on the started activity by startActivity()final IBinder token =mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);// Remove restore ability from current activitymIntent.removeExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN);mIntent.removeExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY);// Put restore tokenintent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);intent.putExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY, true);}}if (options != null) {startActivityForResult(intent, -1, options);} else {// Note we want to go through this call for compatibility with// applications that may have overridden the method.startActivityForResult(intent, -1);}}public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {startActivityForResult(intent, requestCode, null);}public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {if (mParent == null) {options = transferSpringboardActivityOptions(options);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) {// If this start is requesting a result, we can avoid making// the activity visible until the result is received. Setting// this code during onCreate(Bundle savedInstanceState) or onResume() will keep the// activity hidden during this time, to avoid flickering.// This can only be done when a result is requested because// that guarantees we will get information back when the// activity is finished, no matter what happens to it.mStartedActivity = true;}cancelInputsAndStartExitTransition(options);// TODO Consider clearing/flushing other event sources and events for child windows.} 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);}}} }



二、Hook Activity 啟動過程




1、分析相關 類 / 成員 / 方法 結構


要 Hook 的方法是 Instrumentation 的 execStartActivity 方法 ;

public class Instrumentation {@UnsupportedAppUsagepublic ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {} }

Activity 中維護了 Instrumentation mInstrumentation 成員變量 ;

public class Activity extends ContextThemeWrapperimplements LayoutInflater.Factory2,Window.Callback, KeyEvent.Callback,OnCreateContextMenuListener, ComponentCallbacks2,Window.OnWindowDismissedCallback,AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {// set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called.@UnsupportedAppUsageprivate Instrumentation mInstrumentation; }

2、反射獲取 Activity 中的 Instrumentation mInstrumentation 成員字段


首先 , 任何反射操作 , 都要獲取其字節碼文件 , 作為反射的入口 , 這里先獲取 Activity 字節碼對象 , 直接通過 Activity.class 獲取即可 ;

// 1. 獲取 Activity 字節碼文件// 字節碼文件是所有反射操作的入口Class<?> clazz = Activity.class;

然后 , 獲取 Activity 中的 Instrumentation mInstrumentation 成員 Field 字段 ;

// 2. 獲取 Activity 的 Instrumentation mInstrumentation 成員 Field 字段Field mInstrumentation_Field = null;try {mInstrumentation_Field = clazz.getDeclaredField("mInstrumentation");} catch (NoSuchFieldException e) {e.printStackTrace();}

最后 , 設置 Field mInstrumentation 字段的可訪問性 , 只要是調用反射方法 , 或者訪問反射的成員字段 , 第一件事就是設置可訪問性 ;

正常可訪問的方法或字段 , 絕對不會使用反射獲取 , 既然使用了反射 , 那么設置可訪問性是標配操作 ;

// 3. 設置 Field mInstrumentation 字段的可訪問性mInstrumentation_Field.setAccessible(true);

本步驟完整代碼示例 :

// 1. 獲取 Activity 字節碼文件// 字節碼文件是所有反射操作的入口Class<?> clazz = Activity.class;// 2. 獲取 Activity 的 Instrumentation mInstrumentation 成員 Field 字段Field mInstrumentation_Field = null;try {mInstrumentation_Field = clazz.getDeclaredField("mInstrumentation");} catch (NoSuchFieldException e) {e.printStackTrace();}// 3. 設置 Field mInstrumentation 字段的可訪問性mInstrumentation_Field.setAccessible(true);

3、獲取 Activity 中的 Instrumentation mInstrumentation 成員字段值


獲取 Activity 的 Instrumentation mInstrumentation 成員對象值 , 該成員值就是需要被代理的目標對象 ;

代理者 需要 持有 被代理的目標對象 ;

獲取該成員的意義是 , 創建 Instrumentation 代理時, 需要將原始的 Instrumentation 傳入代理對象中 ;

// 4. 獲取 Activity 的 Instrumentation mInstrumentation 成員對象值// 獲取該成員的意義是 , 創建 Instrumentation 代理時, 需要將原始的 Instrumentation 傳入代理對象中Instrumentation mInstrumentation = null;try {mInstrumentation = (Instrumentation) mInstrumentation_Field.get(this);} catch (IllegalAccessException e) {e.printStackTrace();}

4、設置 Activity 中的 Instrumentation mInstrumentation 成員字段值


設置 Activity 中的 Instrumentation mInstrumentation 成員字段值 , 將 Activity 的 Instrumentation mInstrumentation 成員變量 設置為自己定義的 Instrumentation 代理對象 ;

此處使用的是靜態代理 ;

// 5. 將 Activity 的 Instrumentation mInstrumentation 成員變量// 設置為自己定義的 Instrumentation 代理對象try {mInstrumentation_Field.set(this, new InstrumentationProxy(mInstrumentation));} catch (IllegalAccessException e) {e.printStackTrace();}

5、代理類開發


被代理的目標對象是 Activity 中的 Instrumentation mInstrumentation 成員變量 ;

代理類中需要持有上述成員變量 , 通過反射獲取該成員 , 并設置給代理者 ;

在代理類中 , 繼承 Instrumentation 類 , 這是因為還需要通過反射 , 將代理類設置給 Activity 的 Instrumentation mInstrumentation 成員 , 以達到偷梁換柱的目的 , 檔 Activity 調用 Instrumentation mInstrumentation 成員時 , 其實調用的是開發者開發的代理類 ;

在 Android 界面跳轉時 , 會自動回調 Activity 中的 Instrumentation mInstrumentation 成員的 execStartActivity 方法 ;

實際上調用的是代理類的 execStartActivity 方法 ;

在代理類 execStartActivity 方法中 , 首先調用持有的 Activity 中原本的 Instrumentation mInstrumentation 成員的 execStartActivity 方法 , 然后在該方法的前面 , 后面 , 可以添加自己的業務邏輯 , 該方法的執行參數也可以進行修改 ;

這樣就成功將自己的業務邏輯注入到了 Activity 啟動過程中 ;


代碼示例 :

package com.example.plugin_hook;import android.app.Activity; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.util.Log;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class InstrumentationProxy extends Instrumentation {private static final String TAG = "InstrumentationProxy";/*** Activity 中原本的 Instrumentation mInstrumentation 成員* 從構造函數中進行初始化*/final Instrumentation orginalInstrumentation;public InstrumentationProxy(Instrumentation orginalInstrumentation) {this.orginalInstrumentation = orginalInstrumentation;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {Log.i(TAG, "注入的 Hook 前執行的業務邏輯");// 1. 反射執行 Instrumentation orginalInstrumentation 成員的 execStartActivity 方法Method execStartActivity_Method = null;try {execStartActivity_Method = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);} catch (NoSuchMethodException e) {e.printStackTrace();}// 2. 設置方法可訪問性execStartActivity_Method.setAccessible(true);// 3. 執行 Instrumentation orginalInstrumentation 的 execStartActivity 方法// 使用 Object 類型對象接收反射方法執行結果ActivityResult activityResult = null;try {activityResult = (ActivityResult) execStartActivity_Method.invoke(orginalInstrumentation,who,contextThread,token,target,intent,requestCode,options);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}Log.i(TAG, "注入的 Hook 后執行的業務邏輯");return activityResult;} }



三、完整代碼示例




1、主界面代碼示例


主界面代碼示例 :

package com.example.plugin_hook;import androidx.appcompat.app.AppCompatActivity;import android.app.Activity; import android.app.Instrumentation; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button;import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 獲取按鈕 , 并未按鈕組件設置點擊事件Button button = findViewById(R.id.button);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Log.i(TAG, "Button OnClickListener onClick");startActivity(new Intent(MainActivity.this, MainActivity2.class));}});hookOnClick(button);hookStartActivity();}@Overridepublic void startActivity(Intent intent) {super.startActivity(intent);}/*** hook Button 組件的 getListenerInfo 方法* @param view*/private void hookOnClick(View view){// 獲取 View 的 getListenerInfo 方法Method getListenerInfo = null;try {getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");} catch (NoSuchMethodException e) {e.printStackTrace();}// 執行所有的反射方法 , 設置成員變量 之前 , 都要設置可見性getListenerInfo.setAccessible(true);// 執行 View view 對象的 getListenerInfo 方法Object mListenerInfo = null;try {mListenerInfo = getListenerInfo.invoke(view);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}// 反射獲取 OnClickListener 成員// ① 先根據全類名獲取 ListenerInfo 字節碼Class<?> clazz = null;try {clazz = Class.forName("android.view.View$ListenerInfo");} catch (ClassNotFoundException e) {e.printStackTrace();}// ② 獲取 android.view.View.ListenerInfo 中的 mOnClickListener 成員Field field = null;try {field = clazz.getField("mOnClickListener");} catch (NoSuchFieldException e) {e.printStackTrace();}// ③ 設置該字段訪問性, 執行所有的反射方法 , 設置成員變量 之前 , 都要設置可見性field.setAccessible(true);// ④ 獲取 mOnClickListener 成員變量View.OnClickListener mOnClickListener = null;try {mOnClickListener = (View.OnClickListener) field.get(mListenerInfo);} catch (IllegalAccessException e) {e.printStackTrace();}// ⑤ 修改 View 的 ListenerInfo 成員的 mOnClickListener 成員// 其中 ListenerInfo 成員 是try {View.OnClickListener finalMOnClickListener = mOnClickListener;field.set(mListenerInfo, new View.OnClickListener(){@Overridepublic void onClick(View v) {Log.i(TAG, "Hook Before");finalMOnClickListener.onClick(view);Log.i(TAG, "Hook After");}});} catch (IllegalAccessException e) {e.printStackTrace();}}/*** Hook Activity 界面啟動過程* 鉤住 Instrumentation 的 execStartActivity 方法* 向該方法中注入自定義的業務邏輯*/private void hookStartActivity(){// 1. 獲取 Activity 字節碼文件// 字節碼文件是所有反射操作的入口Class<?> clazz = Activity.class;// 2. 獲取 Activity 的 Instrumentation mInstrumentation 成員 Field 字段Field mInstrumentation_Field = null;try {mInstrumentation_Field = clazz.getDeclaredField("mInstrumentation");} catch (NoSuchFieldException e) {e.printStackTrace();}// 3. 設置 Field mInstrumentation 字段的可訪問性mInstrumentation_Field.setAccessible(true);// 4. 獲取 Activity 的 Instrumentation mInstrumentation 成員對象值// 獲取該成員的意義是 , 創建 Instrumentation 代理時, 需要將原始的 Instrumentation 傳入代理對象中Instrumentation mInstrumentation = null;try {mInstrumentation = (Instrumentation) mInstrumentation_Field.get(this);} catch (IllegalAccessException e) {e.printStackTrace();}// 5. 將 Activity 的 Instrumentation mInstrumentation 成員變量// 設置為自己定義的 Instrumentation 代理對象try {mInstrumentation_Field.set(this, new InstrumentationProxy(mInstrumentation));} catch (IllegalAccessException e) {e.printStackTrace();}}}

2、代理類代碼示例


代理類代碼示例 :

package com.example.plugin_hook;import android.app.Activity; import android.app.Instrumentation; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.util.Log;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;public class InstrumentationProxy extends Instrumentation {private static final String TAG = "InstrumentationProxy";/*** Activity 中原本的 Instrumentation mInstrumentation 成員* 從構造函數中進行初始化*/final Instrumentation orginalInstrumentation;public InstrumentationProxy(Instrumentation orginalInstrumentation) {this.orginalInstrumentation = orginalInstrumentation;}public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {Log.i(TAG, "注入的 Hook 前執行的業務邏輯");// 1. 反射執行 Instrumentation orginalInstrumentation 成員的 execStartActivity 方法Method execStartActivity_Method = null;try {execStartActivity_Method = Instrumentation.class.getDeclaredMethod("execStartActivity",Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);} catch (NoSuchMethodException e) {e.printStackTrace();}// 2. 設置方法可訪問性execStartActivity_Method.setAccessible(true);// 3. 執行 Instrumentation orginalInstrumentation 的 execStartActivity 方法// 使用 Object 類型對象接收反射方法執行結果ActivityResult activityResult = null;try {activityResult = (ActivityResult) execStartActivity_Method.invoke(orginalInstrumentation,who,contextThread,token,target,intent,requestCode,options);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}Log.i(TAG, "注入的 Hook 后執行的業務邏輯");return activityResult;} }

3、跳轉的界面


跳轉的界面 :

package com.example.plugin_hook;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;public class MainActivity2 extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main2);} }

4、執行結果


按鈕點擊事件 , 在調用按鈕事件的前后 , 注入的業務邏輯 , 分別打印 Hook Before 和 Hook After ;

Activity 啟動過程 , Hook 住了 Activity 的 Instrumentation mInstrumentation 成員的 execStartActivity 方法 , 在調用該方法的 前后 , 各注入的業務邏輯中打印 注入的 Hook 前執行的業務邏輯 和 注入的 Hook 后執行的業務邏輯 ;

I/MainActivity: Hook Before I/MainActivity: Button OnClickListener onClick I/InstrumentationProxy: 注入的 Hook 前執行的業務邏輯 I/InstrumentationProxy: 注入的 Hook 后執行的業務邏輯 I/MainActivity: Hook After



四、博客資源



博客資源 :

  • GitHub : https://github.com/han1202012/Plugin_Hook

總結

以上是生活随笔為你收集整理的【Android 插件化】Hook 插件化框架 ( Hook Activity 启动过程 | 静态代理 )的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。