Android插件化技术之加载未安装APK

1、概述

Android插件化是一种解决方案,当一个应用发展成一个平台级应用时,就更需要针对各个子业务模块按需动态加载,要做到按需动态加载一种是可以通过H5的方案,另一种就是
针对各个子业务模块单独开发成一个APK,这时候这个平台级应用我们称为宿主,子业务模块APK称为插件,宿主通过反射点击去学习、代理点击去学习等实现hook技术来完成插件APK的免安装加载。

所以必须要先了解这个HOOK技术。

2、HOOK技术

HOOK翻译成钩子,钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。在Android系统中,通俗来讲,就是去阅读源码,然后通过反射或代理的方式去替换/绕过系统原有的调用逻辑,嵌入自己的逻辑代码,从而实现业务功能的扩展。

Android的插件化过程中最重要的步骤就是在宿主应用中去打开未安装插件APK的Activity,要想实现这个过程,通过常规的API方式去调用是无法做到这一点的,startActivity只可以实现打开一个已安装应用的且android:exported="true"的Activity,所以我们必须要想办法去解决这个问题,也就是做到不安装APK也能打开其Activity。

所以,就需要通过源码去了解startActivity的调用过程,找到可以hook的地方,startActivty有三种常用的调用方式,Context.startActivity(intent,…),Activity.startActivity(intent,…),隐式调用。

2.1、根据Android9.0系统源码,查看Activity.startActivity(intent,…)的调用流程

[开始] --> [startActivity] --> [Activity.startActivityForResult]
–> [mInstrumentation.execStartActivity] --> [ActivityManager.getService().startActivity]
–> [ActivityManagerService.startActivity] --> [ActivityManagerService.startActivityAsUser]
–> [ActivityStarter.execute()] --> [startActivity] --> [startActivityUnchecked]
–> [ActivityStackSupervisor.resumeFocusedStackTopActivityLocked] --> [ActivityStack.resumeTopActivityUncheckedLocked]
–> [resumeTopActivityInnerLocked] --> [ClientLifecycleManager.scheduleTransaction]
–> [ClientTransaction.schedule] --> [IApplicationThread.scheduleTransaction]
–> [ActivityThread.this.scheduleTransaction] --> [ClientTransactionHandler.sendMessage]
–> [TransactionExecutor.execute] --> [executeCallbacks] --> [ClientTransactionItem.execute]
–> [LaunchActivityItem.execute] --> [ClientTransactionHandler.handleLaunchActivity]
–> [ActivityThread.handleLaunchActivity] --> [performLaunchActivity]
–> [mInstrumentation.newActivity] --> [mInstrumentation.callActivityOnCreate]
–> [Activity.performCreate] --> [Activity.onCreate] --> [结束]

Activity Instrumentation ActivityManagerService ActivityStarter ActivityStackSupervisor ActivityStack ClientLifecycleManager ClientTransaction IApplicationThread ClientTransactionHandler TransactionExecutor ClientTransactionItem LaunchActivityItem ActivityThread startActivityForResult execStartActivity startActivityAsUser startActivityUnchecked resumeFocusedStackTopActivityLocked resumeTopActivityInnerLocked scheduleTransaction schedule sendMessage execute execute execute handleLaunchActivity handleLaunchActivity performLaunchActivity newActivity、callActivityOnCreate、performCreate onCreate Activity Instrumentation ActivityManagerService ActivityStarter ActivityStackSupervisor ActivityStack ClientLifecycleManager ClientTransaction IApplicationThread ClientTransactionHandler TransactionExecutor ClientTransactionItem LaunchActivityItem ActivityThread

2.2、根据Android9.0系统源码,查看Context.startActivity(intent,…)的调用流程

[开始] --> [getBaseContext.startActivity] --> [ContextImpl.startActivity] --> [mMainThread.getInstrumentation().execStartActivity] --> [ActivityManager.getService().startActivity] --> …之后同上Activity.startActivity… --> [结束]

看源码的网站,随便推荐一个: 点击查看Android系统源码

3、最终解决方案

3.1、实现思路

现在开始来选取Hook的注入点,主要是针对Instrumentation这个类进行代理扩展系统业务功能,execStartActivity执行前,先把传入的插件APK目标Intent替换成我们在宿主项目中创建的已在manifest.xml中注册的替身类ShaowActivity,绕过Manifest注册检查(对应的checkStartActivityResult函数就是做这个检查的),然后我们选择newActivity作为关键点,创建新的Activity时使用插件APK的Activity,创建好插件APK的Activity之后,还需要替换插件的资源(因为现在的资源还是宿主的),故我们选择在callActivityOnCreate这个地方作为替换插件APK的上下文、Resources、Theme等等资源的关键点,具体操作就是需要构建插件的上下文然后通过反射进行替换。(多多参考学习)

3.2、演示效果

在这里插入图片描述

4、最终代码实现

4.1、创建代理类:InstrumentationProxy.java



import android.app.Activity;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PersistableBundle;
import android.util.Log;

import com.xx.escape.plan.ProxyActivity;
import com.xx.escape.plan.plugin.PluginContext;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @author james
 * @date 2024-08-09 ~ 2024-08-23 调试完毕
 * @brief description
 * 标准版
 */
public class InstrumentationProxy extends Instrumentation {
    public static final String TAG = InstrumentationProxy.class.getSimpleName();
    Instrumentation mInstrumentation;

    static final String KEY_TARGET_INTENT = "key_target_intent";

    public InstrumentationProxy(Instrumentation instrumentation) {
        mInstrumentation = instrumentation;
    }

    private PluginContext mPluginContext;

    public void inject(PluginContext pluginContext) {
        this.mPluginContext = pluginContext;
    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        Log.d(TAG, "\n执行了execStartActivity, 参数如下: \n" + "who = [" + who + "], " +
                "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
                "\ntarget = [" + target + "], \nintent = [" + intent +
                "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");

        try {
            //List<ResolveInfo> infoList = who.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_ALL);//这种写法不兼容6.0以下的设备
            boolean isPluginActivity = isUnregisteredManifestActivity((Activity) who, intent);
            //替换系统传递的intent(这个意图里面就是我们要访问的插件APK的Activity界面,直接访问插件APK里面的Activity是会被系统拦截的,因为没有注册manifest.xml,所以我们需要伪装)为我们指定的目标targetIntent,通过伪装成访问宿主的ProxyActivity,跳过manifest.xml的注册检测
            Intent targetIntent;
            if (isPluginActivity) {
                targetIntent = new Intent(who, ProxyActivity.class);
                //塞入到Extra中,执行到后面的newActivity再进行还原
                targetIntent.putExtra(KEY_TARGET_INTENT, intent);
            } else {
                targetIntent = intent;
            }
            Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class,
                    Activity.class, Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, contextThread, token, target, targetIntent, requestCode, options);
        } catch (Exception e) {
            throw new RuntimeException("don't support context start!");
        }
    }

    /**
     * 隐式调用
     *
     * @param intent
     * @return
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ClassNotFoundException
     */
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, String target,
            Intent intent, int requestCode, Bundle options) {
        Log.d(TAG, "\n执行了execStartActivity, 参数如下: \n" + "who = [" + who + "], " +
                "\ncontextThread = [" + contextThread + "], \ntoken = [" + token + "], " +
                "\ntarget = [" + target + "], \nintent = [" + intent +
                "], \nrequestCode = [" + requestCode + "], \noptions = [" + options + "]");

        try {
            boolean isPluginActivity = isUnregisteredManifestActivity((Activity) who, intent);
            //替换系统传递的intent(这个意图里面就是我们要访问的插件APK的Activity界面,直接访问插件APK里面的Activity是会被系统拦截的,因为没有注册manifest.xml,所以我们需要伪装)为我们指定的目标targetIntent,通过伪装成访问宿主的ProxyActivity,跳过manifest.xml的注册检测
            Intent targetIntent;
            if (isPluginActivity) {
                targetIntent = new Intent(who, ProxyActivity.class);
                //塞入到Extra中,执行到后面的newActivity再进行还原
                targetIntent.putExtra(KEY_TARGET_INTENT, intent);
            } else {
                targetIntent = intent;
            }

            Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class,
                    Activity.class, Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            return (ActivityResult) execStartActivity.invoke(mInstrumentation, who, contextThread, token, target, targetIntent, requestCode, options);
        } catch (Exception e) {
            throw new RuntimeException("don't support context start!");
        }
    }

    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        if (intent != null) {
            Intent targetIntent = intent.getParcelableExtra(KEY_TARGET_INTENT);
            //通过插件的ClassLoader来载入一个插件的Activity
            if (targetIntent != null && targetIntent.getComponent() != null) {
                ComponentName componentName = targetIntent.getComponent();
                return mInstrumentation.newActivity(mPluginContext.getClassLoader(), componentName.getClassName(), targetIntent);
            }
        }
        return mInstrumentation.newActivity(cl, className, intent);
    }

    @Override
    public void callActivityOnCreate(Activity activity, Bundle icicle) {
        injectActivity(activity);
        mInstrumentation.callActivityOnCreate(activity, icicle);
    }

    @Override
    public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
        injectActivity(activity);
        mInstrumentation.callActivityOnCreate(activity, icicle, persistentState);
    }


    /**
     * 载入插件的Activity之后,就可以替换这个插件的Activity属性,mResources、mBase、mApplication
     *
     * @param activity
     */
    private void injectActivity(Activity activity) {
        try {

            Intent targetIntent = activity.getIntent().getParcelableExtra(KEY_TARGET_INTENT);
            if (targetIntent == null) {
                Log.d(TAG, "没有目标参数,不是需要启动的插件Activity");
                return;
            }

            boolean isPluginActivity = isUnregisteredManifestActivity(activity, targetIntent);
            if (isPluginActivity) {
                Context base = activity.getBaseContext();

                Reflect.on(activity).set("mBase", mPluginContext);
                Reflect.on(base).set("mResources", mPluginContext.getResources());
                Reflect.on(activity).set("mResources", mPluginContext.getResources());
                Reflect.on(activity).set("mApplication", mPluginContext.getApplicationContext());

                //改变主题 ,解决DecorContentParent.setWindowCallback 空指针的问题 2131689738
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    activity.setTheme(mPluginContext.getTheme());
                } else {
                    //mTheme属性是Activity没有,在Activity的父类ContextThemeWrapper中定义了这个属性 ContextThemeWrapper,也需要设置否则出现空指针的问题
                    Reflect.on(activity).set("mTheme", mPluginContext.getTheme());
                }
                //设置Title直接反射Activity的mTitle,替换成插件的即可,参考xPlugin框架
                Reflect.on(activity).set("mTitle", "插件APK标题");
                //兼容AppCompat ,替换Activity父类的 mThemeResource 这个属性值 old 2131689738/7f0f010a  0x7f0f010a	Theme.AppCompat.NoActionBar	false, true	 ; new 2131689477/7f0f0005 0x7f0f0005	AppFullScreenTheme	false, @ref/0x00000000, true, false
                //当Application 没有设置,只有Activity设置了@style/AppFullScreenTheme,则需要替换调用下面的代码;如果Application 设置了@style/AppFullScreenTheme,则不需要执行下面这段代码
                setActivityResIdTheme(activity);
            }
        } catch (Exception ex) {
            Log.e("InjectActivity", "Error during injectActivity", ex);
        }
    }

    /**
     * 根据是否在Manifest中注册了Activity,来判断是否是插件Activity;
     *
     * @param activity
     * @return
     */
    private boolean isUnregisteredManifestActivity(Activity activity, Intent targetIntent) {
        try {
            ComponentName component = targetIntent.getComponent();
            // 获取包管理器
            PackageManager packageManager = activity.getPackageManager();
            // 获取当前应用的包信息
            PackageInfo packageInfo = packageManager.getPackageInfo(activity.getPackageName(), PackageManager.GET_ACTIVITIES);
            // 获取注册的所有 Activity
            ActivityInfo[] activities = packageInfo.activities;
            if (activities != null) {
                for (ActivityInfo activityInfo : activities) {
                    String activityName = activityInfo.name;
                    String targetIntentName = component.getClassName();
                    if (activityName.equals(targetIntentName)) {
                        Log.d(TAG, "已在宿主项目中注册,不是需要启动的插件Activity");
                        return false;
                    }
                }
            } else {
                Log.i(TAG, "No activities registered in the manifest.");
            }
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return true;
    }

    final Map<String, ActivityInfo> activityMap = new HashMap<>();   // <cl

    /**
     * 为了支持AppCompat.Theme主题,必须要执行此函数,否则会一直报错
     * java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
     * 是从ActivityInfo中取获取theme并设置到Activity中,而不能直接从,activity变量中的mTheme变量是null的值或者说是Activity的样式而不是我们需要的AppCompatActivity中的mTheme样式,所以需要从ActivityInfo中去获取,ActivityInfo右是根据ApplicationInfo来的
     * 参考了xPlugin 框架
     *
     * @param activity
     */
    private void setActivityResIdTheme(Activity activity) {
        PackageManager pm = mPluginContext.getApplicationContext().getPackageManager();
        PackageInfo pkgInfo = pm.getPackageArchiveInfo(mPluginContext.getPluginPath(),
                PackageManager.GET_META_DATA | PackageManager.GET_ACTIVITIES |
                        PackageManager.GET_SERVICES | PackageManager.GET_RECEIVERS |
                        PackageManager.GET_PROVIDERS);
        if (pkgInfo != null) {
            if (pkgInfo.activities != null) {
                ApplicationInfo appInfo = pkgInfo.applicationInfo;
                int appTheme = appInfo.theme;
                int appLabelRes = appInfo.labelRes;
                int appIcon = appInfo.icon;

                for (ActivityInfo info : pkgInfo.activities) {
                    info.theme = info.theme != 0 ? info.theme : appTheme;
                    info.labelRes = info.labelRes != 0 ? info.labelRes : appLabelRes;
                    info.icon = info.icon != 0 ? info.icon : appIcon;
                    String className = info.name.startsWith(".") ?
                            info.packageName + info.name : info.name;
                    activityMap.put(className, info);
                }

                ActivityInfo activityInfo = activityMap.get(activity.getClass().getName());
                if (activityInfo != null) {
                    ActivityInfo activityInfoNew = new ActivityInfo(activityInfo);
                    if (activityInfoNew.theme != 0) {
                        // 虽然ContextThemeWrapper已通过反射更改了Theme,但是Activity又重写了这个类的setTheme(int resId)函数,所以还需要在这个地方设置theme,注意这个theme是int类型的,所以可以从插件apk中的ActivityInfo来取到这个int值,这个地方需要多次尝试
                        // 设置方式1、调用公开访问的API ,推荐使用这种方式
                        activity.setTheme(activityInfoNew.theme);
                        //  设置方式2、也可以使用反射来间接调用
                        /**
                         * Reflect.on(activity).set("mThemeResource", activityInfoNew.theme);
                         Reflect.on(activity).call("initializeTheme");
                         Reflect.on(activity.getWindow()).call("setTheme", activityInfoNew.theme);
                         */
                    }
                }
            }
        }
    }
}


4.2、创建插件上下文类:PluginContext.java

mport android.app.Application;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.view.ContextThemeWrapper;

import java.lang.reflect.Method;

/**
 * @author james
 * @date 2024-08-20
 * @brief description
 * 独立构造一个新的插件的上下文
 * 其实就是自己扩展一个ContextThemeWrapper,来new 出一个插件的上下文
 */
public class PluginContext extends ContextThemeWrapper {

    private Context context;
    private Application application;
    private Resources resources;
    private ClassLoader classLoader;
    private AssetManager assetManager;
    private Resources.Theme theme;

    public String getPluginPath() {
        return pluginPath;
    }

    private String pluginPath;


    public PluginContext(Context context, Application application, ClassLoader classLoader, String pluginPath) {
        super(context, 0);
        this.context = context;
        this.application = application;
        this.classLoader = classLoader;
        this.pluginPath = pluginPath;

        generateResources();
    }

    private void generateResources() {
        try {
            assetManager = AssetManager.class.newInstance();
            Method method = assetManager.getClass().getMethod("addAssetPath", String.class);
            method.setAccessible(true);
            method.invoke(assetManager, pluginPath);

            resources = new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public Context getApplicationContext() {
        return application;
    }

    public PackageManager getPackageManager() {
        return context.getPackageManager();
    }

    @Override
    public AssetManager getAssets() {
        return getResources().getAssets();
    }

    @Override
    public Resources getResources() {
        return resources;
    }

    @Override
    public ClassLoader getClassLoader() {
        return classLoader;
    }

    /**
     * 必须要重新构建这个主题,不然会报一个错误
     * Attempt to invoke interface method 'void androidx.appcompat.widget.DecorContentParent.setWindowCallback(android.view.Window$Callback)' on a null object reference
     *
     * @return
     */
    @Override
    public Resources.Theme getTheme() {
        if (this.theme == null) {
            Resources.Theme oldTheme = super.getTheme();
            this.theme = this.getResources().newTheme();
            this.theme.setTo(oldTheme);
        }
        return this.theme;
    }

}

4.3、宿主中TestActivity调用

public class TestActivity extends AppCompatActivity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		//初始化插件上下文
		initPluginContext();

		//绕过getActivityInfo检查,解除不能打开未安装应用的Activity的限制
        hookPackageManager();
      
		//Context.startActivity(intent)方式,注入InstrumentationProxy代理类
        attachContext();
		//注意Context与Activity的startActivity函数对应的Instrumentation是不一样的,Context.startActivity对应的是ActivityThread中的mInstrumentation,实现类是ContextImpl,而Activity.startActivity对应的是Activity中的mInstrumentation,所以都需要Hook
		
		//Activity.startActivity(intent)方式,注入InstrumentationProxy代理类
        attachActivity();
	}
	
	
	private String pluginPath;//插件APK存放地址,你也可以自定义
    private File nativeLibDir;
    private File dexOutPath;
    private PluginContext pluginContext;//插件上下文,这是非常关键的一个类
    public static String pluginActivityName = "com.xx.plugindemo1.PluginAActivity";//插件APK内的Activity类名
    public static String pluginPackageName = "com.xx.plugindemo1";//插件APK内的包名
    private void initPluginContext() {
        String fileName = "plugin.apk";
        File filesDir = getFilesDir();//路径是:/data/data/< package name >/files/…,插件APK需要拷贝到这个路径下面,这个路径你也可以放在其他目录,只是要注意权限问题
        pluginPath = new File(filesDir, fileName).getAbsolutePath();
        nativeLibDir = new File(filesDir, "pluginlib");
        dexOutPath = new File(filesDir, "dexout");
        if (!dexOutPath.exists()) {
            dexOutPath.mkdirs();
        }
        DexClassLoader pluginClassLoader = new DexClassLoader(pluginPath, dexOutPath.getAbsolutePath(), nativeLibDir.getAbsolutePath(), this.getClassLoader());
        pluginContext = new PluginContext(this, getApplication(), pluginClassLoader, pluginPath);
    }
	
	private void hookPackageManager() {

        // 这一步是因为 initializeJavaContextClassLoader 这个方法内部无意中检查了这个包是否在系统安装
        // 如果没有安装, 直接抛出异常, 这里需要临时Hook掉 PMS, 绕过这个检查.

        try {
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");

            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            Object currentActivityThread = currentActivityThreadMethod.invoke(null);

            // 获取ActivityThread里面原始的 sPackageManager
            Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
            sPackageManagerField.setAccessible(true);
            Object sPackageManager = sPackageManagerField.get(currentActivityThread);

            // 准备好代理对象, 用来替换原始的对象
            Class<?> iPackageManagerInterface = Class.forName("android.content.pm.IPackageManager");
            Object proxy = Proxy.newProxyInstance(iPackageManagerInterface.getClassLoader(),
                    new Class<?>[]{iPackageManagerInterface},
                    new IPackageManagerHookHandler(sPackageManager));

            // 1. 替换掉ActivityThread里面的 sPackageManager 字段
            sPackageManagerField.set(currentActivityThread, proxy);

            // set ApplicationPackageManager#mPM
            // 2. 替换 ApplicationPackageManager里面的 mPM对象
            //这段代码非常关键,解决了代理类中AppCompatDelegateImpl.java:2669)  final ActivityInfo info = pm.getActivityInfo( new ComponentName(mContext, mHost.getClass()), flags); 不能被调用的问题
            PackageManager pm = this.getPackageManager();
            Field mPmField = pm.getClass().getDeclaredField("mPM");
            mPmField.setAccessible(true);
            mPmField.set(pm, proxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static class IPackageManagerHookHandler implements InvocationHandler {
        Object realPackageManager;

        public IPackageManagerHookHandler(Object realPackageManager) {
            this.realPackageManager = realPackageManager;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("getActivityInfo")) {
                Log.d(InstrumentationProxy.TAG, "getActivityInfo============>" + method.getName() + ", args :" + Arrays.deepToString(args));
                ComponentName component = (ComponentName) args[0];
                Intent intent = new Intent();
                intent.setPackage(component.getPackageName());
                intent.setComponent(component);

                if (((ComponentName) args[0]).getClassName().equals(pluginActivityName)) {
                    //intent的信息(主要就是指component信息包名、类名改掉就能跳转到你指定的目标)已被替换了,开始还原为系统已注册的Activity
                    intent.setClassName(component.getPackageName(), ProxyActivity.class.getName());
                    //更改原始的com.xx.escape.plan/com.xx.plugindemo1.PluginAActivity (未注册)变成已注册的(com.xx.escape.plan/com.xx.escape.plan.ProxyActivity)
                    args[0] = intent.getComponent();
                    ActivityInfo info = (ActivityInfo) method.invoke(realPackageManager, args);
                    Log.d(InstrumentationProxy.TAG, "info1========> " + info);
                    return info;
                } else {
                    ActivityInfo info = (ActivityInfo) method.invoke(realPackageManager, args);
                    Log.d(InstrumentationProxy.TAG, "info2========> " + info);
                    return info;
                }
            }
            return method.invoke(realPackageManager, args);
        }
    }
	
	private void attachContext() {
        try {

            // 获取ActivityThread类的全名
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            //根据ActivityThread类的全名访问其静态方法currentActivityThread(),主线程只有一个
            Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            //调用currentActivityThread()方法返回当前的ActivityThread
            Object realActivityThread = currentActivityThreadMethod.invoke(null);

            //拿到原始的mInstrumentation
            Field realInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
            realInstrumentationField.setAccessible(true);
            //注意是根据currentActivityThread来获取的属性值
            Instrumentation realInstrumentation = (Instrumentation) realInstrumentationField.get(realActivityThread);

            //创建重载函数的子类对象
            InstrumentationProxy myInstrumentation = new InstrumentationProxy(realInstrumentation);
            //开始为原来的字段,通过原来的对象赋予新的字段值
            realInstrumentationField.set(realActivityThread, myInstrumentation);

            //TODO 注入初始化
            myInstrumentation.inject(pluginContext);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * 只调用这个方法有缺陷,会导致newActivity不会回调,但是可以结合上面 attachContext() 一起调用,就能回调newActivity,只是创建了两个InstrumentationProxy 代理对象
     */
    private void attachActivity() {
        try {
            // 获取Activity类的全名
            Class<?> activityClass = Class.forName("android.app.Activity");
            //根据Activity类的全名访问其静态方法获取其私有属性mInstrumentation
            Field instrumentationField = activityClass.getDeclaredField("mInstrumentation");

            instrumentationField.setAccessible(true);
            //注意是根据this(当前Activity)来获取的属性值
            Instrumentation realInstrumentation = (Instrumentation) instrumentationField.get(this);

            //创建重载函数的子类对象
            InstrumentationProxy myInstrumentation = new InstrumentationProxy(realInstrumentation);
            //通过this(当前Activity)为原来的字段赋予新的字段值
            instrumentationField.set(this, myInstrumentation);
            //TODO 注入初始化
            myInstrumentation.inject(pluginContext);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
	
	//注册一个xml的点击事件,打开插件的Activity,支持Activity及AppCompatActivity
	public void viewOpenPluginActivity(View view) {
        // 根据apk路径加载apk代码到DexClassLoader中
        try {
            ClassLoader classLoader = pluginContext.getClassLoader();
            Class clazz = classLoader.loadClass(pluginActivityName);
            Intent intent = new Intent(TestBActivity.this, clazz);
            intent.setPackage(pluginPackageName);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            getBaseContext().startActivity(intent);//Context.startActivity这种方式支持
            //startActivity(intent); //Activity.startActivity这种方式也支持
        } catch (Exception e) {
            Log.d(InstrumentationProxy.TAG, "TestBActivity==========>" + e.getMessage());
        }
    }
}

4.4、宿主与插件的主题

建议保持一致,可以避免很多奇怪的问题,最关键的是要支持Theme.AppCompat ,这个主题直接关系到extends AppCompatActivity会不会报错。
插件APK的主题设置< application android:theme=“@style/AppFullScreenTheme” >如下:

<resources>
    <!-- Base application theme. -->
    <style name="Theme.PluginDemo1" parent="Theme.AppCompat.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryDark">@color/purple_700</item>
        <item name="colorAccent">@color/white</item>
    </style>

    <style name="AppFullScreenTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <item name="android:windowNoTitle">false</item>
        <item name="android:windowActionBar">false</item>
        <item name="android:windowFullscreen">true</item>
        <item name="android:windowContentOverlay">@null</item>
    </style>
</resources>

宿主的主题设置< application android:theme=“@style/Theme.EscapePlanDemo” >如下:
宿主可以随便一点,只是插件主题设置需要有要求。

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Theme.EscapePlanDemo" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item>
        <item name="colorPrimaryDark">@color/purple_700</item>
        <item name="colorAccent">@color/white</item>

    </style>
</resources>

4.5、插件依赖库保持简单一点

在插件APK中,每新增一个库都有可能导致不兼容,所以加库需谨慎。
插件的APP的build.gradle 配置如下如下:

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
}

android {
    namespace 'com.xx.plugindemo1'
    compileSdk 32

    defaultConfig {
        applicationId "com.xx.plugindemo1"
        minSdk 19
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

4.6、反射工具类Reflect.java

实现参考如下:


import java.lang.reflect.*;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 一个拥有流畅特性(Fluent-API)的反射工具类, 使用起来就像直接调用一样流畅易懂.
 *
 * @author Lody
 */
public class Reflect {

    private final Object object;
    private final boolean isClass;
    private boolean isSuper;

    private Reflect(Class<?> type) {
        this.object = type;
        this.isClass = true;
    }

    private Reflect(Object object) {
        this.object = object;
        this.isClass = false;
    }

    /**
     * 根据指定的类名构建反射工具类
     *
     * @param name 类的全名
     * @return 反射工具类
     * @throws ReflectException 如果反射出现意外
     * @see #on(Class)
     */
    public static Reflect on(String name) throws ReflectException {
        return on(forName(name));
    }

    /**
     * 从指定的类加载起寻找类,并构建反射工具类
     *
     * @param name        类的全名
     * @param classLoader 需要构建工具类的类的类加载器 loaded.
     * @return 反射工具类
     * @throws ReflectException 如果反射出现意外
     * @see #on(Class)
     */
    public static Reflect on(String name, ClassLoader classLoader) throws ReflectException {
        return on(forName(name, classLoader));
    }

    /**
     * 根据指定的类构建反射工具类
     * <p>
     * 当你需要访问静态字段的时候本方法适合你, 你还可以通过调用 {@link #create(Object...)} 创建一个对象.
     *
     * @param clazz 需要构建反射工具类的类
     * @return 反射工具类
     */
    public static Reflect on(Class<?> clazz) {
        return new Reflect(clazz);
    }

    // ---------------------------------------------------------------------
    // 构造器
    // ---------------------------------------------------------------------

    /**
     * Wrap an object.
     * <p>
     * Use this when you want to access instance fields and methods on any
     * {@link Object}
     *
     * @param object The object to be wrapped
     * @return A wrapped object, to be used for further reflection.
     */
    public static Reflect on(Object object) {
        return new Reflect(object);
    }

    /**
     * 让一个{@link AccessibleObject}可访问.
     *
     * @param accessible
     * @param <T>
     * @return
     */
    public static <T extends AccessibleObject> T accessible(T accessible) {
        if (accessible == null) {
            return null;
        }

        if (accessible instanceof Member) {
            Member member = (Member) accessible;

            if (Modifier.isPublic(member.getModifiers())
                    && Modifier.isPublic(member.getDeclaringClass().getModifiers())) {

                return accessible;
            }
        }
        if (!accessible.isAccessible()) {
            accessible.setAccessible(true);
        }

        return accessible;
    }

    // ---------------------------------------------------------------------
    // Fluent Reflection API
    // ---------------------------------------------------------------------

    /**
     * 将给定字符串的开头改为小写.
     *
     * @param string
     * @return
     */
    private static String property(String string) {
        int length = string.length();

        if (length == 0) {
            return "";
        } else if (length == 1) {
            return string.toLowerCase();
        } else {
            return string.substring(0, 1).toLowerCase() + string.substring(1);
        }
    }

    private static Reflect on(Constructor<?> constructor, Object... args) throws ReflectException {
        try {
            return on(accessible(constructor).newInstance(args));
        } catch (Exception e) {
            throw new ReflectException(e);
        }
    }

    private static Reflect on(Method method, Object object, Object... args) throws ReflectException {
        try {
            accessible(method);

            if (method.getReturnType() == void.class) {
                method.invoke(object, args);
                return on(object);
            } else {
                return on(method.invoke(object, args));
            }
        } catch (Exception e) {
            throw new ReflectException(e);
        }
    }

    /**
     * 取得内部维护的对象.
     */
    private static Object unwrap(Object object) {
        if (object instanceof Reflect) {
            return ((Reflect) object).get();
        }

        return object;
    }

    /**
     * 将Object数组转换为其类型的数组. 如果对象中包含null,我们用NULL.class代替.
     *
     * @see Object#getClass()
     */
    private static Class<?>[] types(Object... values) {
        if (values == null) {
            return new Class[0];
        }

        Class<?>[] result = new Class[values.length];

        for (int i = 0; i < values.length; i++) {
            Object value = values[i];
            result[i] = value == null ? NULL.class : value.getClass();
        }

        return result;
    }

    /**
     * 取得一个类,此操作会初始化类的static区域.
     *
     * @see Class#forName(String)
     */
    private static Class<?> forName(String name) throws ReflectException {
        try {
            return Class.forName(name);
        } catch (Exception e) {
            throw new ReflectException(e);
        }
    }

    private static Class<?> forName(String name, ClassLoader classLoader) throws ReflectException {
        try {
            return Class.forName(name, true, classLoader);
        } catch (Exception e) {
            throw new ReflectException(e);
        }
    }

    /**
     * 如果给定的Class是原始类型,那么将其包装为对象类型, 否则返回本身.
     */
    public static Class<?> wrapper(Class<?> type) {
        if (type == null) {
            return null;
        } else if (type.isPrimitive()) {
            if (boolean.class == type) {
                return Boolean.class;
            } else if (int.class == type) {
                return Integer.class;
            } else if (long.class == type) {
                return Long.class;
            } else if (short.class == type) {
                return Short.class;
            } else if (byte.class == type) {
                return Byte.class;
            } else if (double.class == type) {
                return Double.class;
            } else if (float.class == type) {
                return Float.class;
            } else if (char.class == type) {
                return Character.class;
            } else if (void.class == type) {
                return Void.class;
            }
        }

        return type;
    }

    /**
     * 取得内部维护的实际对象
     *
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> T get() {
        return (T) object;
    }

    /**
     * 设置指定字段为指定值
     *
     * @param name
     * @param value
     * @return
     * @throws ReflectException
     */
    public Reflect set(String name, Object value) throws ReflectException {
        try {
            Field field = field0(name);
            field.setAccessible(true);
            field.set(object, unwrap(value));
            return this;
        } catch (Exception e) {
            throw new ReflectException(e);
        }
    }

    /**
     * @param name name
     * @param <T>  type
     * @return object
     * @throws ReflectException
     */
    public <T> T get(String name) throws ReflectException {
        return field(name).get();
    }

    /**
     * 取得指定名称的字段
     *
     * @param name name
     * @return reflect
     * @throws ReflectException
     */
    public Reflect field(String name) throws ReflectException {
        try {
            Field field = field0(name);
            return on(field.get(object));
        } catch (Exception e) {
            throw new ReflectException(object.getClass().getName(), e);
        }
    }

    private Field field0(String name) throws ReflectException {
        Class<?> type = type();

        // 先尝试取得公有字段
        try {
            return type.getField(name);
        }

        // 此时尝试非公有字段
        catch (NoSuchFieldException e) {
            do {
                try {
                    return accessible(type.getDeclaredField(name));
                } catch (NoSuchFieldException ignore) {
                }

                type = type.getSuperclass();
            } while (type != null);

            throw new ReflectException(e);
        }
    }

    /**
     * 取得一个Map,map中的key为字段名,value为字段对应的反射工具类
     *
     * @return Map
     */
    public Map<String, Reflect> fields() {
        Map<String, Reflect> result = new LinkedHashMap<String, Reflect>();
        Class<?> type = type();

        do {
            for (Field field : type.getDeclaredFields()) {
                if (!isClass ^ Modifier.isStatic(field.getModifiers())) {
                    String name = field.getName();

                    if (!result.containsKey(name))
                        result.put(name, field(name));
                }
            }

            type = type.getSuperclass();
        } while (type != null);

        return result;
    }

    /**
     * 调用指定的无参数方法
     *
     * @param name
     * @return
     * @throws ReflectException
     */
    public Reflect call(String name) throws ReflectException {
        return call(name, new Object[0]);
    }

    /**
     * 调用方法根据传入的参数
     *
     * @param name
     * @param args
     * @return
     * @throws ReflectException
     */
    public Reflect call(String name, Object... args) throws ReflectException {
        Class<?>[] types = types(args);

        try {
            Method method = exactMethod(name, types);
            return on(method, object, args);
        } catch (NoSuchMethodException e) {
            try {
                Method method = similarMethod(name, types);
                return on(method, object, args);
            } catch (NoSuchMethodException e1) {
                throw new ReflectException(e1);
            }
        }
    }

    public Method exactMethod(String name, Class<?>[] types) throws NoSuchMethodException {
        Class<?> type = type();

        try {
            return type.getMethod(name, types);
        } catch (NoSuchMethodException e) {
            do {
                try {
                    return type.getDeclaredMethod(name, types);
                } catch (NoSuchMethodException ignore) {
                }

                type = type.getSuperclass();
            } while (type != null);

            throw new NoSuchMethodException();
        }
    }

    /**
     * 根据参数和名称匹配方法,如果找不到方法,
     */
    private Method similarMethod(String name, Class<?>[] types) throws NoSuchMethodException {
        Class<?> type = type();

        for (Method method : type.getMethods()) {
            if (isSimilarSignature(method, name, types)) {
                return method;
            }
        }

        do {
            for (Method method : type.getDeclaredMethods()) {
                if (isSimilarSignature(method, name, types)) {
                    return method;
                }
            }

            type = type.getSuperclass();
        } while (type != null);

        throw new NoSuchMethodException("No similar method " + name + " with params " + Arrays.toString(types)
                + " could be found on type " + type() + ".");
    }

    private boolean isSimilarSignature(Method possiblyMatchingMethod, String desiredMethodName,
                                       Class<?>[] desiredParamTypes) {
        return possiblyMatchingMethod.getName().equals(desiredMethodName)
                && match(possiblyMatchingMethod.getParameterTypes(), desiredParamTypes);
    }

    /**
     * 创建一个实例通过默认构造器
     *
     * @return Reflect
     * @throws ReflectException
     */
    public Reflect create() throws ReflectException {
        return create(new Object[0]);
    }

    /**
     * 创建一个实例根据传入的参数
     *
     * @param args 参数
     * @return Reflect
     * @throws ReflectException
     */
    public Reflect create(Object... args) throws ReflectException {
        Class<?>[] types = types(args);

        try {
            Constructor<?> constructor = type().getDeclaredConstructor(types);
            return on(constructor, args);
        } catch (NoSuchMethodException e) {
            for (Constructor<?> constructor : type().getDeclaredConstructors()) {
                if (match(constructor.getParameterTypes(), types)) {
                    return on(constructor, args);
                }
            }

            throw new ReflectException(e);
        }
    }

    /**
     * 创建一个动态代理根据传入的类型. 如果我们正在维护的是一个Map,那么当调用出现异常时我们将从Map中取值.
     *
     * @param proxyType 需要动态代理的类型
     * @return 动态代理生成的对象
     */
    @SuppressWarnings("unchecked")
    public <P> P as(Class<P> proxyType) {
        final boolean isMap = (object instanceof Map);
        final InvocationHandler handler = new InvocationHandler() {
            
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String name = method.getName();
                try {
                    return on(object).call(name, args).get();
                } catch (ReflectException e) {
                    if (isMap) {
                        Map<String, Object> map = (Map<String, Object>) object;
                        int length = (args == null ? 0 : args.length);

                        if (length == 0 && name.startsWith("get")) {
                            return map.get(property(name.substring(3)));
                        } else if (length == 0 && name.startsWith("is")) {
                            return map.get(property(name.substring(2)));
                        } else if (length == 1 && name.startsWith("set")) {
                            map.put(property(name.substring(3)), args[0]);
                            return null;
                        }
                    }

                    throw e;
                }
            }
        };

        return (P) Proxy.newProxyInstance(proxyType.getClassLoader(), new Class[]{proxyType}, handler);
    }

    /**
     * 检查两个数组的类型是否匹配,如果数组中包含原始类型,将它们转换为对应的包装类型.
     */
    private boolean match(Class<?>[] declaredTypes, Class<?>[] actualTypes) {
        if (declaredTypes.length == actualTypes.length) {
            for (int i = 0; i < actualTypes.length; i++) {
                if (actualTypes[i] == NULL.class)
                    continue;

                if (wrapper(declaredTypes[i]).isAssignableFrom(wrapper(actualTypes[i])))
                    continue;

                return false;
            }

            return true;
        } else {
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    public int hashCode() {
        return object.hashCode();
    }

    /**
     * {@inheritDoc}
     */
    public boolean equals(Object obj) {
        return obj instanceof Reflect && object.equals(((Reflect) obj).get());

    }

    /**
     * {@inheritDoc}
     */
    public String toString() {
        return object.toString();
    }

    /**
     * 取得我们正在反射的对象的类型.
     *
     * @see Object#getClass()
     */
    public Class<?> type() {
        if (isClass) {
            return (Class<?>) object;
        } else {
            if (isSuper) {
                return object.getClass().getSuperclass();
            }
            return object.getClass();
        }
    }

    public Reflect superClass() {
        isSuper = true;
        return this;
    }

    public static String getMethodDetails(Method method) {
        StringBuilder sb = new StringBuilder(40);
        sb.append(Modifier.toString(method.getModifiers()))
                .append(" ")
                .append(method.getReturnType().getName())
                .append(" ")
                .append(method.getName())
                .append("(");
        Class<?>[] parameters = method.getParameterTypes();
        for (Class<?> parameter : parameters) {
            sb.append(parameter.getName()).append(", ");
        }
        if (parameters.length > 0) {
            sb.delete(sb.length() - 2, sb.length());
        }
        sb.append(")");
        return sb.toString();
    }


    /**
     * 用来表示null的类.
     *
     * @author Lody
     */
    private static class NULL {
    }

    /**
     * 智能调用 但是只调用类本身声明方法 按照优先级 匹配
     * <p>
     * 1.完全匹配
     * 2.形参 Object...
     * 3.名字相同 无参数
     *
     * @param name
     * @param args
     * @return
     * @throws ReflectException
     */
    public Reflect callBest(String name, Object... args) throws ReflectException {
        Class<?>[] types = types(args);
        Class<?> type = type();

        Method bestMethod = null;
        int level = 0;
        for (Method method : type.getDeclaredMethods()) {
            if (isSimilarSignature(method, name, types)) {
                bestMethod = method;
                level = 2;
                break;
            }
            if (matchObjectMethod(method, name, types)) {
                bestMethod = method;
                level = 1;
                continue;
            }
            if (method.getName().equals(name) && method.getParameterTypes().length == 0 && level == 0) {
                bestMethod = method;
            }
        }
        if (bestMethod != null) {
            if (level == 0) {
                args = new Object[0];
            }
            if (level == 1) {
                Object[] args2 = {args};
                args = args2;
            }
            return on(bestMethod, object, args);
        } else {
            throw new ReflectException("no method found for " + name, new NoSuchMethodException("No best method " + name + " with params " + Arrays.toString(types)
                    + " could be found on type " + type() + "."));
        }
    }

    private boolean matchObjectMethod(Method possiblyMatchingMethod, String desiredMethodName,
                                      Class<?>[] desiredParamTypes) {
        return possiblyMatchingMethod.getName().equals(desiredMethodName)
                && matchObject(possiblyMatchingMethod.getParameterTypes());
    }

    private boolean matchObject(Class<?>[] parameterTypes) {
        Class<Object[]> c = Object[].class;
        return parameterTypes.length > 0 && parameterTypes[0].isAssignableFrom(c);
    }
}

4.7、反射异常类ReflectException.java

实现参考如下:

/**
 * @author Lody
 */
public class ReflectException extends RuntimeException {

	public ReflectException(String message, Throwable cause) {
		super(message, cause);
	}

	public ReflectException(Throwable cause) {
		super(cause);
	}
}

5、实践过程中的各种报错及原因

5.1、Failed to resolve attribute at index 1: TypedValue{t=0x2/d=0x101005a a=-1}

Caused by: java.lang.UnsupportedOperationException: Failed to resolve attribute at index 1: TypedValue{t=0x2/d=0x101005a a=-1}
这个错误可能的原因就是宿主与插件库资源ID冲突的,必须要去找出来。

5.2、‘android.view.Window$Callback android.view.Window.getCallback()’ on a null object reference

这个错误就是插件库不支持AppCompatActivity,只能使用Activity来开发,因为这块需要针对Compact的Theme做单独适配。

5.3、Exception while getting ActivityInfo

android.content.pm.PackageManagerNameNotFoundException: ComponentInfo{com.xx.escape.plan/com.xxx.LoadActivity}
at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:435)
at androidx.appcompat.app.AppCompatDelegateImpl.isActivityManifestHandlingUiMode(AppCompatDelegateImpl.java:2669)
这个错误是因为ApplicationPackageManager.getActivityInfo这个函数为空,必须要通过Hook来跳过这段代码的检测,API28要替换掉ApplicationPackageManager#mPM属性,才能进入到getActivityInfo这个地方,否则hook代码不会执行。

5.4、You need to use a Theme.AppCompat theme (or descendant) with this activity.

原因1:这个错误是因为不支持AppCompat.Theme属性造成的,需要调用Activity的setTheme(int resid)的这个函数后,才能让样式生效,其中这个resid的获取方式很神奇,需要从ActivityInfo中来获取。
原因2:插件库引入依赖包必须要注意是否存在样式重复引入的问题,比如只在插件中引入了 androidx.legacy:legacy-support-v4:1.0.0 这个库,宿主没引入,运行插件就会一直报错。

6、参考资料

I、Android插件化实现动态加载Activity笔记
II、xPlugin源码

这是一篇精炼的原创文章,耗费了一定时间,所以阅读前需要熟悉一些插件化的知识,这样才会更容易理解,很多注释在代码中写得非常详细了,很容易看懂。
注意:本文的这种Hook方式,可能无法通过GooglePlay上架审核,要解决此问题可以使用腾讯的Shadow开源项目,但其采用的实现方式不同。






原创不易,求个关注。

在这里插入图片描述

微信公众号:一粒尘埃的漫旅
里面有很多想对大家说的话,就像和朋友聊聊天。
写代码,做设计,聊生活,聊工作,聊职场。
我见到的世界是什么样子的?
搜索关注我吧。

公众号与博客的内容不同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值