Android-Framework-05-AMS-06-启动一个没注册的Activity

66 阅读3分钟

1 启动流程

image.png

2 Activiy启动

image.png

image.png

3 Activiy栈关系

image.png

4 Task

String affinity; // 是指root activity的affinity,即该Task中第一个

final int mTaskId; // 任务栈的ID

int mCallingUid; // 调用者的UID

String mCallingPackage; //调用者的包名

ActivityRecord mPausingActivity //正在pause

ActivityRecord mLastPausedActivity

ActivityRecord mResumedActivity //已经resumed

5 ActivityTask

ArrayList activities // 当前task的所有Activity列表

6 ActivityRecord

Task task:跑在那个Task

ActivityInfo info:Activity信息

ActivityState mState:Activity状态

ApplicationInfo appInfo:跑在哪个app

String packageName:包名

String processName:进程名

String taskAffinity;

int launchMode:启动模式

int mUserId:该Activity运行在哪个用户id

@ActivityType int mActivityType:Activity类型

image.png

frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java

image.png

7 实现启动一个没注册的Activity的思路

通过 动态代理 和 反射 实现 Hook

image.png

在 Android 系统中,Activity 默认需在 AndroidManifest.xml 中注册才能启动。动态加载未注册的 Activity 主要依赖 占位 Activity 和 Hook 技术,结合类加载机制实现。以下是具体方案:


一、核心实现原理

  1. 占位 Activity 替换

    • 创建一个已注册的 StubActivity,在 AMS(ActivityManagerService)检查阶段临时替换目标 Activity,绕过注册验证45
    • AMS 验证通过后,在 Instrumentation 或 ActivityThread 回调阶段将 Intent 恢复为原始目标 Activity5
  2. Hook AMS 通信

    • 通过动态代理替换 IActivityManager(或 IActivityTaskManager)的 Binder 对象,拦截 startActivity 请求并修改 Intent 参数45
  3. 类加载与生命周期管理

    • 使用 DexClassLoader 加载外部 APK/DEX 中的 Activity 类,替换 LoadedApk 中的 ClassLoader 以支持动态加载34
    • 通过宿主 StubActivity 代理生命周期方法(如 onCreateonResume),将事件转发至目标 Activity4

二、具体实现步骤

  1. 创建占位 Activity

    xml
    复制
    <!-- AndroidManifest.xml  中注册占位 Activity -->
    <activity android:name=".StubActivity" />
    
  2. Hook AMS 拦截启动请求

    kotlin
    复制
    // 替换 IActivityManager 代理对象 
    val singletonClass = Class.forName("android.app.ActivityManagerNative") 
    val singletonField = singletonClass.getDeclaredField("gDefault") 
    val singleton = singletonField.get(null) 
    val mInstanceField = singleton.javaClass.getDeclaredField("mInstance") 
    mInstanceField.isAccessible  = true 
    val originalAMS = mInstanceField.get(singleton) 
    
    // 动态代理修改 Intent 
    val proxy = Proxy.newProxyInstance( 
        classLoader, arrayOf(Class.forName("android.app.IActivityManager")),  
        InvocationHandler { proxy, method, args ->
            if (method.name  == "startActivity") {
                // 将原始 Intent 替换为 StubActivity 的 Intent 
                val rawIntent = args[2]()  as Intent 
                val stubIntent = Intent().apply {
                    setClassName(rawIntent.component?.packageName  ?: "", StubActivity::class.java.name) 
                    putExtra("TARGET_INTENT", rawIntent)
                }
                args[2]()  = stubIntent 
            }
            method.invoke(originalAMS,  *args)
        }
    )
    mInstanceField.set(singleton,  proxy)
    
  3. 恢复目标 Activity

    kotlin
    复制
    // 在 Instrumentation 回调中恢复原始 Intent 
    val activityThreadClass = Class.forName("android.app.ActivityThread") 
    val currentActivityThread = activityThreadClass.getMethod("currentActivityThread").invoke(null) 
    val mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation") 
    mInstrumentationField.isAccessible  = true 
    val originalInstrumentation = mInstrumentationField.get(currentActivityThread)  as Instrumentation 
    
    // 自定义 Instrumentation 实现恢复逻辑 
    val proxyInstrumentation = object : Instrumentation() {
        override fun newActivity(cl: ClassLoader, className: String, intent: Intent): Activity {
            val targetIntent = intent.getParcelableExtra<Intent>("TARGET_INTENT") 
            return super.newActivity(cl,  targetIntent?.component?.className ?: className, targetIntent ?: intent)
        }
    }
    mInstrumentationField.set(currentActivityThread,  proxyInstrumentation)
    
  4. 动态加载外部 Activity

    kotlin
    复制
    // 加载 APK/DEX 中的类
    val dexPath = "/sdcard/plugin.apk" 
    val optimizedDir = context.getDir("dex",  Context.MODE_PRIVATE).absolutePath 
    val classLoader = DexClassLoader(dexPath, optimizedDir, null, context.classLoader) 
    
    // 替换 LoadedApk 的 ClassLoader 
    val loadedApkField = context.javaClass.getDeclaredField("mLoadedApk") 
    loadedApkField.isAccessible  = true 
    val loadedApk = loadedApkField.get(context) 
    val mClassLoaderField = loadedApk.javaClass.getDeclaredField("mClassLoader") 
    mClassLoaderField.isAccessible  = true 
    mClassLoaderField.set(loadedApk,  classLoader)
    

三、注意事项与优化

  1. 兼容性问题

    • Android 10+ 使用 IActivityTaskManager 替代 IActivityManager,需调整 Hook 逻辑4
    • 不同厂商系统可能对 AMS 实现有差异,需测试适配。
  2. 资源加载

    • 外部 APK 的布局资源需通过 AssetManager 加载,或使用宿主 App 的资源3
  3. 性能与稳定性

    • 避免频繁 Hook 系统服务,防止 ANR 或崩溃。
    • 使用 try-catch 包裹反射代码,确保兼容性4

通过上述方案,可实现动态加载未注册 Activity,适用于插件化开发、热修复等场景。具体实现需结合业务需求调整 Hook 粒度与资源管理策略。