Plug-in APK dynamic loading

Plug-in related concepts:

Understand the concept according to the difference between componentization and plug-in

The difference between componentization and plug-in

Componentization: It is to divide an APP into multiple modules, and each module is a module. During the development process, we can make these components depend on each other or debug some components separately, but when the final release, these components will be merged into One unified APK.

Plug-in: splitting the entire APP into many modules, each module is an APK (each module of componentization is a lib), and the host APK and plug-in APK are packaged separately when finally packaged, and the plug-in APK is downloaded dynamically Send to the host APK .

Advantages of plug-in

  • Reduce the size of the installed APK and download modules on demand
  • Dynamically update plugins
  • The host and plug-ins are compiled separately to improve team development efficiency
  • Solution number exceeds 65535 problems

Plug-in framework comparison 

Plug-in implementation idea 

 Using plug-in will inevitably have host apk and plug-in apk. If you want to use plug-in things in the host, you will face the following three problems

  • How to load resources
  • How to dynamically load classes
  • How to start the component

How to load plugin resources? (Dynamic loading of res)

When the plug-in apk is dynamically loaded, it will not follow the normal application initialization process, and the resource files will not be loaded into the resources of the host apk, and need to be loaded manually.

Implement a resource to obtain the resources of the plug-in. Before creating the resource, we first instantiate an AssetManager object, then call the addAssetPath method through reflection, set the address of our plug-in apk, and finally pass Resources (AssetManager assets, DisplayMetrics metrics, Configuration config) method to create a new resource (the resource only contains the res resource in the plug-in apk)

/**
     * 加载资源
     *
     * @return
     */
    public Resources loadResources(Context context) throws Exception {
        AssetManager pluginAssets = AssetManager.class.newInstance();
        Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
        String apkPath = context.getExternalFilesDir(null).getAbsolutePath() + "/plugin-debug.apk";
        addAssetPathMethod.invoke(pluginAssets, apkPath);
        addAssetPathMethod.setAccessible(true);
        Resources pluginRes = new Resources(pluginAssets, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());

        return pluginRes;
    }

Unify the entry of getResource, application inherits ContextWrapper, ContextWrapper has getResources method, we need to rewrite

public class MyApplication extends Application {
    private Resources resources;

    @Override
    public void onCreate() {
        super.onCreate();
        PluginManager pluginManager = PluginManager.getInstance(this);
        try {
            this.resources = pluginManager.loadResources();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    @Override
    public Resources getResources() {
        // 插件资源不为空就用插件的,否则用默认的
       return resources == null ? super.getResources(): resources;
    }
}

Similarly, for the activity, you need to write a base class to override getResources, and get it from the application uniformly.

How to dynamically load classes

This involves dynamic loading of classes. Commonly used class loaders are:

  • BootClassLoader: It is used to load the common classes of the system when the system starts, and the internal class of ClassLoader.

  • PathClassLoader: Loads system classes and application classes, generally not recommended for developers.

  • DexClassLoader: Load dex files and apk or jar containing dex files. Loading from SD card is also supported, which also

    It means that DexClassLoader can not load dex related files when the application is not installed. Therefore, it is hotfix

    The basis of complex and plug-in technology.

Briefly mention the parent delegation mechanism of class loading

Parental delegation is not a parent-subclass relationship at the Java inheritance level. For example, the parent classes of PsathClassLoader and DexClassLoader are both BaseDexClassLoader, and they are brothers.

Advantages of Parental Delegation Mechanism

  • Avoid repeated loading, if loaded, read directly from the cache.
  • It is more secure and prevents developers from modifying system classes.

For specific class loader-related content, move to the Android Plug-in Development Guide - Class Loader_Helan Pig's Blog-CSDN Blog 

You can know that the DexClassLoader (String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) to be used here is
* dexPath just fill in the file location containing dex (in-app directory)
* optimizedDirectory This is the path where the cache will survive after the dex is loaded . The Dalvik virtual machine used below Android 5.0 will generate a cache of "filename.dex" in the optimizedDirectory directory, while Android 5.0 and above will generate oat in the directory of the same level as the apk of the patch because it uses the Art runtime folder, and generate "filename.apk.cur.prof to store the cache
* librarySearchPath c, c++ library, in most cases null is enough
* parent The parent loader of the loader, generally the loader of the current execution class.

//加载dex
        File file = new File(Environment.getExternalStorageDirectory(), "plugin-debug.apk");
        //dex -> odex缓存路径
        File odexPath = this.getDir("cache_plugin", Context.MODE_PRIVATE);
        //使用类加载器加载dex
        DexClassLoader dexClassLoader = new DexClassLoader(file.getAbsolutePath(), odexPath.getAbsolutePath(), null, getClassLoader());
        try {
            Class<?> aClass = dexClassLoader.loadClass("com.example.myapplication.plugin.ToastUtils");
            Method showInfo = aClass.getMethod("showInfo", Context.class);
            showInfo.setAccessible(true);
            showInfo.invoke(aClass.newInstance(), PluginActivity.this);
        } catch (Exception e) {
            e.printStackTrace();
        }

Need to add read and write memory permissions. 

However, although the above method can load external plug-ins, the DexClassLoader object must be obtained every time it is called. Although we can write it as a singleton mode, from the perspective of code writing, it is troublesome to get the DexClassLoader of the external dex every time. And if there are many external dex or apk plugins in an application, do programmers need to remember which classes are in each dex? This is obviously unrealistic. So it needs to be done in another way.

------------------- Lazy, the following is transferred from, after seeing this, you can go directly to the blogger's Android plug-in development guide - Hook technology (1) [long Text]_Android hook plug-in development_Dream Blog-CSDN Blog

Load the external dex into the dexElements of the host app

In order to know Appwhere the class is loaded in the host, we need to start from the class loader. Here OtherActivity's getClassLoader()how to start tracking. As shown below:

insert image description here

The Context here is an abstract class, and the getClassLoader method is an abstract method, so we need to find its implementation class ContextImpl. Its source code can be viewed at the link: ContextImpl.java.

// ContextImpl
final LoadedApk mPackageInfo;
@Override
public ClassLoader getClassLoader() {
    return mPackageInfo != null ?
        mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();
}

So you need to understand where the mPackageInfo variable is assigned:

// ContextImpl
static ContextImpl createSystemContext(ActivityThread mainThread) {
    LoadedApk packageInfo = new LoadedApk(mainThread);
    ContextImpl context = new ContextImpl(null, mainThread,
            packageInfo, null, null, 0, null, null, Display.INVALID_DISPLAY);
    context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
            context.mResourcesManager.getDisplayMetrics());
    return context;
}

In other words, this LoadedApk is linked to the Main thread. Here continue to look at the getClassLoader method:

// LoadedApk.java
public ClassLoader getClassLoader(){
    synchronized (this) {
        if (mClassLoader == null) {
            createOrUpdateClassLoaderLocked(null);
        }
        return mClassLoader;
    }
}

As for the createOrUpdateClassLoaderLocked method, ClassLoader.getSystemClassLoader() is actually used to obtain a class loader.
This method will eventually call the ClassLoader.createSystemLoader() method, which is as follows:

private static ClassLoader createSystemClassLoader() {
    String classPath = System.getProperty("java.class.path", ".");
    String librarySearchPath = System.getProperty("java.library.path", "");
    return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}

In other words, what is actually returned is a PathClassLoader class loader. That is to say, after the getClassLoader() method in the ContextImpl file is called, what is returned is a PathClassLoader class loader. Find the source file of PathClassLoader.java: PathClassLoader.java. At a glance, the functions of this class basically come from its parent class BaseDexClassLoader, because it has only two construction methods. So here you can view the source file of BaseDexClassLoader.java.

The code of this file is also relatively simple, as follows:

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;
    
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String librarySearchPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
    ...
}

It can be seen from the above code that the search is actually searched from its attribute field DexPathList. That is to say, it is actually necessary to further search for the source code of this class here, because there are many codes in the class DexPathList.java, so here we only look at the pathList.findClass method.

// DexPathList
public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

Observing the above code, we can know that when findClass is executed, it is actually searched in dexElements. And this dexElements is directly defined as an array:

private Element[] dexElements;

So if we can add the dexElements in the dex or apk file of the external plug-in to the dexElements of the host app, we can complete the expectation.

May wish to express the above logic with a sequence diagram:

Then a corresponding tool class can be written to complete the above steps. code show as below:

public class LoadUtils {

    private static String pluginPath = "/sdcard/plugin-debug.apk";

    public static void init(Context context) {
        if(context == null) return;
        try {
            // 获取应用程序App的dexElements
            PathClassLoader classLoader = (PathClassLoader) context.getClassLoader();
            Class<?> baseDexClassLoaderClazz = Class.forName("dalvik.system.BaseDexClassLoader");
            Field dexPathListField = baseDexClassLoaderClazz.getDeclaredField("pathList");
            dexPathListField.setAccessible(true);
            Object dexPathListValue = dexPathListField.get(classLoader);
            Field dexElementsField = dexPathListValue.getClass().getDeclaredField("dexElements");
            dexElementsField.setAccessible(true);
            Object dexElementsValue = dexElementsField.get(dexPathListValue);

            // 获取外部插件的dexElements
            DexClassLoader dexClassLoader = new DexClassLoader(pluginPath,
                    context.getDir("plugin", Context.MODE_PRIVATE).getAbsolutePath(),
                    null, context.getClassLoader());

            Object pluginDexPathListValue = dexPathListField.get(dexClassLoader);
            Object pluginDexElementsValue = dexElementsField.get(pluginDexPathListValue);

            // 合并两个dexElements
            int appDexElementsLength = Array.getLength(dexElementsValue);
            int pluginDexElementsLength = Array.getLength(pluginDexElementsValue);
            int newLength = appDexElementsLength + pluginDexElementsLength;

            Class<?> componentType = dexElementsValue.getClass().getComponentType();
            Object newArray = Array.newInstance(componentType, newLength);
            System.arraycopy(dexElementsValue, 0, newArray, 0, appDexElementsLength);
            System.arraycopy(pluginDexElementsValue, 0, newArray, appDexElementsLength, pluginDexElementsLength);

            // 设置新的内容到app的PathList中的Elements[]
            dexElementsField.set(dexPathListValue, newArray);
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

Then the corresponding loading method is:

public class OtherActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_other);

        LoadUtils.init(this);

        try {
            Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.ToastUtils");
            Method showInfo = aClass.getMethod("showInfo", Context.class);
            showInfo.setAccessible(true);
            showInfo.invoke(aClass.newInstance(), OtherActivity.this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

You can directly use the PathClassLoader obtained in the context for reflection.

How to start the component

When loading the classes in the external apk above, what we load is just an ordinary class. In Android, the four major components have certain particularities, because they all need to be registered in the manifest file. For the Activity or Service in the external plug-in, it is impossible to register in the host App, so we need a way to bypass the check of the configuration information in the manifest file when the system is loaded.

Hook startActivity

First you need to figure out what happened after startActivity(intent). So the first thing you need to understand is that AMS (ActivityManagerService) literally means the management service of Activity, but in fact, all four major components are under its management.

AMS is mainly responsible for the startup, switching, scheduling of the four major components in the system, and the management and scheduling of application processes. Its responsibilities are similar to those of the process management and scheduling modules in the operating system. When the initiating process starts or the component starts, the request will be passed to AMS through the Binder communication mechanism, and AMS will perform unified processing.

Since AMS manages the four major components, why not directly hook the desired functions at the AMS layer? Because if Hook can be performed at the AMS layer, it is obviously a virus program, so it is not allowed to do so in Android. So our Hook points can only be in the four major components, because at least we need to ensure that only the current program is affected, and other programs cannot be affected.

Here is an example of ActivityA starting ActivityB:

  • ActivityA sends a message to AMS to start ActivityB;
  • AMS saves the information of ActivityB, and at the same time, AMS checks whether ActivityB is registered in the manifest file, and proceeds to the following steps if it is registered;
  • ActivityA sleeps, AMS notifies ActivityThread to start ActivityB;

从startActivity(intent);Start, you can see the following call process: (The source code here is the version of api 25, which is 7.1!!!!)

insert image description here
That is to say, through the method startActivity(intent);that will eventually request Activitythe class startActivityForResult, two member variables can be seen in the method:

mInstrumentation  // Instrumentation
mMainThread   // ActivityThread

// 对应逻辑摘要
Instrumentation.ActivityResult ar =
    mInstrumentation.execStartActivity(
            this, mMainThread.getApplicationThread(), mToken, this,
            intent, requestCode, options);
    if (ar != null) {
mMainThread.sendActivityResult(
        mToken, mEmbeddedID, requestCode, ar.getResultCode(),
        ar.getResultData());
}

 So first look at the methods Instrumentationin this class execStartActivity.

// Instrumentation
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    ...
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}

 In the execStartActivity method, the target Activity will be started through the ActivityManagerNative.getDefault().startActivity() method. And ActivityManagerNative.getDefault() finally returns an IActivityManager object, which is often called an AMS object. May wish to continue to see how this AMS was obtained, we continue to track:

// ActivityManagerNative
static public IActivityManager getDefault() {
	return gDefault.get();
}

private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
    protected IActivityManager create() {
        IBinder b = ServiceManager.getService("activity");
        if (false) {
            Log.v("ActivityManager", "default service binder = " + b);
        }
        IActivityManager am = asInterface(b);
        if (false) {
            Log.v("ActivityManager", "default service = " + am);
        }
        return am;
    }
};

static public IActivityManager asInterface(IBinder obj) {
    if (obj == null) {
        return null;
    }
    IActivityManager in =
            (IActivityManager)obj.queryLocalInterface(descriptor);
    if (in != null) {
        return in;
    }

    return new ActivityManagerProxy(obj);
}
And for the singleton generic class Singleton here:
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

From the above code, we can know that AMSthe definition here is a singleton object, this singleton is Singleton<T>modified with the above, and the real creation method is newspecified by the time. And gDefaultduring the creation of , IBinderthe and are used ActivityManagerProxyto convert.

Get the AMS instance object

This singleton object gDefault can be obtained through reflection. Then get the mInstance defined as generic, that is, the AMS object. Then you can intercept the startActivity method called by AMS through dynamic proxy. Then here you can simply get the AMS object through reflection. Since AMS is obtained through getDefault() in the ActivityManagerNative.java file, here is:
 

public class HookAMSUtils {

    public static void getActivityManagerService() {
        try {
            Class<?> aClass = Class.forName("android.app.ActivityManagerNative");
            Field getDefault = aClass.getDeclaredField("gDefault");
            getDefault.setAccessible(true);

            // 获取静态的gDefault对象
            Object getDefaultObj = getDefault.get(null);
            // 而实际上AMS在单例Singleton中
            Class<?> singletonClazz = Class.forName("android.util.Singleton");
            Field mInstance = singletonClazz.getDeclaredField("mInstance");
            mInstance.setAccessible(true);
            Object amsObj = mInstance.get(getDefaultObj); // AMS
            Log.e("TAG", "getActivityManagerService: " + amsObj.toString());

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

AMSThat is to say, the singleton  contained in the Hook can be used to get AMSthe object.

The timing diagram of the process analyzed above can be expressed as:

insert image description here Hook startActivity

After the above logical analysis, we know that when one Activitystarts the other , the method will eventually be reached Activityaccording to a series of calls . Here we paste the relevant code again:AMSstartActivity

// Instrumentation
public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    ...
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManagerNative.getDefault()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}

According to the return value type of getDefault() in ActivityManagerNative, we can easily know that the AMS object obtained earlier is an IActivityManager interface object.

And if we need to cheat in AMS, that is, to bypass the registration check of the four major components in the manifest file. Here you need to use the dynamic proxy mode, and then intercept the startActivity method of AMS.

Create AMS proxy object

 Here is the proxy interface IActivityManager.java. So using a dynamic proxy will get a lot of methods when invoking, and here we only need startActivity, which is defined as:

// IActivityManager.java
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
         String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
         ProfilerInfo profilerInfo, Bundle options) throws RemoteException;

Then we can intercept this startActivity method in the dynamic proxy method. In order to achieve the purpose of bypassing the manifest file check, we can register a proxy Activity in the manifest file in advance, and then replace the Activity object in the intercepted startActivity method. For example the following code:

public class HookAMSUtils {

    public static final String ORIGIN_INTENT = "ORIGIN_INTENT";

    public static void getActivityManagerService(Context context, Class<? extends Activity> proxyActivityClazz) {
        try {
            Class<?> aClass = Class.forName("android.app.ActivityManagerNative");
            Field getDefault = aClass.getDeclaredField("gDefault");
            getDefault.setAccessible(true);

            // 获取静态的gDefault对象
            Object getDefaultObj = getDefault.get(null);
            // 而实际上AMS在单例Singleton中
            Class<?> singletonClazz = Class.forName("android.util.Singleton");
            Field mInstance = singletonClazz.getDeclaredField("mInstance");
            mInstance.setAccessible(true);
            Object amsObj = mInstance.get(getDefaultObj); // AMS
            Log.e("TAG", "getActivityManagerService: " + amsObj.toString());

            // 创建AMS的代理对象
            Class<?> aClass1 = Class.forName("android.app.IActivityManager");
            // 得到AMS的代理对象
            Object amsProxy = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{aClass1}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    // 代理方法处理
                    Log.e("TAG", "invoke: startActivity");
                    if (method.getName().equals("startActivity")) {
                        // 查找参数,找到Intent对象
                        int index = 0;
                        for (int i = 0; i < args.length; i++) {
                            if (args[i] instanceof Intent) {
                                index = i;
                                break;
                            }
                        }
                        // 拿到意图
                        Intent oldIntent = (Intent) args[index];
                        String name = oldIntent.getStringExtra("NAME");
                        Log.e("TAG", "invoke: " + name);
                        // 创建一个新的意图,将这个旧的意图添加到新的意图中
                        Intent newIntent = new Intent(context, proxyActivityClazz);
                        // 将旧的意图放入到新的意图中
                        newIntent.putExtra(ORIGIN_INTENT, oldIntent);
                        // 设置startActivity的意图对象为新的意图
                        args[index] = newIntent;
                    }
                    return method.invoke(amsObj, args);
                }
            });
            // 将AMS代理对象设置为原本的AMS对象,
            // 也就是设置ActivityManagerNative.java中属性字段gDefault的值为代理对象
            mInstance.set(getDefaultObj, amsProxy);

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

Of course you need to create a ProxyActivity class registered in the manifest file. Then test in MainActivity:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LoadUtils.init(this);
        HookAMSUtils.getActivityManagerService(this, ProxyActivity.class);
        try {
            Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.MainActivity");
            Log.e("TAG", "onCreate: " + aClass.getName());
            Intent intent = new Intent(MainActivity.this, aClass);
            intent.putExtra("NAME", "123");
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

result:

insert image description here
That is to say, the jump here is replaced by the proxy's Activity object. So we still need to replace the original target com.weizu.plugin.MainActivity somewhere. Of course, replacing the proxy Activity here with the original MainActivity needs to be done in the ActivityThread. The timing diagram of this process can be expressed as:

insert image description here
From the above figure, we can know that Hanlder is used in ActivityThread to send messages. So we can handle the callback interface of the Handler to replace the Activity. Therefore, the first step is to get the instance object of ActivityThread, and then set the method of processing messages as our own method. The ActivityThread defines a static reference of its own, so it is relatively easy to get the object. The corresponding code is:

 

public static void hookActivityThreadToLaunchActivity(){
    try {
        // 得到ActivityThread的对象
        Class<?> activityThreadClazz = Class.forName("android.app.ActivityThread");
        Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
        sCurrentActivityThreadField.setAccessible(true);
        Object activityThreadValue = sCurrentActivityThreadField.get(null);

        // 找到Handler,即mH
        Field mHField = activityThreadClazz.getDeclaredField("mH");
        mHField.setAccessible(true);
        Object mHValue = mHField.get(activityThreadValue);

        // 重新赋值
        Class<?> handlerClazz = Class.forName("android.os.Handler");
        Field mCallBackField = handlerClazz.getDeclaredField("mCallback");
        mCallBackField.setAccessible(true);
        mCallBackField.set(mHValue, new HandlerCallBack());

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

// 因为在ActivityThread中通过Handler来接受消息,
// 所以这里为了替换,就实现其回调接口

private static class HandlerCallBack implements Handler.Callback{

    @Override
    public boolean handleMessage(Message message) {
        // 处理消息
        if(message.what == 100) { // H.LAUNCH_ACTIVITY
            handleLaunchActivity(message);
        }

        return false;
    }

    private void handleLaunchActivity(Message message) {
        try {
            // 得到ActivityClientRecord r对象
            Object r = message.obj;
            // 而在得到ActivityClientRecord中就存储着传进来的Intent意图对象
            // 所以可以先获取到意图,然后修改意图对象
            Field intentField = r.getClass().getDeclaredField("intent");
            // 取出intent的值
            intentField.setAccessible(true);
            Intent newIntent = (Intent) intentField.get(r);
            // 从这个newIntent得到真正的意图
            Intent oldIntent = newIntent.getParcelableExtra(ORIGIN_INTENT);
            Log.e("TAG", "handleLaunchActivity: " + oldIntent.toString());
            if(oldIntent != null){
                // 设置r中的intent为当前的这个oldIntent
                intentField.set(r, oldIntent);
            }

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

Then use when calling:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        LoadUtils.init(this);
        HookAMSUtils.getActivityManagerService(this, ProxyActivity.class);
        HookAMSUtils.hookActivityThreadToLaunchActivity();
    }

    // onClick
    public void jump(View view){
        try {
            Class<?> aClass = getClassLoader().loadClass("com.weizu.plugin.MainActivity");
            Log.e("TAG", "onCreate: " + aClass.getName());
            Intent intent = new Intent(MainActivity.this, aClass);
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

You can click on the text box and then jump.

Guess you like

Origin blog.csdn.net/u013773608/article/details/130184857