Android插件化原理

Activity 启动过程:

1.startActivity的时候最终会走到AMS的startActivity方法。

2.系统会检查一堆的信息验证这个Activity是否合法。

3.然后会回调ActivityThread的Handler里的 handleLaunchActivity。

4.在这里走到了performLaunchActivity方法去创建Activity并回调一系列生命周期的方法。

5.创建Activity的时候会创建一个LoaderApk对象,然后使用这个对象的getClassLoader来创建Activity。

6.我们查看getClassLoader()方法发现返回的是PathClassLoader,然后他继承自BaseDexClassLoader。

7.然后我们查看BaseDexClassLoader发现他创建时创建了一个DexPathList类型的pathList对象,

然后在findClass时调用了pathList.findClass的方法。

8.然后我们查看DexPathList类中的findClass发现他内部维护了一个Element[] dexElements的dex数组,

findClass时是从数组中遍历查找的。

 

插件化实现步骤:

1. 首先我们通过DexClassloader创建一个我们自己的DexClassloader对象去加载我们的插件apk。

//dex优化后路径
String cachePath = MainActivity.this.getCacheDir().getAbsolutePath();

//插件apk的路径
String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/chajian.apk";

//创建一个属于我们自己插件的ClassLoader,我们分析过只能使用DexClassLoader
DexClassLoader mClassLoader = new DexClassLoader(apkPath, cachePath,cachePath, getClassLoader());   

2. 拿到宿主apk里ClassLoader中的pathList对象和我们Classloader的pathList,因为最终加载时是执行了pathList.findClass方法。

//拿到本应用的ClassLoader
PathClassLoader pathLoader = (PathClassLoader) MyApplication.getContext().getClassLoader();

//获取宿主pathList
Object suZhuPathList = getPathList(pathLoader);

//获取插件pathList
Object chaJianPathList = getPathList(loader);

3. 然后我们拿到宿主pathList对象中的Element[]和我们创建的Classloader中的Element[]

//获取宿主数组
Object suzhuElements = getDexElements(suZhuPathList)

//获取插件数组
Object chajianElements = getDexElements(chaJianPathList)

4. 因为我们要加入一个dex文件,那么原数组的长度要增加,所有我们要新建一个新的Element类型的数组,长度是新旧的和

//获取原数组类型
Class<?> localClass = suzhu.getClass().getComponentType();

//获取原数组长度
int i = Array.getLength(suzhu);

//插件数组加上原数组的长度
int j = i + Array.getLength(chajian);

//创建一个新的数组用来存储
Object result = Array.newInstance(localClass, j);       

5. 将我们的插件dex文件和宿主原来的dex文件都放入我们新建的数组中合并

//一个个的将dex文件设置到新数组中
for (int k = 0; k < j; ++k) {
    if (k < i) {
        Array.set(result, k, Array.get(suzhu, k));
    } else {
        Array.set(result, k, Array.get(chajian, k - i));
    }
}

6. 将我们的新数组设值给pathList对象

setField(suZhuPathList, suZhuPathList.getClass(), "dexElements", result);

7. 代理系统启动Activity的方法,然后将要启动的Activity替换成我们占坑的Activity已达到欺骗系统去检查的目的。

//获取ActivityManagerNative的类
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");

//拿到gDefault字段
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");

gDefaultField.setAccessible(true);

//从gDefault字段中取出这个对象的值
Object gDefault = gDefaultField.get(null);

// gDefault是一个 android.util.Singleton对象; 我们取出这个单例里面的字段
Class<?> singleton = Class.forName("android.util.Singleton");

//这个gDefault是一个Singleton类型的,我们需要从Singleton中再取出这个单例的AMS代理
Field mInstanceField = singleton.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
//ams的代理对象
Object rawIActivityManager = mInstanceField.get(gDefault);

现在我们已经拿到了这个ams的代理对象,现在我们需要创建一个我们自己的代理对象去拦截原ams中的方法。

class IActivityManagerHandler implements InvocationHandler {
    ...

     @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if ("startActivity".equals(method.getName())) {
            Log.e("Main","startActivity方法拦截了");

            // 找到参数里面的第一个Intent 对象
            Intent raw;
            int index = 0;

            for (int i = 0; i < args.length; i++) {
                if (args[i] instanceof Intent) {
                    index = i;
                    break;
                }
            }
            raw = (Intent) args[index];
            //创建一个要被掉包的Intent
            Intent newIntent = new Intent();
            // 替身Activity的包名, 也就是我们自己的"包名"
            String stubPackage = MyApplication.getContext().getPackageName();

            // 这里我们把启动的Activity临时替换为 ZhanKengActivitiy
            ComponentName componentName = new ComponentName(stubPackage, ZhanKengActivitiy.class.getName());
            newIntent.setComponent(componentName);

            // 把我们原始要启动的TargetActivity先存起来
            newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);

            // 替换掉Intent, 达到欺骗AMS的目的
            args[index] = newIntent;
            Log.e("Main","startActivity方法 hook 成功");
            Log.e("Main","args[index] hook = " + args[index]);
            return method.invoke(mBase, args);
        }

        return method.invoke(mBase, args);
    }
}

然后我们使用动态代理去代理上面获取的ams。

// 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活,这里我们使用动态代理,

//动态代理依赖接口,而ams实现与IActivityManager
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");

//返回代理对象,IActivityManagerHandler是我们自己的代理对象,具体代码请下载demo
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
       new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));

//将我们的代理设值给singleton的单例
mInstanceField.set(gDefault, proxy);

8. 等系统检查完了之后,再次代理拦截系统创建Activity的方法将原来我们替换的Activity再次替换回来,已到达启动不在AndroidManifest注册的目的。

try {
    // 把替身恢复成真身
    Field intent = obj.getClass().getDeclaredField("intent");
    intent.setAccessible(true);
    Intent raw = (Intent) intent.get(obj);

    Intent target = raw.getParcelableExtra(AMSHookHelper.EXTRA_TARGET_INTENT);
    raw.setComponent(target.getComponent());
    Log.e("Main","target = " + target);
} catch (Exception e) {
    throw new RuntimeException("hook launch activity failed", e);
}

9. 最后调用我们之前写的这些代码,越早越好,在Application里调用也行,在Activity的attachBaseContext方法中也行。
Demo:https://github.com/suyimin/Plugin

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值