【Android插件化】启动没有在Manifest中注册的Activity

1 概述

如果要启动没有在Manifest中注册的Activity,应该从startActivity着手。一般启动Activity的方式有两种,一种是startActivity,一种是startActivityForResult。其实startActivity最终调用的也是startActivityForResult,如下所示:

    //Activity.java
    @Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        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);
        }
    }

当我们启动一个Activity时,AMS会对这个Activity是否在AndroidManifest中声明注册进行检查,如果没有注册,则会报错。要想启动没有在AndroidManifest中注册的Activity,则需要“欺骗AMS”,让其以为我们已经在AndroidManifest中注册了。

基本思路是:

  1. 发送要启动的Activity信息给AMS之前,把这个Activity替换为一个在AndroidManifest中声明的StubActivity,这样就能绕过AMS的检查,在替换过程中,要把原来的Activity信息保存起来。
  2. AMS通知App启动StubActivity时,我们再将保存的Activity替换StubActivity,这样就能启动没有在AndroidManifest中注册的Activity。

修改Activity启动流程如图所示(图片来自网络)

image.png

2 代码实现

AndroidManifest.xml

    <application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".SubActivity"/>
    </application>

一共两个Activity,分别是MainActivity和SubActivity,一个MainApplication,SubActivity是一个壳Activity,用于欺骗AMS的。

MainActivity.java

    private View.OnClickListener mListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent();
            intent.setClass(MainActivity.this, PluginTestActivity.class);
            startActivity(intent);
        }
    };

PluginTestActivity并没有在AndroidManifest中注册,正常情况下会报错,所以我们需要欺骗AMS。

欺骗AMS的方式有几种,这里我们选择对ActivityThread的mInstrumentation进行hook。代码如下:

public class HookHelper {
    public static final String PLUGIN_INTENT = "plugin_intent";
    public static void hookInstrumentation(Context context,String className) throws Exception{
        //获取ActivityThread的字节码对象
        Class<?> clazz = Class.forName("android.app.ActivityThread");
        //获取ActivityThread中的sCurrentActivityThread字段
        Field sCurrentActivityThreadField = ReflectUtils.getField(clazz,"sCurrentActivityThread");
        //获取ActivityThread中的mInstrumentation字段
        Field mInstrumentationField = ReflectUtils.getField(clazz,"mInstrumentation");
        //获取ActivityThread中的sCurrentActivityThread的值并赋值给currentActivityThread
        Object currentActivityThread = sCurrentActivityThreadField.get(clazz);
        //获取ActivityThread中的mInstrumentation的值并赋值给instrumentation
        Instrumentation instrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
        //创建一个PackageManager对象packageManager
        PackageManager packageManager = context.getPackageManager();
        //创建InstrumentationProxy对象,InstrumentationProxy是继承Instrumentation的类,下面详细介绍
        InstrumentationProxy instrumentationProxy = new InstrumentationProxy(instrumentation,packageManager,className);
        //将ActivityThread中的mInstrumentation赋值为instrumentationProxy
        ReflectUtils.setFieldObject(clazz,currentActivityThread,"mInstrumentation",instrumentationProxy);
    }
}

InstrumentationProxy类如下

package com.example.plugintest;

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;

import java.lang.reflect.Method;
import java.util.List;

/**
 * Created by yds
 * on 2020/2/14.
 */
public class InstrumentationProxy extends Instrumentation{
    private Instrumentation mInstrumentation;
    private PackageManager mPackageManager;
    private String className;

    public InstrumentationProxy(Instrumentation mInstrumentation, PackageManager mPackageManager, String className) {
        this.mInstrumentation = mInstrumentation;
        this.mPackageManager = mPackageManager;
        this.className = className;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
        if (infos == null || infos.size() == 0) {
            intent.putExtra(HookHelper.PLUGIN_INTENT,intent.getComponent().getClassName());
            intent.setClassName(who,className);
        }
        try {
            Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
                    Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);
            return (ActivityResult) execMethod.invoke(mInstrumentation,who,contextThread,token,target,intent,requestCode,options);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public Activity newActivity(ClassLoader cl,String className,Intent intent) throws
            InstantiationException,IllegalAccessException,ClassNotFoundException{
        String intentName = intent.getStringExtra(HookHelper.PLUGIN_INTENT);
        if(!TextUtils.isEmpty(intentName)){
            return super.newActivity(cl,intentName,intent);
        }
        return super.newActivity(cl,className,intent);
    }
}

只要将InstrumentationProxy赋值给ActivityThread的mInstrumentation,调用startActivity时,最终会调用InstrumentationProxy的execStartActivity和newActivity。我们只要在execStartActivity中将未注册的Activity保存起来,并使用已注册的SubActivity,然后在newActivity中将保存起来未注册的Activity替换SubActivity就可以了。

源码地址:https://github.com/Yedongsheng/PluginTest01

发布了136 篇原创文章 · 获赞 175 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/u013293125/article/details/105407056
今日推荐