插件化基础(三)——启动插件组件

系列文章目录:

插件化基础(一)——加载插件的类
插件化基础(二)——加载插件资源
插件化基础(三)——启动插件组件


由于插件没有直接运行的环境,所以我们不能直接像平常那样启动它们,一般都需要通过宿主提供环境,或者将插件中要启动的内容添加到宿主中,借由宿主启动。启动插件主要有三种方式:

  1. 占位式:在宿主中为每种组件设置一个代理,通过代理将宿主的运行环境传递给插件,并协助控制插件组件的生命周期
  2. Hook 式:分别 Hook AMS 和 Handler,使得没在宿主 AndroidManifest 中注册的,插件中的 Activity 绕过 AMS 检查得以启动
  3. LoadedApk 式:也需要 Hook AMS 和 Handler,只是加载加载插件类的 ClassLoader 的构造方式与 Hook 式不同,严格来说不是一种单独的插件化方法,而是在 Hook 式上的演进

一、占位式

占位指的是宿主中四大组件的代理,插件组件的启动都要经由代理处理,代理的主要作用有二:

  1. 将本体传递给插件作为插件的运行环境
  2. 管理插件中各组件的生命周期

整个结构如下:

1.1 Activity

先看宿主如何启动插件的入口,首先定义一个 Activity 的接口:

public interface ActivityInterface {

	// 给插件传入宿主运行环境
    void insertAppContext(Activity activity);

    void onCreate(Bundle savedInstanceState);

    void onStart();

    void onResume();

    void onPause();

    void onStop();

    void onDestroy();
}

插件的 BaseActivity 要实现这个接口:

public class BaseActivity extends Activity implements ActivityInterface {

    protected Activity hostActivity;

    @Override
    public void insertAppContext(Activity activity) {
        hostActivity = activity;
    }
	
	// 由于插件没有运行环境,所以像 context、super、this 这些是不能使用的,因此
	// 无法调用 super.onCreate(),只能添加 @SuppressLint("MissingSuperCall")
    @SuppressLint("MissingSuperCall")
    @Override
    public void onCreate(Bundle savedInstanceState) {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStart() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onResume() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onPause() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onStop() {

    }

    @SuppressLint("MissingSuperCall")
    @Override
    public void onDestroy() {

    }

    @Override
    public void setContentView(int layoutResID) {
        if (hostActivity != null) {
            hostActivity.setContentView(layoutResID);
        }
    }

    @Override
    public <T extends View> T findViewById(int id) {
        if (hostActivity != null) {
            return hostActivity.findViewById(id);
        }
        // super 其实是用不了的
        return super.findViewById(id);
    }
}

由于没有直接运行的环境,所以像 setContentView()、findViewById() 这些方法都需要借用宿主的 Activity 实现。

插件中的所有 Activity 需要继承 BaseActivity:

public class PluginActivity extends BaseActivity {
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin);
    }
}

宿主这边,启动插件的入口 Activity 时,其实是先启动代理的 ProxyActivity,将插件入口 Activity 的全类名存入 Intent:

	private void startPlugin() {
        // 待加载插件路径
        String pluginPath = getFilesDir().getAbsolutePath() + File.separator + "placeholder_plugin-debug.apk";

        // 获取插件的 PackageInfo
        PackageInfo packageInfo = getPackageManager().getPackageArchiveInfo(pluginPath, PackageManager.GET_ACTIVITIES);
        if (packageInfo != null) {
            // 需要插件入口 Activity 在 AndroidManifest 中第一个定义
            ActivityInfo activityInfo = packageInfo.activities[0];
            Intent intent = new Intent(this, ProxyActivity.class);
            intent.putExtra("className", activityInfo.name);
            startActivity(intent);
        }
    }

代理从 Intent 中拿到要启动的插件类,转换成 ActivityInterface,再执行插件 Activity 的生命周期方法:

public class ProxyActivity extends Activity {

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        String target = getIntent().getStringExtra("className");
        try {
            Class<?> targetClass = getClassLoader().loadClass(target);
            ActivityInterface activityInterface = (ActivityInterface) targetClass.newInstance();
            // 给插件注入宿主环境
            activityInterface.insertAppContext(this);
            // 调用插件 Activity 的 onCreate()
            activityInterface.onCreate(new Bundle());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这样宿主就能启动插件的入口 Activity 了,而插件内部 Activity 之间跳转,实际是要先跳到宿主的代理 ProxyActivity (因为需要通过 ProxyActivity 模拟 Activity 栈),再跳到真正要启动的插件 Activity,示例代码如下:

public class PluginActivity extends BaseActivity {
	
	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin);

		// 启动插件的 TestActivity
        findViewById(R.id.button_activity).setOnClickListener((listener) -> {
            Intent intent = new Intent(hostActivity, TestActivity.class);
            startActivity(intent);
        });
    }
}

BaseActivity 要重写 startActivity(),将目标 Activity 的全类名放在 Intent 的 className 中传递到宿主的代理 ProxyActivity 中:

	@Override
    public void startActivity(Intent intent) {
        Intent newIntent = new Intent();
        newIntent.putExtra("className", intent.getComponent().getClassName());
        hostActivity.startActivity(newIntent);
    }

ProxyActivity 的 startActivity() 要先启动一个新的 ProxyActivity 实例(模拟 Activity 栈),Intent 还是用 className 保存目标 Activity 类名:

	@Override
    public void startActivity(Intent intent) {
        String className = intent.getStringExtra("className");
        Intent proxyIntent = new Intent(this, ProxyActivity.class);
        proxyIntent.putExtra("className", className);
        super.startActivity(proxyIntent);
    }

这样新的 ProxyActivity 启动后,在 onCreate() 中就会反射创建目标 Activity 实例并调用其 onCreate()。

1.2 Service

跟 Activity 是类似的套路,先定义接口:

public interface ServiceInterface {

    void insertAppContext(Service service);

    void onCreate();

    int onStartCommand(Intent intent, int flags, int startId);

    void onDestroy();
}

插件中待启动的 Service 需要实现这个接口:

public class TestService extends Service implements ServiceInterface {

    private static final String TAG = TestService.class.getSimpleName();

    public TestService() {
    }

    @Override
    public void insertAppContext(Service service) {

    }

    @Override
    public void onCreate() {

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand: TestService");
        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {

    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

在插件的 UI 中启动 TestService:

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin);
        ...
		// 启动插件 Service
        findViewById(R.id.button_service).setOnClickListener((listener) ->
                startService(new Intent(hostActivity, TestService.class))
        );
    }

然后重写 BaseActivity 的 startService():

	@Override
    public ComponentName startService(Intent service) {
        Intent intent = new Intent();
        intent.putExtra("className", service.getComponent().getClassName());
        return hostActivity.startService(intent);
    }

将 TestService 的全类名通过 Intent 传到宿主的 ProxyActivity 中:

	@Override
    public ComponentName startService(Intent service) {
        Intent intent = new Intent(this, ProxyService.class);
        intent.putExtra("className", service.getStringExtra("className"));
        return super.startService(intent);
    }

启动宿主中的代理 Service:

	public class ProxyService extends Service {

    public ProxyService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String serviceName = intent.getStringExtra("className");

        try {
            Class<?> clazz = getClassLoader().loadClass(serviceName);
            ServiceInterface serviceInterface = (ServiceInterface) clazz.newInstance();
            serviceInterface.insertAppContext(this);
            serviceInterface.onStartCommand(intent, flags, startId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.onStartCommand(intent, flags, startId);
    }

	@Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

在 ProxyService 的 onStartCommand() 中实例化插件的 TestService 并调用其 onStartCommand()。

1.3 BroadcastReceiver

BroadcastReceiver 分为动态注册和静态注册两种方式,对两种方式的插件化处理也不同。

动态注册

动态注册的 BroadcastReceiver 的处理方式与前面类似,先来个接口:

public interface ReceiverInterface {

    void onReceive(Context context, Intent intent);
}

插件的 BroadcastReceiver 实现该接口:

public class TestReceiver extends BroadcastReceiver implements ReceiverInterface {

    private static final String TAG = TestReceiver.class.getSimpleName();

    @Override
    public void onReceive(Context context, Intent intent) {
        Log.d(TAG, "onReceive: " + intent);
    }
}

动态注册、注销、发送广播:

public class PluginActivity extends BaseActivity {
    private BroadcastReceiver mReceiver;

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_plugin);

		// 注册插件动态广播
        findViewById(R.id.button_register_dynamic_broadcast).setOnClickListener((listener) -> {
            IntentFilter dynamicFilter = new IntentFilter();
            dynamicFilter.addAction("com.apk.plugin.receiver.dynamic");
            mReceiver = new TestReceiver();
            // 在 BaseActivity 中重写
            registerReceiver(mReceiver, dynamicFilter);
        });

        // 发送插件动态广播
        findViewById(R.id.button_send_broadcast).setOnClickListener((listener) -> {
            Intent intent = new Intent();
            intent.setAction("com.apk.plugin.receiver.dynamic");
            // 在 BaseActivity 中重写
            sendBroadcast(intent);
        });
    }

	@Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver);
    }
}

BaseActivity 重写注册、注销、发广播的方法,都是要交给宿主的代理 ProxyActivity 处理:

	@Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        return hostActivity.registerReceiver(receiver, filter);
    }

    @Override
    public void sendBroadcast(Intent intent) {
        hostActivity.sendBroadcast(intent);
    }

    @Override
    public void unregisterReceiver(BroadcastReceiver receiver) {
        hostActivity.unregisterReceiver(receiver);
    }

其中注册广播的 registerReceiver() 要拿到待启动 BroadcastReceiver 的全类名给到代理的 ProxyReceiver:

	@Override
    public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
        String className = receiver.getClass().getName();
        return super.registerReceiver(new ProxyReceiver(className), filter);
    }

最后在代理中启动插件的 Receiver:

public class ProxyReceiver extends BroadcastReceiver {

    private String className;

    public ProxyReceiver(String className) {
        this.className = className;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            Class<?> clazz = context.getClassLoader().loadClass(className);
            ReceiverInterface receiverInterface = (ReceiverInterface) clazz.newInstance();
            receiverInterface.onReceive(context, intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

静态注册

静态注册的 BroadcastReceiver 都是在 AndroidManifest 文件中配置的,在应用安装时会系统会解析其 AndroidManifest 的内容,如果解析到静态广播就会进行注册。所以我们的思路就是,让未安装的插件 apk 也能被系统扫描解析,并注册静态广播。

源码中是通过 PackageParser.Package 的 parsePackage() 来解析 apk 的:

	public Package parsePackage(File packageFile, int flags, boolean useCaches)
            throws PackageParserException {
        Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;
        if (parsed != null) {
            return parsed;
        }

        if (packageFile.isDirectory()) {
            parsed = parseClusterPackage(packageFile, flags);
        } else {
            parsed = parseMonolithicPackage(packageFile, flags);
        }

        cacheResult(packageFile, flags, parsed);

        return parsed;
    }

parsePackage() 解析的结果是一个 Package,而 PackageParser.Package 这个类保存着所有解析的结果:

	public final static class Package implements Parcelable {
		...
		public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
        public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
        public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
        // AndroidManifest 中注册的广播接收者集合,注意这个 Activity 不是四大组件的 Activity,而是 PackageParser 的内部类
        public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
        public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
        public final ArrayList<Service> services = new ArrayList<Service>(0);
        public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);

        public final ArrayList<String> requestedPermissions = new ArrayList<String>();

		public ArrayList<String> protectedBroadcasts;

        public Package parentPackage;
        public ArrayList<Package> childPackages;
        ...
    }

我们可以通过反射调用 parsePackage() 来解析插件 apk,然后从 receivers 集合中拿到静态注册的 BroadcastReceiver,最后再手动注册它们:

	public void parseApk(Context context, List<String> pluginPaths) {
        for (String pluginPath : pluginPaths) {
            if (TextUtils.isEmpty(pluginPath)) {
                Log.e(TAG, "插件包路径不能为空!");
                return;
            }
            File pluginFile = new File(pluginPath);
            if (!pluginFile.exists()) {
                Log.e(TAG, "插件 apk 文件不存在!");
            }

            try {
                // 调用 PackageParser 的 parsePackage() 解析 pluginFile,得到结果 packageObject 实际是 Package
                Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
                Object packageParser = packageParserClass.newInstance();
                Method parsePackageMethod = packageParserClass.getMethod("parsePackage",
                        File.class, int.class);
                Object packageObject = parsePackageMethod.invoke(packageParser, pluginFile, PackageManager.GET_ACTIVITIES);

                // 拿到 Package 内的 receivers 集合,可以把 receiver 理解成 AndroidManifest 中的 <receiver> 
                // 元素,解析出它的类名和 IntentFilter 信息即可手动注册这个 BroadcastReceiver
                Field receiversField = packageObject.getClass().getDeclaredField("receivers");
                ArrayList receivers = (ArrayList) receiversField.get(packageObject);

                // ArrayList 的泛型类型是 PackageParser.Activity,不是四大组件那个 Activity,
                // class Activity extends Component<ActivityIntentInfo>
                // 接下来就遍历 ArrayList,对每一个 Receiver 进行手动注册
                if (receivers != null) {
                    // Component 的 ArrayList<II> intents 字段保存着 IntentFilter 信息,
                    // 所以首先我们就通过反射拿到该 receiver 对象下所有的 IntentFilter
                    Class<?> componentClass = Class.forName("android.content.pm.PackageParser$Component");
                    Field intentsField = componentClass.getField("intents");

                    // 通过 PackageParser 的 generateActivityInfo() 得到包含 receiver 类名的
                    // ActivityInfo 对象,先构造该方法需要的参数 PackageUserState 对象
                    Class<?> packageUsageStateClass = Class.forName("android.content.pm.PackageUserState");
                    Class<?> userHandleClass = Class.forName("android.os.UserHandle");
                    
                    for (Object receiver : receivers) {
                        // 尽量把公共代码提到循环外,减少反射的执行次数
                        ArrayList<IntentFilter> intents = (ArrayList) intentsField.get(receiver); // 直接用子类对象去拿
                        
                        int userId = (int) userHandleClass.getMethod("getCallingUserId").invoke(null);

                        // 反射调用 generateActivityInfo() 生成 ActivityInfo 并拿到全类名
                        Method generateActivityInfoMethod = packageParserClass.getMethod("generateActivityInfo",
                                receiver.getClass(), int.class, packageUsageStateClass, int.class);
                        ActivityInfo activityInfo = (ActivityInfo) generateActivityInfoMethod.invoke(null,
                                receiver, 0, packageUsageStateClass.newInstance(), userId);
                        String receiverName = activityInfo.name;

                        // 注册
                        Class<?> receiverClass = context.getClassLoader().loadClass(receiverName);
                        BroadcastReceiver broadcastReceiver = (BroadcastReceiver) receiverClass.newInstance();
                        for (IntentFilter intentFilter : intents) {
                            context.registerReceiver(broadcastReceiver, intentFilter);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

测试的话在插件的 AndroidManifest 中注册一个静态 BroadcastReceiver:

		<receiver
            android:name=".StaticReceiver"
            android:enabled="true"
            android:exported="false">
            <intent-filter>
                <action android:name="com.demo.placeholder.plugin.test"/>
            </intent-filter>
        </receiver>

发广播:

		// 发送插件静态广播
        findViewById(R.id.button_send_static_broadcast).setOnClickListener((listener) -> {
            Intent intent = new Intent();
            intent.setAction("com.demo.placeholder.plugin.test");
            sendBroadcast(intent);
        });

验证 StaticReceiver 中可以收到广播,回调 onReceive()。

1.4 ContentProvider

仍然需要在宿主中通过代理的 ContentProvider 去加载插件的 ContentProvider 对象,再调用对应的 CURD 方法,代码比较糙,主要看思路。

首先在宿主中新建代理的 ProxyProvider,配置好 android:authorities 属性:

		<provider
            android:name=".ProxyProvider"
            android:authorities="com.demo.placeholder.host.ProxyProvider"
            android:enabled="true"
            android:exported="false"/>

Java 代码:

public class ProxyProvider extends ContentProvider {

    private static final String TAG = ProxyProvider.class.getSimpleName();

    // Uri path 与插件的 ContentProvider 全类名的映射关系表
    private static final Map<String, String> sClassNameMap;
    // 插件的 ContentProvider 全类名与实例对象的缓存
    private Map<String, ContentProvider> mCache;
    private ContentProvider mPluginProvider;

    static {
        sClassNameMap = new HashMap<>();
        sClassNameMap.put("plugin00", "com.demo.placeholder.plugin.PluginProvider");
        sClassNameMap.put("plugin01", "com.demo.placeholder.plugin.PluginProvider1");
    }

    /**
     * 1.根据要操作的 Uri 先找出对应的插件 ContentProvider 的全类名
     * 2.利用全类名反射得到插件 ContentProvider 的实例对象
     * 后续 CURD 操作都需要先来拿 ContentProvider 实例再操作
     */
    public ContentProvider loadPluginProvider(Uri uri) {
        Log.d(TAG, "Uri: " + uri.toString());
        // uri.getPath() 拿到的是 /plugin00,而 getPathSegments() 拿到的是
        // 没有 / 的,每段路径名组成的集合,对于当前 demo 的代码直接取 pathSegments(0)
        List<String> pathSegments = uri.getPathSegments();
        String className = sClassNameMap.get(pathSegments.get(0));

        // 先去缓存中找
        if (mCache != null) {
            ContentProvider contentProvider = mCache.get(className);
            if (contentProvider != null) {
                return contentProvider;
            }
        }

        // 缓存没有再反射
        try {
            Class<?> clazz = getContext().getClassLoader().loadClass(className);
            mPluginProvider = (ContentProvider) clazz.newInstance();
            if (mCache == null) {
                mCache = new HashMap<>();
            }
            mCache.put(className, mPluginProvider);
        } catch (Exception e) {
            e.printStackTrace();
        }

        Log.d(TAG, "loadPluginProvider: " + mPluginProvider);
        return mPluginProvider;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        ContentProvider pluginProvider = loadPluginProvider(uri);
        if (pluginProvider != null) {
            return pluginProvider.delete(uri, selection, selectionArgs);
        }
        return 0;
    }

    @Override
    public String getType(Uri uri) {
        ContentProvider pluginProvider = loadPluginProvider(uri);
        if (pluginProvider != null) {
            return pluginProvider.getType(uri);
        }
        return "";
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        ContentProvider pluginProvider = loadPluginProvider(uri);
        if (pluginProvider != null) {
            return pluginProvider.insert(uri, values);
        }
        return null;
    }

    @Override
    public boolean onCreate() {
        Log.d(TAG, "onCreate: ");
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        ContentProvider pluginProvider = loadPluginProvider(uri);
        if (pluginProvider != null) {
            return pluginProvider.query(uri, projection, selection, selectionArgs, sortOrder);
        }
        return null;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        ContentProvider pluginProvider = loadPluginProvider(uri);
        if (pluginProvider != null) {
            pluginProvider.update(uri, values, selection, selectionArgs);
        }
        return 0;
    }
}

插件这边新建两个测试用的 ContentProvider,需要实现的 6 个方法中只添加 log 看效果,没有实际操作。宿主添加一段测试代码,加载插件的 ContentProvider 做 CURD 测试:

	private void testPluginProvider() {
        ArrayList<Uri> list = new ArrayList<>();
        list.add(Uri.parse("content://com.demo.placeholder.host.ProxyProvider/plugin00"));
        list.add(Uri.parse("content://com.demo.placeholder.host.ProxyProvider/plugin01"));

        ContentResolver contentResolver = getContentResolver();
        for (Uri uri : list) {
            contentResolver.query(uri, null, null, null, null);
            contentResolver.insert(uri, new ContentValues());
            contentResolver.delete(uri, "", new String[]{});
            contentResolver.update(uri, new ContentValues(), "", new String[]{});
        }
    }

Log 截图:

二、Hook 式

2.1 startActivity() 过程

Hook 式插件化需要对 startActivity() 的执行流程有一定了解,先上幅图:

由 Activity1 启动 Activity2 的大致过程如上,贴点源码简单说说与 Hook 相关的部分。

Activity1 这边调用 startActivity() 后会执行到 startActivityForResult(),并执行到图中的第 1 步:

	public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {
        	Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            ...
        }
        ...
    }

Instrumentation 的 execStartActivity() 会跨进程调用 AMS 的 startActivity() 并检查启动的结果,对应上图 2、3 步:

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

		...
		try {
            ...
            // 调用 AMS 的 startActivity() 并得到启动结果 result
            int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            // 检查这个 result
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;

checkStartActivityResult() 检查 result 时如果有问题会抛出异常:

	public static void checkStartActivityResult(int res, Object intent) {
        if (!ActivityManager.isStartResultFatalError(res)) {
            return;
        }

        switch (res) {
            case ActivityManager.START_INTENT_NOT_RESOLVED:
            case ActivityManager.START_CLASS_NOT_FOUND:
            	// 要启动的 Activity 没在 Manifest 中注册会抛这个异常 
                if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                    throw new ActivityNotFoundException(
                            "Unable to find explicit activity class "
                            + ((Intent)intent).getComponent().toShortString()
                            + "; have you declared this activity in your AndroidManifest.xml?");
                throw new ActivityNotFoundException(
                        "No Activity found to handle " + intent);
            case ActivityManager.START_PERMISSION_DENIED:
                throw new SecurityException("Not allowed to start activity "
                        + intent);
            // 还有好多 case,省略了... 
            default:
                throw new AndroidRuntimeException("Unknown error code "
                        + res + " when starting " + intent);
        }
    }

比如说,要启动的 Activity 如果没在 AndroidManifest 中注册,就会抛 ActivityNotFoundException 问你在 AndroidManifest 声明了吗……

第 4 ~ 7 步是 AMS 内部处理,这里就省略不表了。看第 8 步调用了 ActivityThread 中 Handler 的 handleMessage(),消息类型为
LAUNCH_ACTIVITY:

	private class H extends Handler {
	
        public static final int LAUNCH_ACTIVITY         = 100;
        
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    // 开始启动 Activity
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                } break;
            	...
            }
            ...
        }

第 9 步,handleLaunchActivity() 启动 Activity:

	private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
		...
		// Make sure we are running with the most recent config.
        handleConfigurationChanged(null, null);
        ...
        
        // 将 Activity 实例化
		Activity a = performLaunchActivity(r, customIntent);
		
		if (a != null) {
			// resume
			handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
			if (!r.activity.mFinished && r.startsNotResumed) {
                // pause
                performPauseActivityIfNeeded(r, reason);
				...
            }
		} else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            try {
                ActivityManager.getService()
                    .finishActivity(r.token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
	}

步骤 10,performLaunchActivity() 生成一个 Activity 对象:

	private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
		// 赋值 ActivityClientRecord 的 packageInfo 字段
		ActivityInfo aInfo = r.activityInfo;
        if (r.packageInfo == null) {
            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
                    Context.CONTEXT_INCLUDE_CODE);
        }

		// 生成 Activity 对应的 ComponentName
        ComponentName component = r.intent.getComponent();
        if (component == null) {
            component = r.intent.resolveActivity(
                mInitialApplication.getPackageManager());
            r.intent.setComponent(component);
        }

        if (r.activityInfo.targetActivity != null) {
            component = new ComponentName(r.activityInfo.packageName,
                    r.activityInfo.targetActivity);
        }

		// 开始实例化 Activity
	    ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            // Instrumentation 创建 Activity,其实就是
            // return (Activity)cl.loadClass(className).newInstance();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ...
        } catch (Exception e) {
            ...
        }

		try {
			Application app = r.packageInfo.makeApplication(false, mInstrumentation);
			if (activity != null) {
				...
				// attach 过程
				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);
				...
				// create 过程,会回调 Activity 的 onCreate(),即图中步骤 11
				if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
			}
		}
        ...
        return activity;
	}

2.2 Hook 启动插件 Activity

了解了 Activity 的启动过程,再来看如何 Hook 启动插件中的 Activity。由于 AMS 会检查待启动的 Activity 是否在 AndroidManifest 中注册,所以直接用 startActivity() 启动插件 Activity 肯定不行,只能在 AMS 检查之前先换成宿主的代理 Activity 以通过检查,然后在第 10 步 Instrumentation.newActivity() 之前换回插件 Activity,被实例化,然后启动:

Hook 过程分为两段,其中 HookAMS 过程主要用到动态代理,HookHandler 过程主要用到反射。

此外,Hook 点不止一个,我们寻找 Hook 点的原则是:

  1. 尽量静态变量或者单例对象(容易获取且不易改变)
  2. 尽量 Hook public 的对象和方法(影响范围会小一点)

HookAMS

宿主中新建 ProxyActivity,它不用做任何操作,只需要在 AndroidManifest 中注册,帮助我们绕过 AMS 检测就好。

接下来用这个 ProxyActivity 去替换要启动的插件 Activity,替换要在 AMS 检查之前,也就是上图中第 2、3 步执行 AMS 的 startActivity() 之前,索性我们就选择这个方法作为 Hook 点:

			int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);

将该方法第 3 个参数 intent 的目标改成 ProxyActivity 即可。在此之前,看下 ActivityManager 的 getService():

	/**
     * @hide
     */
    public static IActivityManager getService() {
    	// Singleton 的 get() 得到的就是 create() 返回的对象
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                	// 通过 ServiceManager 获取 ACTIVITY_SERVICE 服务,准备要 IPC
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };

所以 Hook AMS 的代码:

	public void hookAMS() {
        try {
            // 1.反射调用 ActivityManager.getService() 得到 IActivityManager 对象
            Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
            Method getServiceMethod = activityManagerClass.getMethod("getService");
            Object iActivityManager = getServiceMethod.invoke(null);

            Log.d("Frank", "hookAMS: " + getServiceMethod.getName());

            // 2.生成动态代理对象,监听 IActivityManager 方法执行,替换掉 startActivity() 的参数 intent
            Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
            Object iActivityManagerProxy = Proxy.newProxyInstance(mContext.getClassLoader(),
                    new Class[]{iActivityManagerClass},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            // 通过包名过滤一下,只处理插件中的方法
                            if (method.getDeclaringClass().getName().startsWith(PACKAGE_PLUGIN)) {
                                if ("startActivity".equals(method.getName())) {
                                    for (Object arg : args) {
                                        if (arg instanceof Intent) {
                                            Intent proxyIntent = new Intent(mContext, ProxyActivity.class);
                                            proxyIntent.putExtra("actionIntent", (Intent) arg);
                                            arg = proxyIntent;
                                        }
                                    }
                                }
                            }
                            return method.invoke(iActivityManager, args);
                        }
                    });

            // 3.用动态代理对象替换掉 IActivityManagerSingleton 内的 mInstance
            Field iSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
            iSingletonField.setAccessible(true);
            Object IActivityManagerSingleton = iSingletonField.get(null);

            Class<?> singletonClass = Class.forName("android.util.Singleton");
            Field mInstanceField = singletonClass.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            mInstanceField.set(IActivityManagerSingleton, iActivityManagerProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Hook 点并不是唯一的,Activity.startActivityForResult() 中 mInstrumentation.execStartActivity() 也可以作为 Hook 点,不过主要想法都是去想办法替换 Intent。

HookHandler

AMS 检查完毕后,需要将 ProxyActivity 再换成原本要启动的插件 Activity,我们选择在 ActivityThread 中的 Handler mH 处理 LAUNCH_ACTIVITY 消息时将 Activity 替换回来。为了不影响 mH 原本的执行过程,需要在 Handler.Callback 中做替换工作,而不是直接去修改 handleMessage():

Handler.java:

	final Callback mCallback;
	
	public void dispatchMessage(Message msg) {
		// Message 的 callback 执行具有最高优先级
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
        	// mCallback 优先级居中
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // handleMessage() 只有当 mCallback.handleMessage() 
            // 返回 false 时才能被执行到,优先级最低
            handleMessage(msg);
        }
    }

为了让 Handler 本身的 handleMessage() 执行不受影响,最合适的方法就是在 mCallback 的 handleMessage() 中执行替换,并且返回 false:

	public static final int LAUNCH_ACTIVITY = 100;

    private Handler.Callback mCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message message) {
            if (message.what == LAUNCH_ACTIVITY) {
                try {
                    // message.obj 实际上是一个 ActivityClientRecord,但是 @hide 的,
                    // 我们不能直接用,只能拿 ActivityClientRecord 的 intent
                    Field intentField = message.obj.getClass().getDeclaredField("intent");
                    intentField.setAccessible(true);
                    Intent intent = (Intent) intentField.get(message.obj);

                    // 之前 Hook AMS 的时候我们把真正启动插件的 Intent 放在 Extra 里了
                    Intent actionIntent = intent.getParcelableExtra("actionIntent");
                    if (actionIntent != null) {
                        intentField.set(message.obj, actionIntent);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    };

再将上面的 mCallback 对象设置给 ActivityThread 中的 Handler——mH 的 mCallback 字段:

	public void hookHandler() {
        try {
            // 1.通过 currentActivityThread() 拿到 ActivityThread 对象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread");
            Object activityThread = currentActivityThreadMethod.invoke(null);

            // 2.拿到 mH 对象
            Field mHField = activityThreadClass.getDeclaredField("mH");
            mHField.setAccessible(true);
            Object mH = mHField.get(activityThread);

            // 3.设置 mH 的 mCallback 字段
            Class<?> handlerClass = Class.forName("android.os.Handler");
            Field mCallbackField = handlerClass.getDeclaredField("mCallback");
            mCallbackField.setAccessible(true);
            mCallbackField.set(mH, mCallback);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

使用前先在 Application 中调用两端 Hook 方法:

	@Override
    public void onCreate() {
        super.onCreate();

        PluginManager pluginManager = PluginManager.getInstance(this);
        String pluginPath = getFilesDir() + File.separator + "hook_plugin-debug.apk";
        pluginManager.loadPlugin(pluginPath);
        pluginManager.hookAMS();
        pluginManager.hookHandler();
    }

启动插件 Activity 时需要指定其 ComponentName:

	public void startPluginActivity(View view) {
        Intent intent = new Intent();
        String pluginPackage = "com.demo.hook.plugin";
        String pluginActivityClass = "com.demo.hook.plugin.PluginActivity";
        intent.setComponent(new ComponentName(pluginPackage, pluginActivityClass));
        startActivity(intent);
    }

在 Application 中调用

经过 HookAMS 和 HookHandler 两步,Hook 式插件化就基本上完成了,再加上上一篇介绍过的加载插件过程:

	public void loadPluginForHook(String pluginPath) {
        if (TextUtils.isEmpty(pluginPath)) {
            Log.e(TAG, "插件路径不能拿为空!");
        }

        File pluginFile = new File(pluginPath);
        if (!pluginFile.exists()) {
            Log.e(TAG, "插件包不存在!");
        }

        try {
            // 1.获取宿主的 dexElements
            Class<?> clazz = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListField = clazz.getDeclaredField("pathList");
            pathListField.setAccessible(true);

            Class<?> dexPathListClass = Class.forName("dalvik.system.DexPathList");
            Field dexElements = dexPathListClass.getDeclaredField("dexElements");
            dexElements.setAccessible(true);

            ClassLoader pathClassLoader = mContext.getClassLoader();
            Object dexPathList = pathListField.get(pathClassLoader);
            Object[] hostElements = (Object[]) dexElements.get(dexPathList);

            // 2.获取插件的 dexElements
            DexClassLoader dexClassLoader = new DexClassLoader(pluginPath,
                    mContext.getCacheDir().getAbsolutePath(), null, pathClassLoader);
            Object pluginPathList = pathListField.get(dexClassLoader);
            Object[] pluginElements = (Object[]) dexElements.get(pluginPathList);

            // 3.合并到新的 Element[] 中,先创建一个新数组
            Object[] newElements = (Object[]) Array.newInstance(hostElements.getClass().getComponentType(),
                    hostElements.length + pluginElements.length);
            System.arraycopy(hostElements, 0, newElements, 0, hostElements.length);
            System.arraycopy(pluginElements, 0, newElements, hostElements.length, pluginElements.length);

            // 4.赋值
            dexElements.set(dexPathList, newElements);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

最后在 Application 中调用这三个方法即可:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        PluginManager pluginManager = PluginManager.getInstance(this);
        String pluginPath = getFilesDir() + File.separator + "hook_plugin-debug.apk";
        // 加载插件类
        pluginManager.loadPluginForHook(pluginPath);
     	// HookAMS
        pluginManager.hookAMS();
        // HookHandler
        pluginManager.hookHandler();
    }
}

2.3 兼容问题

插件化一个最大的问题就是兼容问题,特别是要反射系统源码时,兼容问题体现尤为突出,只要 Hook 的源码随着系统升级发生了改动,就要对新系统进行适配,这种适配也是插件化成本高的原因之一。

比如说,我们上面的例子在 8.0(API 26)上的系统是可以运行的,但是到了 9.0 就不行了,因为 Handler 启动 Activity 的流程变了,那么 HookHandler 的过程就要针对 9.0 进行适配。

9.0 将 Activity 的生命周期状态都用状态模式管理了,相应的在 ActivityThread 的 Handler 中,处理生命周期不再是用各种消息区分,而是统一用 EXECUTE_TRANSACTION 这个消息处理生命周期,所以 Hook Handler 的代码也要做出对应的更改。

先附上从 AMS 出来后,启动流程的时序图:

篇幅有限,仅说重要部分。首先,从 AMS 出来,会调用到 ActivityStackSupervisor 的 realStartActivityLocked():

	final ActivityManagerService mService;
	
	final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
            boolean andResume, boolean checkConfig) throws RemoteException {
        ...
        // 给 ClientTransaction 添加 Callback,内容类型是 LaunchActivityItem,
        // LaunchActivityItem 就是执行 LaunchActivity 动作的 ClientTransactionItem 的子类,
        // new Intent(r.intent) 就是启动 Activity 的那个 Intent,被封装在 LaunchActivityItem
        clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                        System.identityHashCode(r), r.info,
                        mergedConfiguration.getGlobalConfiguration(),
                        mergedConfiguration.getOverrideConfiguration(), r.compat,
                        r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
                        r.persistentState, results, newIntents, mService.isNextTransitionForward(),
                        profilerInfo));
        ...
		// Schedule transaction.
        mService.getLifecycleManager().scheduleTransaction(clientTransaction);
        ...
	}

通过 AMS 拿到 ClientLifecycleManager 再开始生命周期调度:

	void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
		// 通过 ClientTransaction 的 getClient() 获取 IApplicationThread 类型的客户端
        final IApplicationThread client = transaction.getClient();
        transaction.schedule();
        // 如果 client 是 Binder 实例需要回收
        if (!(client instanceof Binder)) {
            transaction.recycle();
        }
    }

ClientTransaction 内维护着一个客户端 IApplicationThread 对象:

	/** Target client. */
    private IApplicationThread mClient;

	public IApplicationThread getClient() {
        return mClient;
    }

	public void schedule() throws RemoteException {
		// 调用 IApplicationThread 的 scheduleTransaction() 由 AMS 回到客户端 app
        mClient.scheduleTransaction(this);
    }

调用 IApplicationThread 的 scheduleTransaction() 经过 IPC 实际上调用的是客户端 app 中 ActivityThread 的内部类 ApplicationThread 的 scheduleTransaction():

		@Override
        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
            ActivityThread.this.scheduleTransaction(transaction);
        }

ApplicationThread 继承了 ClientTransactionHandler,scheduleTransaction() 是 ClientTransactionHandler 中的方法,ApplicationThread 没有重写,所以来到 ClientTransactionHandler:

	/** Prepare and schedule transaction for execution. */
	void scheduleTransaction(ClientTransaction transaction) {
        transaction.preExecute(this);
        // 发消息让 H 处理
        sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
    }

H 的 handleMessage() 处理消息 EXECUTE_TRANSACTION:

	// An executor that performs multi-step transactions.
    private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);

	class H extends Handler {
	
		public static final int EXECUTE_TRANSACTION = 159;

		public void handleMessage(Message msg) {
            switch (msg.what) {
            	case EXECUTE_TRANSACTION:
            		// msg.obj 是一个 ClientTransaction 对象
                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
                    // 执行 ClientTransaction
                    mTransactionExecutor.execute(transaction);
                    if (isSystem()) {
                        transaction.recycle();
                    }
                    break;
            }
        }
	}

TransactionExecutor 的 execute():

	public void execute(ClientTransaction transaction) {
        final IBinder token = transaction.getActivityToken();
		// 执行生命周期事务
        executeCallbacks(transaction);

        executeLifecycleState(transaction);
        mPendingActions.clear();
    }

	// 循环遍历回调请求的所有状态,并在适当的时间执行它们
	public void executeCallbacks(ClientTransaction transaction) {
		// 获取回调列表,拿到的是 ClientTransaction 中的 
		// List<ClientTransactionItem> mActivityCallbacks
		final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
        if (callbacks == null) {
            return;
        }
		...
		final int size = callbacks.size();
        for (int i = 0; i < size; ++i) {
            final ClientTransactionItem item = callbacks.get(i);
            ...
			// 调用 ClientTransactionItem 的 execute()
            item.execute(mTransactionHandler, token, mPendingActions);
            item.postExecute(mTransactionHandler, token, mPendingActions);
            ...
        }
		...
	}

ClientTransactionItem 有众多子类,如 LaunchActivityItem 负责启动一个 Activity(类似的还有 ResumeActivityItem、StopActivityItem 等等):

/**
 * Request to launch an activity.
 * @hide
 */
public class LaunchActivityItem extends ClientTransactionItem {

    private Intent mIntent;

	@Override
    public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        // 还是把要启动的 Activity 信息封装在 ActivityClientRecord 中
        ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
                mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
                mPendingResults, mPendingNewIntents, mIsForward,
                mProfilerInfo, client);
        client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
    }
}

抽象类 ClientTransactionHandler 的子类是 ActivityThread,所以最后是调用 ActivityThread 的 handleLaunchActivity() 去启动 Activity 的。

这样源码看下来,可以梳理一下针对 9.0 系统 Hook Handler 的思路:

  1. 要替换的 Intent 被封装在 LaunchActivityItem 中
  2. LaunchActivityItem 被保存在 ClientTransaction 的 mActivityCallbacks 列表中
  3. ClientTransaction 是 H 类 handleMessage() 处理 EXECUTE_TRANSACTION 消息时能获取到的参数 msg.obj

所以更新 Hook Handler 的代码如下:

	public static final int LAUNCH_ACTIVITY = 100;
    public static final int EXECUTE_TRANSACTION = 159;

    public static final String CLASS_NAME_LAUNCH_ACTIVITY_ITEM = "android.app.servertransaction.LaunchActivityItem";

    private final Handler.Callback mCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message message) {
            switch (message.what) {
                case LAUNCH_ACTIVITY:
                	// LAUNCH_ACTIVITY 是给 9.0 之前用的,前面写过,这里省略
                    ...
                    break;
                case EXECUTE_TRANSACTION:
                    try {
                        // 拿到 mActivityCallbacks 列表
                        Field mActivityCallbacksField = message.obj.getClass().getDeclaredField("mActivityCallbacks");
                        mActivityCallbacksField.setAccessible(true);
                        ArrayList mActivityCallbacks = (ArrayList) mActivityCallbacksField.get(message.obj);

                        // 从 mActivityCallbacks 中找到 LaunchActivityItem
                        Object launchActivityItem = null;
                        for (Object callback : mActivityCallbacks) {
                            if (CLASS_NAME_LAUNCH_ACTIVITY_ITEM.equals(callback.getClass().getName())) {
                                launchActivityItem = callback;
                            }
                        }

                        // 取出 Intent 并替换
                        if (launchActivityItem != null) {
                            Field mIntentField = launchActivityItem.getClass().getDeclaredField("mIntent");
                            mIntentField.setAccessible(true);
                            Intent intent = (Intent) mIntentField.get(launchActivityItem);
                            Intent actionIntent = intent.getParcelableExtra("actionIntent");
                            if (actionIntent != null) {
                                mIntentField.set(launchActivityItem, actionIntent);
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    break;
            }

            return false;
        }
    };

三、LoadedApk 式

LoadedApk 式并不是一套完整的插件化方式,它只是针对 Hook 式的部分流程做出了改进,进一步讲,它只是优化了 Hook 式中,生成可以加载插件类的 ClassLoader 的方法,HookAMS 和 HookHandler 的过程几乎没有变化。

Hook 式是将所有插件的 dexElements 都融合到宿主所使用的 PathClassLoader 中,当插件数量过多时,就会使得宿主的 dexElements 过大而占用过多内存。

但是 LoadedApk 式加载插件类入侵系统比 Hook 式更强,会使用到更多的反射和动态代理,相比于 Hook 式要更繁琐,这里也仅就做开拓思路用。

那么 LoadedApk 式是如何避免上述情况发生的呢?还是回到源码,在 ActivityThread 中 mH 的 handleMessage() 在处理 LAUNCH_ACTIVITY 消息时:

		public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
					// 这次关注点在 getPackageInfoNoCheck() 上
                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                } break;
            }
    	}

ActivityClientRecord 的 packageInfo 属性是通过 getPackageInfoNoCheck() 获取的,这个 packageInfo 是一个 LoadedApk 对象:

	// 缓存,key 是应用包名,value 是 LoadedApk 的弱引用
	final ArrayMap<String, WeakReference<LoadedApk>> mPackages
            = new ArrayMap<String, WeakReference<LoadedApk>>();
    final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages
            = new ArrayMap<String, WeakReference<LoadedApk>>();
	
	public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
            CompatibilityInfo compatInfo) {
        return getPackageInfo(ai, compatInfo, null, false, true, false);
    }
	
	// 获取包信息的方法,返回值类型为 LoadedApk。 
	private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
            boolean registerPackage) {
        // 判断是否是不同用户
        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
        synchronized (mResourcesManager) {
        	// 先去缓存中找 LoadedApk 的弱引用
            WeakReference<LoadedApk> ref;
            if (differentUser) {
                // 不支持跨用户缓存
                ref = null;
            } else if (includeCode) {
                ref = mPackages.get(aInfo.packageName);
            } else {
                ref = mResourcePackages.get(aInfo.packageName);
            }

			// 初始化 packageInfo,若缓存中有就直接拿,否则先置 null
            LoadedApk packageInfo = ref != null ? ref.get() : null;
            // 如果 packageInfo 为 null,就进入生成阶段,否则最后直接 return 现有值
            if (packageInfo == null || (packageInfo.mResources != null
                    && !packageInfo.mResources.getAssets().isUpToDate())) {
                // 创建 LoadedApk 类型的变量 packageInfo
                packageInfo =
                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
                            securityViolation, includeCode &&
                            (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

                if (mSystemThread && "android".equals(aInfo.packageName)) {
                    packageInfo.installSystemApplicationInfo(aInfo,
                            getSystemContext().mPackageInfo.getClassLoader());
                }

				// 将新建的 packageInfo 放入缓存
                if (differentUser) {
                    // 不支持跨用户缓存
                } else if (includeCode) {
                    mPackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                } else {
                    mResourcePackages.put(aInfo.packageName,
                            new WeakReference<LoadedApk>(packageInfo));
                }
            }
            return packageInfo;
        }
    }

getPackageInfo() 会先去缓存中找,如果没找到就创建一个 LoadedApk 类型的变量返回给 ActivityClientRecord 的 packageInfo 字段。

接下来执行 handleLaunchActivity(),由于前面贴过这部分源码了,这次就只贴与本节相关的部分了:

	private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        ...
        Activity a = performLaunchActivity(r, customIntent);
		...
    }
    
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
		...
        Activity activity = null;
        try {
        	// ※获取 LoadedApk 的 ClassLoader,并用它加载 Activity 类
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            ...
        } catch (Exception e) {
			...           
        }
		...
        return activity;
    }

performLaunchActivity() 调用 Instrumentation.newActivity() 创建一个 Activity 对象:

	public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }

加载 Activity 所使用的 ClassLoader 对象正是来自于 getPackageInfo() 的返回值 LoadedApk,且 getPackageInfo() 都会先去缓存 mPackages 中找之前生成的 LoadedApk 对象:

既然宿主使用 LoadedApk 的 ClassLoader 加载 Activity,那么我们可以模仿它,自定义一个 LoadedApk 对象,让它的 ClassLoader 可以加载插件中的类,然后通过反射将它添加到 mPackages 中。

我们可以反射 getPackageInfoNoCheck(),把它返回的 LoadedApk 对象中的 mClassLoader 字段改成一个可以加载插件类的 ClassLoader 即可。

3.1 获取 ApplicationInfo 和 CompatibilityInfo

调用 getPackageInfoNoCheck() 需要 ApplicationInfo 和 CompatibilityInfo 先获取两个参数。PackageParser 的 generateApplicationInfo() 可以得到 ApplicationInfo:

/frameworks/base/core/java/android/content/pm/PackageParser.java:

	/**
     *
     * @param p Package 是 PackageParser 的静态内部类,解析了 AndroidManifest 并保存
     *          了解析结果。可以通过 PackageParser 的 parsePackage() 获取。
     * @param flags 可以直接传 0
     * @param state PackageUserState 对象可以通过 Class 反射后用 newInstance() 得到。
     * @return
     */
    public static ApplicationInfo generateApplicationInfo(Package p, int flags,
            PackageUserState state) {
        return generateApplicationInfo(p, flags, state, UserHandle.getCallingUserId());
    }

执行代码:

	/**
     * 反射执行 PackageParser 的 generateApplicationInfo() 以获取 ApplicationInfo 对象
     */
    private ApplicationInfo getPluginAppInfo() {
        try {
            // 1.调用 parsePackage() 得到 Package 对象
            Class<?> packageParserClass = Class.forName("android.content.pm.PackageParser");
            Method parsePackageMethod = packageParserClass.getDeclaredMethod("parsePackage",
                    File.class, int.class);
            Object packageParser = packageParserClass.newInstance();
            Object packageObject = parsePackageMethod.invoke(packageParser, mPluginFile, PackageManager.GET_ACTIVITIES);

            // 2.创建一个 PackageUserState 对象
            Class<?> packageUserStateClass = Class.forName("android.content.pm.PackageUserState");
            Object packageUserState = packageUserStateClass.newInstance();

            // 3. 调用 generateApplicationInfo()
            Class<?> packageClass = Class.forName("android.content.pm.PackageParser$Package");
            Method generateApplicationInfoMethod = packageParserClass.getDeclaredMethod(
                    "generateApplicationInfo", packageClass, int.class, packageUserStateClass);
            ApplicationInfo applicationInfo = (ApplicationInfo) generateApplicationInfoMethod.invoke(
                    null, packageObject, 0, packageUserState);

            // 4.把 applicationInfo 中的路径设置成插件的路径
            if (mPluginFile != null) {
                String pluginApkPath = mPluginFile.getAbsolutePath();
                applicationInfo.sourceDir = pluginApkPath;
                applicationInfo.publicSourceDir = pluginApkPath;
            }

            return applicationInfo;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

CompatibilityInfo 对象可以直接去拿 CompatibilityInfo 类中的默认对象 DEFAULT_COMPATIBILITY_INFO:

public class CompatibilityInfo implements Parcelable {
    /** default compatibility info object for compatible applications */
    public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() {
    };
}

执行代码:

		Class mCompatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
        Field defaultField = mCompatibilityInfoClass.getField("DEFAULT_COMPATIBILITY_INFO");
        Object mCompatibilityInfo = defaultField.get(null);

3.2 执行 getPackageInfoNoCheck()

反射执行 getPackageInfoNoCheck() 得到一个 LoadedApk 对象,修改它的 ClassLoader 为可以加载插件类的 ClassLoader,并将其添加到 ActivityThread 的缓存 mPackages 中:

	/**
     * 自定义一个 LoadedApk,其 ClassLoader 可以加载插件类
     */
    public void makeCustomLoadedApk(File pluginFile) {
        try {
            // 1.获取 ActivityThread 对象
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Method currentActivityThreadMethod = activityThreadClass.getMethod("currentActivityThread");
            Object activityThread = currentActivityThreadMethod.invoke(null);

            // 2.获取 ApplicationInfo
            ApplicationInfo pluginAppInfo = getPluginAppInfo(pluginFile);

            // 3.获取 CompatibilityInfo
            Class<?> compatibilityInfoClass = Class.forName("android.content.res.CompatibilityInfo");
            Field defaultField = compatibilityInfoClass.getField("DEFAULT_COMPATIBILITY_INFO");
            Object compatibilityInfo = defaultField.get(null);

            // 4.反射调用 getPackageInfoNoCheck()
            Method getPackageInfoNoCheckMethod = activityThreadClass.getMethod("getPackageInfoNoCheck",
                    ApplicationInfo.class, compatibilityInfoClass);
            Object loadedApk = getPackageInfoNoCheckMethod.invoke(activityThread, pluginAppInfo, compatibilityInfo);

            // 5.修改 loadedApk 中的 ClassLoader,替换成可以加载插件中类的 ClassLoader
            ClassLoader classLoader = loadPluginForLoadedApk(pluginFile.getAbsolutePath());
            Class<?> loadedApkClass = Class.forName("android.app.LoadedApk");
            Field mClassLoaderField = loadedApkClass.getDeclaredField("mClassLoader");
            mClassLoaderField.setAccessible(true);
            mClassLoaderField.set(loadedApk,classLoader);

            // 6.将 loadedApk 存入 ActivityThread 的缓存 mPackages 中
            Field mPackagesField = activityThreadClass.getDeclaredField("mPackages");
            mPackagesField.setAccessible(true);
            ArrayMap mPackages = (ArrayMap) mPackagesField.get(activityThread);

            // LoadedApk 是 @hide 的,所以 ArrayMap、WeakReference 无法指定泛型
            WeakReference weakReference = new WeakReference(loadedApk);
            mPackages.put(pluginAppInfo.packageName,weakReference);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

写到这里似乎大功告成了,但运行一下代码发现会抛异常:

	java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.demo.hook.plugin/com.demo.hook.plugin.PluginActivity}: java.lang.ClassNotFoundException: Didn't find class "com.demo.hook.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.demo.loadedapk.host-IUzFcbdJpdwTV6J4MS1xog==/base.apk"],nativeLibraryDirectories=[/data/app/com.demo.loadedapk.host-IUzFcbdJpdwTV6J4MS1xog==/lib/x86, /system/lib, /vendor/lib]]
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2718)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
        ...
	Caused by: java.lang.ClassNotFoundException: Didn't find class "com.demo.hook.plugin.PluginActivity" on path: DexPathList[[zip file "/data/app/com.demo.loadedapk.host-IUzFcbdJpdwTV6J4MS1xog==/base.apk"],nativeLibraryDirectories=[/data/app/com.demo.loadedapk.host-IUzFcbdJpdwTV6J4MS1xog==/lib/x86, /system/lib, /vendor/lib]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:93)
        ...

意思是无法加载插件的类,但可以加载插件的 ClassLoader 的 LoadedApk 对象已经存入 mPackages 中了,是哪里出的问题呢?回看源码,发现有个漏洞我们没有考虑到:

		public void handleMessage(Message msg) {
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                } break;
            }
    	}

想一下这个 ActivityClientRecord 的包名是宿主的还是插件的?答案当然是宿主的,用宿主的包名去 mPackages 去拿到的一定是可以加载宿主的那个 LoadedApk,所以加载插件时才会找不到。

既然这样,在 Handler 处理 LAUNCH_ACTIVITY 消息时,我们就要对 ActivityClientRecord 对象的包名加以处理:

	public static final int LAUNCH_ACTIVITY = 100;

    private Handler.Callback mCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(@NonNull Message message) {
            if (message.what == LAUNCH_ACTIVITY) {
                try {
                    // message.obj 实际上是一个 ActivityClientRecord,但是 @hide 的,
                    // 我们不能直接用,只能一步直接拿 ActivityClientRecord 的 intent
                    Field intentField = message.obj.getClass().getDeclaredField("intent");
                    intentField.setAccessible(true);
                    Intent intent = (Intent) intentField.get(message.obj);

                    // 之前 Hook AMS 的时候我们把真正启动插件的 Intent 放在 Extra 里了
                    Intent actionIntent = intent.getParcelableExtra("actionIntent");
                    if (actionIntent != null) {
                        intentField.set(message.obj, actionIntent);

                        // 如果使用 LoadedApk 方式加载插件,需要给 ActivityInfo.applicationInfo 做包名区分
                        if (useLoadedApk) {
                            Field activityInfoField = message.obj.getClass().getDeclaredField("activityInfo");
                            activityInfoField.setAccessible(true);
                            ActivityInfo activityInfo = (ActivityInfo) activityInfoField.get(message.obj);

                            if (activityInfo != null) {
                                // actionIntent.getPackage() == null 说明是插件,就要取 Component 的包名
                                activityInfo.applicationInfo.packageName = actionIntent.getPackage() == null ?
                                        actionIntent.getComponent().getPackageName() : actionIntent.getPackage();
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    };

上面代码是直接在 Hook 式插件化的 HookHandler 过程中添加的,if (useLoadedApk) 是额外添加的部分,用来区分插件和宿主的包名。

3.3 绕过 PKMS 检测

再次运行程序,又抛出了新的异常:

	java.lang.RuntimeException: Unable to start activity ComponentInfo{com.demo.hook.plugin/com.demo.hook.plugin.PluginActivity}: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.demo.hook.plugin; is package not installed?
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
        ...
	Caused by: java.lang.RuntimeException: Unable to instantiate application android.app.Application: java.lang.IllegalStateException: Unable to get package info for com.demo.hook.plugin; is package not installed?
        at android.app.LoadedApk.makeApplication(LoadedApk.java:971)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2725)
        ...
     Caused by: java.lang.IllegalStateException: Unable to get package info for com.demo.hook.plugin; is package not installed?
        at android.app.LoadedApk.initializeJavaContextClassLoader(LoadedApk.java:793)
        at android.app.LoadedApk.makeApplication(LoadedApk.java:961)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2725) 
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892) 
        at android.app.ActivityThread.-wrap11(Unknown Source:0) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593) 
        at android.os.Handler.dispatchMessage(Handler.java:105) 
        ...

这是因为插件应用没有安装,所以无法获取到 Package 信息。这是 PKMS 的一个检测机制,上源码看一下这个异常的抛出位置:

/frameworks/base/core/java/android/app/LoadedApk.java:

	public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        ...
        initializeJavaContextClassLoader();
        ...
	}

	private void initializeJavaContextClassLoader() {
		IPackageManager pm = ActivityThread.getPackageManager();
		android.content.pm.PackageInfo pi;
        try {
        	// 应用没安装的话 getPackageInfo() 就返回 null,下面就会抛异常了
            pi = pm.getPackageInfo(mPackageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                    UserHandle.myUserId());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        if (pi == null) {
            throw new IllegalStateException("Unable to get package info for "
                    + mPackageName + "; is package not installed?");
        }
        ...
	}

为了让 getPackageInfo() 在插件上不返回 null,可以使用动态代理监听 IPackageManager 接口的 getPackageInfo(),当对插件执行该方法时返回一个 PackageInfo:

	public void hookGetPackageInfo() {
        try {
            // 反射获取 ActivityThread 内的静态变量 sPackageManager
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            Object sPackageManager = sPackageManagerField.get(null);

            Class<?> iPackageManagerClass = Class.forName("android.content.pm.IPackageManager");
            Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
                    new Class[]{iPackageManagerClass},
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //
                            if ("getPackageInfo".equals(method.getName())) {
                                return new PackageInfo();
                            }
                            return method.invoke(sPackageManager, args);
                        }
                    });

            sPackageManagerField.set(null, proxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

最后也是在 Application 中调用这些方法,hookAMS() 与 hookHandler() 与 Hook 式基本相同,只是加载插件类的方式发生了变化:

	@Override
    public void onCreate() {
        super.onCreate();

        PluginManager pluginManager = PluginManager.getInstance(this);
        String pluginPath = getFilesDir() + File.separator + "hook_plugin-debug.apk";

        pluginManager.hookAMS();
        pluginManager.hookHandler();
        if (useLoadedApk) {
            // 使用 LoadedApk 式的 ClassLoader 加载插件
            pluginManager.makeCustomLoadedApk(new File(pluginPath));
            pluginManager.hookGetPackageInfo();
        } else {
            // 使用 Hook 式加载插件类
            pluginManager.loadPluginForHook(pluginPath);
        }
    }

Demo 参考代码:PluginDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值