Android 插件化

     1 插件化的出现主要是由65535问题的出现。用于查分多个dex并动态加载dex来防止65535问题

    2插件化其实就是动态加载,动态加载又包括了代码加载和资源加载。

    3 热修复也是动态加载。


    插件化中动态加载必须解决 代码及资源动态加载,还有生命周期的问题。

    由Activity启动原理 可以知道 代码的加载需要加载dex文件,要用到类PassClasslLoader及DexClassLoader。而资源的加载需要用到AssetManager及隐藏方法addAsssetPath。

  具体步骤 这些步骤需要到Application 中的attachBaseContext中执行。

    1 合并宿主dex文件及插件dex文件 

public static void inject(DexClassLoader loader){
    //拿到本应用的ClassLoader
    PathClassLoader pathLoader = (PathClassLoader) MyApplication.getContext().getClassLoader();
    try {
        //获取宿主pathList
        Object suZhuPathList = getPathList(pathLoader);
        Object chaJianPathList = getPathList(loader);

        Object dexElements = combineArray(
                //获取本应用ClassLoader中的dex数组
                getDexElements(suZhuPathList),
                //获取插件CassLoader中的dex数组
                getDexElements(chaJianPathList));
        setField(suZhuPathList, suZhuPathList.getClass(), "dexElements", dexElements);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

    2 运用反射及Proxy 拦截IActivityManger方法StartActivity方法,替换Intent中的TargetActivity为占坑Activity。绕开Android系统校验。

    

public static void hookActivityManagerNative() throws ClassNotFoundException,
        NoSuchMethodException, InvocationTargetException,
        IllegalAccessException, NoSuchFieldException {

    //获取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);

    // 创建一个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙干活,这里我们使用动态代理
    Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
    Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
            new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
    mInstanceField.set(gDefault, proxy);

}

    3 生成一个Handler.Callback 的继承类 并重写其handMessage方法 把占坑Activity换会之前的TargetActivity。

 public static void hookActivityThreadHandler() throws Exception {

//         先获取到当前的ActivityThread对象
        Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
        //他有一个方法返回了自己
        Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
        currentActivityThreadMethod.setAccessible(true);
        //执行方法得到ActivityThread对象
        Object currentActivityThread = currentActivityThreadMethod.invoke(null);

        // 由于ActivityThread一个进程只有一个,我们获取这个对象的mH
        Field mHField = activityThreadClass.getDeclaredField("mH");
        mHField.setAccessible(true);
        //得到H这个Handler
        Handler mH = (Handler) mHField.get(currentActivityThread);

        Field mCallBackField = Handler.class.getDeclaredField("mCallback");
        mCallBackField.setAccessible(true);
        //设置我们自己的CallBackField
        mCallBackField.set(mH, new ActivityThreadHandlerCallback(mH));

    }

  4  资源文件的加载  要知道插件在使用资源时是默认使用宿主资源 这样就会出现报错。 只要能提供给插件资源就可以了。

     

String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/chajian_demo.apk";

assetManager = AssetManager.class.newInstance();
Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.setAccessible(true);
addAssetPathMethod.invoke(assetManager, apkPath);
Method ensureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
ensureStringBlocks.setAccessible(true);
ensureStringBlocks.invoke(assetManager);
Resources supResource = getResources();
Log.e("Main", "supResource = " + supResource);
newResource = new Resources(assetManager, supResource.getDisplayMetrics(), supResource.getConfiguration());
Log.e("Main", "设置 getResource = " + getResources());
mTheme = newResource.newTheme();
mTheme.setTo(super.getTheme());

    宿主Application提供获取插件资源的方法 供插件调用。

 

@Override
public AssetManager getAssets() {
    return assetManager == null ? super.getAssets() : assetManager;
}

@Override
public Resources getResources() {
    return newResource == null ? super.getResources() : newResource;
}

   在插件的Activity中需要重写 getResources()及getAssets();

  

@Override
public Resources getResources() {
    if(getApplication() != null && getApplication().getResources() != null){
        return getApplication().getResources();
    }
    return super.getResources();
}

@Override
public AssetManager getAssets() {
    if(getApplication() != null && getApplication().getAssets() != null){
        return getApplication().getAssets();
    }
    return super.getAssets();
}

    




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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值