Android插件化开发指南——Hook技术(一)【长文】

文章目录

  • 1. 前言
  • 2. 将外部dex加载到宿主app的dexElements中
  • 3. 插件中四大组件的调用思路
  • 4. Hook
    • 2.1 对startActivity进行Hook
      • 2.1.1 AMS
      • 2.1.2 源码分析
        • 2.1.2.1 得到AMS实例对象
      • 2.1.3 对startActivity进行Hook
        • 2.1.3.1 创建AMS的代理对象
  • 5. References
  • 6. 完整代码

1. 前言

在Android插件化开发指南——插件化技术简介一文中曾提到插件化技术的实现需要使用 Android 系统底层的各种 Hook。在这篇博客中将来简单的介绍下什么是Hook,以及在Android中的一些实践。

Hook中文意思为钩子,在编程中意为钩子函数。Hook原理为在某段SDK源码执行的过程中,通过把原始对象替换为我们的代理对象,我们就可以为所欲为,也就是Hook

其实在上篇Android插件化开发指南——类加载器最后给了一个案例,在案例中我们可以通过DexClassLoader来动态加载外部apkdex等中的字节码问题。这种感觉和插件化很类似,但是确有一个致命的问题没有解决,不妨再次观察下这个加载代码:

public class OtherActivity extends AppCompatActivity {

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


        File file = new File(Environment.getExternalStorageDirectory(), "plugin-debug.apk");
        DexClassLoader dexClassLoader = new DexClassLoader(
                file.getAbsolutePath(),
                getDir("cache_plugin", MODE_PRIVATE).getAbsolutePath(),
                null,
                getClassLoader()
        );
        try {
            Class<?> aClass = dexClassLoader.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();
        }
    }
}

但是需要注意的是,上面这种方式虽然可以加载外部插件,但是需要调用者每次都得到DexClassLoader对象。虽然我们可以写为单例模式,但是从代码的书写角度来讲,每次都需要来得到外部dexDexClassLoader比较麻烦。而且如果在一个应用中有很多个外部dex或者apk插件的时候,难道需要让程序员记住每个dex中有哪些类?这显然不现实。所以需要通过另外一种方式来实现。

2. 将外部dex加载到宿主app的dexElements中

为了知道宿主App中在哪里加载类的,所以需要从类加载器开始看起。这里从OtherActivity中的getClassLoader()方法开始追踪。如下图所示:

在这里插入图片描述

这里的Context为一个抽象类,且getClassLoader方法为一个抽象方法,所以我们需要找到其实现类ContextImpl。其源码可以查看链接:ContextImpl.java。

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

所以需要先了解下mPackageInfo这个变量在哪里赋值:

// 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;
}

也就是说这个LoadedApk是和Main线程挂钩的。这里继续查看getClassLoader这个方法:

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

至于这个createOrUpdateClassLoaderLocked方法中,其实也是使用ClassLoader.getSystemClassLoader()来获取一个类加载器。所以这里就关注于这个方法:

ClassLoader.getSystemClassLoader()

这个方法最终会调用ClassLoader.createSystemLoader()方法,该方法如下:

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());
}

也就是说其实返回的是一个PathClassLoader类加载器。也就是说在ContextImpl文件中的getClassLoader()方法调用之后,返回得到的是一个PathClassLoader类加载器。找到PathClassLoader.java的源文件:PathClassLoader.java。一目了然,这个类的功能基本来自其父类BaseDexClassLoader,因为其只有两个构造方法。所以这里可以查看BaseDexClassLoader.java的源文件。

这个文件的代码也比较简单,如下:

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;
	}
	...
}

从上面的代码中可以看出其实查找是从其属性字段DexPathList中查找。也就是这里其实还需要进一步查找这个类的源码,因为DexPathList.java这个类的代码较多,这里就只查看pathList.findClass方法。

// 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;
}

观察上面的代码可以知道其实在执行findClass的时候,其实是在dexElements中进行查找。而这个dexElements直接定义为一个数组:

private Element[] dexElements;

故而如果我们能够将外部插件的dex或者apk文件中的dexElements加入到宿主appdexElements中就可以完成预期。

不妨将上面的逻辑用时序图来进行表示:

Android插件化开发指南——Hook技术(一)【长文】_第1张图片

那么对应的可以写一个工具类,用于完成上面的步骤。代码如下:

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();
        }
    }
}

那么对应的加载方法为:

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();
        }
    }
}

就可以直接使用context中得到的PathClassLoader来进行反射。结果为:
Android插件化开发指南——Hook技术(一)【长文】_第2张图片
至于其余的细节部分可以参考:Android插件化开发指南——类加载器。

3. 插件中四大组件的调用思路

在上面的加载外部apk中的类的时候,我们加载的只是一个普通的类。而在Android中四大组件具有一定的特殊性,因为都需要在清单文件中注册。对于外部插件中的Activity或者Service等我们却又不可能在宿主App中进行注册,所以实际上更加复杂。因为我们至少需要一种方式可以绕过系统加载的时候对于清单文件中配置信息的检查。

我们知道在实际场景中我们的插件中都会涉及到一些界面的更新,而这个更新过程也就是会涉及到Activity的增加或者修改。所以实际中上面的简单加载外部普通类确实不适用。不妨来个简单的案例,因为在之前导出的plugin_debug.apk中其实包含了一个Activity

Android插件化开发指南——Hook技术(一)【长文】_第3张图片
所以这里可以在app模块中进行测试:

public class OtherActivity extends AppCompatActivity {

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


        File file = new File(Environment.getExternalStorageDirectory(), "plugin-debug.apk");
        DexClassLoader dexClassLoader = new DexClassLoader(
                file.getAbsolutePath(),
                getDir("cache_plugin", MODE_PRIVATE).getAbsolutePath(),
                null,
                getClassLoader()
        );
        try {
            Class<?> aClass = dexClassLoader.loadClass("com.weizu.plugin.MainActivity");
            Intent intent = new Intent(OtherActivity.this, aClass);
            startActivity(intent);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

在这里插入图片描述

这里其实也再次证明了四大组件的特殊性。所以我们需要更加合理的方式来绕开检查。

4. Hook

2.1 对startActivity进行Hook

2.1.1 AMS

要对startActivity进行Hook,那么就首选需要搞清楚在startActivity(intent)之后发生了什么事情。那么首先需要了解的就是AMSActivityManagerService)虽然这个字面意思是Activity的管理服务,但是其实四大组件都归它管。

AMS主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块相类似。当发起进程启动或者组件启动时,都会通过Binder通信机制将请求传递给AMSAMS再做统一处理。

既然AMS管理着四大组件,那么为什么不直接在AMS层对想要的功能进行Hook呢?因为如果可以在AMS层进行Hook,很明显就是病毒程序了,所以在Android中也不允许这么做。所以我们的Hook点只能是在四大组件,因为至少需要保证受影响的只是当前程序,而不能影响别的程序。

这里以ActivityA启动ActivityB为例:

  • ActivityAAMS发送需要启动ActivityB的消息;
  • AMS保存ActivityB的信息,同时AMS要检查ActivityB是否在清单文件中注册,如果注册了才继续下面的步骤;
  • ActivityA休眠,AMS通知ActivityThread去启动ActivityB

2.1.2 源码分析

从自定义的OtherActivity.java文件中的startActivity(intent);出发,可以看见下面的调用流程:

在这里插入图片描述
也就是说通过startActivity(intent);最终会请求Activity类的startActivityForResult方法,在方法中可以看见两个成员变量:

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());
}

那么首先看下Instrumentation这个类中的execStartActivity方法,地址为:Instrumentation.java。

// 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;
}

execStartActivity这个方法中,最终会通过ActivityManagerNative.getDefault().startActivity()方法来启动目标的Activity。而ActivityManagerNative.getDefault()最后返回的其实也就是一个IActivityManager对象,也就是常说的AMS对象。不妨继续看看这个AMS是如何得到的,我们继续追踪:

// 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);
}

而对于这里的单例泛型类Singleton为:

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;
        }
    }
}

从上面的代码中可以知道,这里的AMS定义为单例对象,这个单例使用上面的Singleton来进行修饰,真正的创建方法由new的时候来指定。而在gDefault的创建过程中,使用了IBinderActivityManagerProxy进行转换。

2.1.2.1 得到AMS实例对象

那么如果我们需要通过反射来得到应用程序中的AMS ,那么我们就可以在通过反射来得到这个单例对象gDefault。然后再得到其中定义为泛型的mInstance,也就是AMS对象。然后就可以通过动态代理的方式来拦截AMS调用的startActivity方法。那么这里可以简单通过反射来得到AMS对象,由于AMS是在ActivityManagerNative.java文件中,通过getDefault()得到的,所以这里为:

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();
        }
    }
}

但是很遗憾:
在这里插入图片描述
因为我看的源代码为:Nougat - 7.1.2_r36。可以查看一下Android版本和代号等对应的API级别。可以查看:代号、标记和细分版本号,这里摘要部分:

代号 版本 API 级别
Oreo 8.1.0 API 级别 27
Oreo 8.0.0 API 级别 26
Nougat 7.1 API 级别 25
Nougat 7.0 API 级别 24
Marshmallow 6.0 API 级别 23
Lollipop 5.1 API 级别 22
Lollipop 5.0 API 级别 21

而我的项目中设置的buildToolsVersion30。所以这里还是换为API 级别 25,下载对应的SDK
Android插件化开发指南——Hook技术(一)【长文】_第4张图片
创建一个对应版本的虚拟设备:
Android插件化开发指南——Hook技术(一)【长文】_第5张图片
然后修改gradle文件:

android {
    compileSdkVersion 28
    buildToolsVersion "25.0.3"

    defaultConfig {
        applicationId "com.weizu.myapplication"
        minSdkVersion 25
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    ...

然后继续运行:
在这里插入图片描述

也就是说这里可以Hook容纳AMS的单例得到得到AMS对象。

上面分析的这个过程的时序图可以表示为:

在这里插入图片描述

2.1.3 对startActivity进行Hook

经过上面的逻辑分析,我们知道当一个Activity去启动另一个Activity后,最终根据一系列的调用会到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;
}

在前面得到的AMS对象,根据ActivityManagerNativegetDefault()的返回值类型,很容易我们知道其为IActivityManager 接口对象。

而如果我们需要在AMS中做欺骗,即绕过清单文件中对四大组件的注册检查。这里需要使用动态代理模式,然后拦截AMSstartActivity方法。

2.1.3.1 创建AMS的代理对象

关于动态代理可以查看:Android常见设计模式——代理模式(Proxy Pattern)。

因为这里是代理接口IActivityManager.java,源码地址为:IActivityManager.java。所以使用动态代理在进行invoke的时候会得到很多的方法,而这里我们只需要startActivity,这个方法定义为:

// 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;

那么我们在动态代理方法中,就可以拦截到这个startActivity方法。为了能够做到绕过清单文件检查的目的,我们可以事先在清单文件中注册一个代理的Activity,然后在拦截到的startActivity方法中进行Activity对象的替换即可。比如下面的代码:

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();
        }
    }
}

当然需要创建一个在清单文件中注册的ProxyActivity类。然后在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();
        }
    }
}

结果:
在这里插入图片描述

也就是说这里的跳转替换为了代理的Activity对象。所以我们还需要在某个地方将原本的目标com.weizu.plugin.MainActivity替换回来。当然,具体将这里的代理Activity替换为原本的MainActivity这里需要在ActivityThread中完成。这个过程的时序图可以表示为:

在这里插入图片描述
从上图中可以知道在ActivityThread中使用了Hanlder来发送消息。所以我们可以处理Handler的回调接口来进行Activity的替换。故而首先第一步为得到ActivityThread的实例对象,然后再将处理消息的方法设置为我们自己的方法。而ActivityThread中定义了自己的一个静态引用,故而可以比较容易的得到该对象。对应的代码为:

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();
        }
    }
}

那么在调用的时候使用:

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();
        }
    }
}

即可实现点击文本框然后进行跳转。需要注意的是在跳转到插件的MainActivity后,其实使用的布局的文件还是当前应用的布局文件。其实如果需要完整加载Activity组件,其实还需要解决加载布局文件(资源文件)的问题。这部分内容将放置在下篇进行介绍。


5. References

References

  • 29讲玩转插件化:深入底层分析Android插件化原理,从0到1手写实现360插件化项目架构【1-20】
  • 《Android插件化开发指南》
  • Android插件化开发指南——插件化技术简介
  • Android插件化开发指南——类加载器
  • PathClassLoader.java
  • 代号、标记和细分版本号
  • Android常见设计模式——代理模式(Proxy Pattern)

6. 完整代码

涉及到的两个类的完整代码:

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();
        }
    }

}
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 {
                    // 代理方法处理
                    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];
                        // 创建一个新的意图,将这个旧的意图添加到新的意图中
                        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();
        }
    }

    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();
            }
        }
    }
}

以及调用:

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();
        }
    }
}

清单文件中,注册权限和代理Activity

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<activity android:name=".ProxyActivity"></activity>

你可能感兴趣的:(Android插件化开发指南,Android设计模式,代理模式,startActivity,Hook,动态代理)