人生一切难题,知识给你答案

温馨提示:阅读本文需要 15-20 分钟(一大波代码)


今天,我们来解决一个问题:

Activity 插件化原理第一种方案:Hook Instrumentation

人生一切难题,知识给你答案。


Activity 的插件化解决的一个根本性问题就是插件中的 Activity 并没有在宿主的 AndroidManifest.xml 中进行注册,也就是说我们需要启动一个未注册的 Activity,因此需要对 Activity 的启动过程有个了解。

启动 Activity 时会请求 AMS 创建 Activity,这里的 AMS 指的是 ActivityManagerService,AMS 所属的进程与宿主(发起者)不属于同一个进程,AMS 位于 SystemServer 进程中。

未命名文件 (5).png

应用程序进程与 AMS 之间的通信是通过 Binder 来实现的,AMS 要管理所有 APP 的启动请求,因此我们不能在 SystemServer 进程中进行相应的 Hook,那么我们只能在应用进程中进行相应的 Hook。

如果我们启动一个未注册的 Activity,AMS 会去检查 AndroidManifest 中是否注册了该 Activity,如果未注册会报错。

未命名文件 (6).png

为了让 AMS 验证通过,需要启动一个预先在 AndroidManifest 中注册的 Activity,我们称之为占坑,在启动插件 Activity 时替换为占坑 Activity,达到一个欺上瞒下的作用,当 AMS 验证通过之后,需要将启动的占坑 Activity 替换为插件 Activity。

未命名文件 (8).png

总结下来 Activity 的插件化需要做两件事:

  • 将请求启动的插件 Activity 替换为占坑 Activity。
  • 绕过 AMS 验证后,将占坑 Activity 替换为插件 Activity。

什么时候将插件 Activity 替换为占坑 Activity?又是什么时候还原插件 Activity?这需要我们对 Activity 的启动流程有个相应的认识。

我们在 Activity 中调用 startActivity 方法如下:

    @Override
    public void startActivity(Intent intent) {
        this.startActivity(intent, null);
    }
    <span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">startActivity</span><span class="hljs-params">(Intent intent, @Nullable Bundle options)</span> </span>{
    <span class="hljs-keyword">if</span> (options != <span class="hljs-keyword">null</span>) {
       startActivityForResult(intent, -<span class="hljs-number">1</span>, options);
    } <span class="hljs-keyword">else</span> {
       startActivityForResult(intent, -<span class="hljs-number">1</span>);
    }
}
复制代码

调用 startActivityForResult 方法:

    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
            //Activity 启动
            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) {
                mStartedActivity = true;
            }
        cancelInputsAndStartExitTransition(options);
        windows.
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">if</span> (options != <span class="hljs-keyword">null</span>) {
            mParent.startActivityFromChild(<span class="hljs-keyword">this</span>, intent, requestCode, options);
        } <span class="hljs-keyword">else</span> {
            mParent.startActivityFromChild(<span class="hljs-keyword">this</span>, intent, requestCode);
        }
    }
}
复制代码

startActivityForResult 方法中通过调用 mInstrumentation 的 execStartActivity 方法来启动 Activity,这个 mInstrumentation 是 Activity 的成员变量,在 ActivityThread 的 performLaunchActivity 方法中通过 Activity 的 attach 方法传入,同时 Activity 的创建也是在 performLaunchActivity 方法中创建的,通过 mInstrumentation.newActivity。

//:/frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    try {
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    }
    ...
    activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
    ...
}
复制代码

综上所述 Instrumentation 提供了 execStartActivity 方法来启动 Activity,newActivity 方法来创建 Activity。因此,第一种方案就是用代理 Instrumentation 来替代 Activity 的 Instrumentation,并在代理 Instrumentation 的 execStartActivity 方法中替换为占坑 Activity,在 newActivity 方法还原插件 Activity。

现在我们基于第一种方案 Hook Instrumentation 来实现 Activity 的插件化。

首先创建占坑 Activity:

public class StubActivity extends AppCompatActivity {
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(@Nullable Bundle savedInstanceState)</span> </span>{
    <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
    setContentView(R.layout.activity_stub);
}

}

复制代码

创建插件 Activity:

public class TargetActivity extends AppCompatActivity {
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(@Nullable Bundle savedInstanceState)</span> </span>{
    <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
    setContentView(R.layout.activity_target);
}

}

复制代码

并在 AndroidManifest.xml 中注册占坑 Activity:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.glh.haiproject01">
<span class="hljs-tag">&lt;<span class="hljs-name">application</span>
    <span class="hljs-attr">android:name</span>=<span class="hljs-string">".MyApplication"</span>
    <span class="hljs-attr">android:allowBackup</span>=<span class="hljs-string">"true"</span>
    <span class="hljs-attr">android:icon</span>=<span class="hljs-string">"@mipmap/ic_launcher"</span>
    <span class="hljs-attr">android:label</span>=<span class="hljs-string">"@string/app_name"</span>
    <span class="hljs-attr">android:roundIcon</span>=<span class="hljs-string">"@mipmap/ic_launcher_round"</span>
    <span class="hljs-attr">android:supportsRtl</span>=<span class="hljs-string">"true"</span>
    <span class="hljs-attr">android:theme</span>=<span class="hljs-string">"@style/AppTheme"</span>
    <span class="hljs-attr">tools:ignore</span>=<span class="hljs-string">"AllowBackup,GoogleAppIndexingWarning"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">activity</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">".MainActivity"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">intent-filter</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">action</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.intent.action.MAIN"</span> /&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">category</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">"android.intent.category.LAUNCHER"</span> /&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">intent-filter</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">activity</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">activity</span> <span class="hljs-attr">android:name</span>=<span class="hljs-string">".StubActivity"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">application</span>&gt;</span>

</manifest>

复制代码

在 AndroidManifest.xml 中没有注册插件 Activity,这时如果启动插件 Activity 会报错。

最后 Hook Instrumentation,将 ActivityThread 中的成员变量 Instrumentation 替换成代理的 Instrumentation。

创建代理 Instrumentation 类:

public class InstrumentationProxy extends Instrumentation {
<span class="hljs-keyword">private</span> Instrumentation mInstrumentation;
<span class="hljs-keyword">private</span> PackageManager mPackageManager;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">InstrumentationProxy</span><span class="hljs-params">(Instrumentation instrumentation, PackageManager packageManager)</span> </span>{
    <span class="hljs-keyword">this</span>.mInstrumentation = instrumentation;
    <span class="hljs-keyword">this</span>.mPackageManager = packageManager;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> ActivityResult <span class="hljs-title">execStartActivity</span><span class="hljs-params">(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, <span class="hljs-keyword">int</span> requestCode, Bundle options)</span> </span>{

    List&lt;ResolveInfo&gt; resolveInfo = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
    <span class="hljs-comment">//判断启动的插件Activity是否在AndroidManifest.xml中注册过</span>
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">null</span> == resolveInfo || resolveInfo.size() == <span class="hljs-number">0</span>) {
        <span class="hljs-comment">//保存目标插件</span>
        intent.putExtra(HookHelper.REQUEST_TARGET_INTENT_NAME, intent.getComponent().getClassName());
        <span class="hljs-comment">//设置为占坑Activity</span>
        intent.setClassName(who, <span class="hljs-string">"com.glh.haiproject01.StubActivity"</span>);
    }

    <span class="hljs-keyword">try</span> {
        Method execStartActivity = Instrumentation.class.getDeclaredMethod(<span class="hljs-string">"execStartActivity"</span>,
                Context.class, IBinder.class, IBinder.class, Activity.class,
                Intent.class, <span class="hljs-keyword">int</span>.class, Bundle.class);
        <span class="hljs-keyword">return</span> (ActivityResult) execStartActivity.invoke(mInstrumentation, who, contextThread, token, target, intent, requestCode, options);
    } <span class="hljs-keyword">catch</span> (NoSuchMethodException e) {
        e.printStackTrace();
    } <span class="hljs-keyword">catch</span> (IllegalAccessException e) {
        e.printStackTrace();
    } <span class="hljs-keyword">catch</span> (InvocationTargetException e) {
        e.printStackTrace();
    }
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
}

<span class="hljs-function"><span class="hljs-keyword">public</span> Activity <span class="hljs-title">newActivity</span><span class="hljs-params">(ClassLoader cl, String className, Intent intent)</span> <span class="hljs-keyword">throws</span> InstantiationException,
        IllegalAccessException, ClassNotFoundException </span>{
    String intentName=intent.getStringExtra(HookHelper.REQUEST_TARGET_INTENT_NAME);
    <span class="hljs-keyword">if</span>(!TextUtils.isEmpty(intentName)){
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.newActivity(cl,intentName,intent);
    }
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">super</span>.newActivity(cl,className,intent);
}

}

复制代码

代理类 InstrumentationProxy 的 execStartActivity 方法先判断插件 Activity 是否在 AndroidManifest.xml 中注册过,如果没有注册过就需要替换占坑的 Activity,在 newActivity 方法中还原插件 Activity。

代理类 InstrumentationProxy 写完后,需要对 ActivityThread 的成员变量 mInstrumentation 进行替换。

public class MyApplication extends Application {
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">attachBaseContext</span><span class="hljs-params">(Context base)</span> </span>{
    <span class="hljs-keyword">super</span>.attachBaseContext(base);
    hookActivityThreadInstrumentation();
}


<span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">hookActivityThreadInstrumentation</span><span class="hljs-params">()</span></span>{
    <span class="hljs-keyword">try</span> {
        Class&lt;?&gt; activityThreadClass=Class.forName(<span class="hljs-string">"android.app.ActivityThread"</span>);
        Field activityThreadField=activityThreadClass.getDeclaredField(<span class="hljs-string">"sCurrentActivityThread"</span>);
        activityThreadField.setAccessible(<span class="hljs-keyword">true</span>);
        <span class="hljs-comment">//获取ActivityThread对象sCurrentActivityThread</span>
        Object activityThread=activityThreadField.get(<span class="hljs-keyword">null</span>);

        Field instrumentationField=activityThreadClass.getDeclaredField(<span class="hljs-string">"mInstrumentation"</span>);
        instrumentationField.setAccessible(<span class="hljs-keyword">true</span>);
        <span class="hljs-comment">//从sCurrentActivityThread中获取成员变量mInstrumentation</span>
        Instrumentation instrumentation= (Instrumentation) instrumentationField.get(activityThread);
        <span class="hljs-comment">//创建代理对象InstrumentationProxy</span>
        InstrumentationProxy proxy=<span class="hljs-keyword">new</span> InstrumentationProxy(instrumentation,getPackageManager());
        <span class="hljs-comment">//将sCurrentActivityThread中成员变量mInstrumentation替换成代理类InstrumentationProxy</span>
        instrumentationField.set(activityThread,proxy);
    } <span class="hljs-keyword">catch</span> (NoSuchFieldException e) {
        e.printStackTrace();
    } <span class="hljs-keyword">catch</span> (IllegalAccessException e) {
        e.printStackTrace();
    } <span class="hljs-keyword">catch</span> (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

}

复制代码

这时我们在主界面点击跳转插件 Activity:

public class MainActivity extends AppCompatActivity {
<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(Bundle savedInstanceState)</span> </span>{
    <span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    findViewById(R.id.btn_startActivity).setOnClickListener(<span class="hljs-keyword">new</span> View.OnClickListener() {
        <span class="hljs-meta">@Override</span>
        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onClick</span><span class="hljs-params">(View v)</span> </span>{
            Intent intent=<span class="hljs-keyword">new</span> Intent(MainActivity.<span class="hljs-keyword">this</span>,TargetActivity.class);
            startActivity(intent);
        }
    });
}

}

复制代码

运行效果:

wq1.gif


838794-506ddad529df4cd4.webp.jpg

  • Android

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...