插件化(三) 插件资源加载

大话插件化系列目录
插件化(一) 插件化思想与类加载
插件化(二) 插件化Activity的启动
插件化(三) 插件资源加载

常识回顾

raw文件夹和assets文件夹有什么区别

aw : Android会自动的为这目录中的所有资源文件生成一个ID,这意味着很容易就可以访问到这个资源,甚至在xml 中都是可以访问的,使用ID访问速度是最快的。

assets : 不会生成ID,只能通过AssetManager访问,xml中不能访问,访问速度会慢些,不过操作更加方便。

宿主的资源如何加载

在项目中,我们一般通过 Resources 去访问 res 中的资源,使用 AssetManager 访问 assets 里面的资源。

       String appName = getResources().getString(R.string.app_name);
        InputStream is = getAssets().open("icon.png");

实际上,Resources 类也是通过 AssetManager 类来访问那些被编译过的应用程序资源文件的,不过在访问之前,

AssertManager ---> Resource ---> Context
我们可以通过
Application
Activity
Service
里面去找资源的创建流程

为了方便,我们先拿API 26 开刀

ActivityThread#handleLaunchActivity ---> ActivityThread#performLaunchActivity --> ActivityThread#createBaseContextForActivity 

---> ContextImpl#createActivityContext

--->ResourcesManager#createBaseActivityResources --> ResourcesManager#getOrCreateResources --> ResourcesManager#createResourcesImpl --> ResourcesManager#createAssetManager-->ResourcesManager#assets.addAssetPath

--->Instrumentation
加载资源.png

思路

final ResourcesKey key = new ResourcesKey(
resDir, --- 资源文件

assets.addAssetPath(key.mResDir) --- 把宿主的资源 添加到集合
宿主的代码 --- dexElements

assets.addAssetPath(插件的资源)--- 插件的资源添加到集合
使用资源 ---- 直接使用插件的资源

实现方式:

  1. 插件的资源和宿主的资源直接合并 -- 资源冲突 0x7f0a000a -- aapt 7f -- 70~7e ~ff

2.专门创建一个(Resource)AssetManger 加载插件的资源

问题:

资源冲突

resources.arsc.png

无论宿主或者插件
7f: apk 包的id
0e: 资源类型的 id --- 从01 ++
000a : 同一类型下的 id ---- 从0000 ++

解决:宿主和插件同一个Resource。

宿主和插件分开,插件启动自己主动调用一下,宿主不操作

资源冲突解决思路

尝试思路 1

加载插件资源Resource

public static Resources loadResource(Context context){
        // assets.addAssetPath(key.mResDir) 源码
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            // 让assetManager 对象加载的资源是插件
            Method addAssetPathMethod = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPathMethod.invoke(assetManager, apkPath);

            Resources resources = context.getResources();

            return new Resources(assetManager,resources.getDisplayMetrics(),resources.getConfiguration());
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

如何把资源放到插件中

利用之前系列文章的介绍

我们用双亲委派类加载的时候

Application --- BootClassLoader 加载

而我们运行的只有宿主的,插件的不会,除非我们自己再插件里面写的

冲突原因.png

我们是在宿主获取的插件资源

宿主---Application 重写getResources 拿资源

public
class MyApplication extends Application {

    private Resources mResources;

    @Override
    public void onCreate() {
        super.onCreate();
        LoadUtil.load(this);


        mResources = LoadUtil.loadResource(this);


        HookUtils.hookAMS();
        HookUtils.hookHandler();
    }

    // TODO: 2020/12/1 尝试插件资源的加载
    @Override
    public Resources getResources() {
        return mResources == null ? super.getResources() : mResources;
    }
}

插件 BaseActivity extends Activity

public
class BaseActivity extends Activity {

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

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

-------
使用它,打开我们之前注释的setContentView


//public class MainActivity extends AppCompatActivity {
public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
// TODO: 1.测试启动Activity 加载资源先注释

        // TODO: 2020/12/1 启动资源
                setContentView(R.layout.activity_main);



        Log.e("zcw_plugin" , "onCreate(),启动插件的Activity");

        Log.e("zcw_plugin" , "插件application:" + getApplication());
    }
}

启动后的打印

2020-12-01 18:49:06.681 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 动态代理hookAMS
2020-12-01 18:49:06.692 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 反射hookHandler
2020-12-01 18:49:06.695 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0
2020-12-01 18:49:07.116 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 宿主application:top.zcwfeng.zcwplugin.MyApplication@65c3245
2020-12-01 18:53:04.936 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0
2020-12-01 18:53:04.989 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0
2020-12-01 18:53:05.053 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0
2020-12-01 18:53:05.259 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: onCreate(),启动插件的Activity
2020-12-01 18:53:05.259 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 插件application:top.zcwfeng.zcwplugin.MyApplication@65c3245
2020-12-01 18:53:06.869 11090-11090/top.zcwfeng.zcwplugin E/zcw_plugin: 159 > 9.0

证明我们的插件和宿主Application是一个。

在宿主的MainActiity 中getRsource 拿到的资源是插件的

那面问题产生了

  1. 插件的资源加载影像了宿主的资源 ---- 影像了宿主
  2. 宿主和插件的Application 是同一个 ---- 插件自定义的Application不会执行

所以我们吧loadUtils 放在插件中。创建一个Resource
在插件中getResource 主动加载一下

插件:BaseActivity

public
class BaseActivity extends Activity {


    @Override
    public Resources getResources() {
        // TODO: 2020/12/1 测试方案一
//        if (getApplication() != null && getApplication().getResources() != null) {
//            return getApplication().getResources();
//        }
//        return super.getResources();


        // TODO: 2020/12/1 方案二
        Resources resources = LoadUtils.getResources(getApplication());
        // 如果插件 是单独的app 那么 super.getResources()
        return resources == null ? super.getResources() : resources;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}

插件加载资源

public
class LoadUtils {

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

    private static Resources mResource;

    public static Resources getResources(Context context) {
        if (mResource == null) {
            mResource = loadResource(context);
        }
        return mResource;
    }

    public static Resources loadResource(Context context) {
        // assets.addAssetPath(key.mResDir) 源码
        try {
            AssetManager assetManager = AssetManager.class.newInstance();
            // 让assetManager 对象加载的资源是插件
            Method addAssetPathMethod = AssetManager.class.getMethod("addAssetPath", String.class);
            addAssetPathMethod.invoke(assetManager, apkPath);

            Resources resources = context.getResources();
            // 加载插件资源Resource
            return new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

将插件 apk 放入 我们程序中的路径,验证是可以的,但是还是会有问题。接着分析

AAPT 打包流程

apk打包流程.png

Application Module
Dependencies
compiles
签名
对齐

R.java ----> java Compiler ---> class es ----> dex ----> apkbuilder
aidl
sourcecode

AAPT---->Compile Resource---->apkbuilder---->jarsigner---->sign->zipalign(4K对齐)

.ap_文件
zipalign: 节约 RAM内存。 运行块----对齐后可以用mmap可以和读取内存一样。

官方的流程

apk打包流程2.png

可以framework 修改 aapt,但是我们一般不会这么做。

aapt 代码流程

aapt代码流程.png

回到问题分析思路。BaseActivity我们之前extends 的Activity,现在改成AppCompactActivity,发现报错

2020-12-01 22:48:23.865 13708-13708/top.zcwfeng.zcwplugin W/wfeng.zcwplugi: Accessing hidden method Landroid/content/res/AssetManager;->addAssetPath(Ljava/lang/String;)I (light greylist, reflection)
2020-12-01 22:48:23.869 13708-13708/top.zcwfeng.zcwplugin W/System.err: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference
2020-12-01 22:48:23.876 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at top.zcwfeng.plugin.LoadUtils.loadResource(LoadUtils.java:31)
2020-12-01 22:48:23.877 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at top.zcwfeng.plugin.LoadUtils.getResources(LoadUtils.java:18)
2020-12-01 22:48:23.877 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at top.zcwfeng.plugin.BaseActivity.getResources(BaseActivity.java:23)
2020-12-01 22:48:23.877 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.view.Window.getDefaultFeatures(Window.java:1704)
2020-12-01 22:48:23.880 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.view.Window.<init>(Window.java:671)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.policy.PhoneWindow.<init>(PhoneWindow.java:304)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.policy.PhoneWindow.<init>(PhoneWindow.java:313)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.Activity.attach(Activity.java:7055)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2873)
2020-12-01 22:48:23.890 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
2020-12-01 22:48:23.890 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
2020-12-01 22:48:23.891 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
2020-12-01 22:48:23.891 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
2020-12-01 22:48:23.896 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
2020-12-01 22:48:23.900 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
2020-12-01 22:48:23.901 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.os.Looper.loop(Looper.java:193)
2020-12-01 22:48:23.901 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6669)
2020-12-01 22:48:23.901 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
2020-12-01 22:48:23.902 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
2020-12-01 22:48:23.902 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
-----------------------------------------------------------

2020-12-01 22:48:24.078 13708-13708/top.zcwfeng.zcwplugin D/AppCompatDelegate: Exception while getting ActivityInfo
    android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{top.zcwfeng.zcwplugin/top.zcwfeng.plugin.MainActivity}
        at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:435)
        at androidx.appcompat.app.AppCompatDelegateImpl.isActivityManifestHandlingUiMode(AppCompatDelegateImpl.java:2649)
        at androidx.appcompat.app.AppCompatDelegateImpl.updateForNightMode(AppCompatDelegateImpl.java:2499)
        at androidx.appcompat.app.AppCompatDelegateImpl.applyDayNight(AppCompatDelegateImpl.java:2374)
        at androidx.appcompat.app.AppCompatDelegateImpl.onCreate(AppCompatDelegateImpl.java:494)
        at androidx.appcompat.app.AppCompatActivity.onCreate(AppCompatActivity.java:114)
        at top.zcwfeng.plugin.BaseActivity.onCreate(BaseActivity.java:30)
        at top.zcwfeng.plugin.MainActivity.onCreate(MainActivity.java:11)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

如何解决呢冲突?

aapt --- 单独给插件创建一个 Resource --- 都会产生

都是宿主的 context --- 插件自己创建一个 context -- 绑定 启动插件资源的 Resource

再次修改插件的BaseActivity

public
class BaseActivity extends AppCompatActivity {
    // TODO: 2020/12/1 测试方案三
    protected Context context;

//    @Override
//    public Resources getResources() {
        // TODO: 2020/12/1 测试方案一
//        if (getApplication() != null && getApplication().getResources() != null) {
//            return getApplication().getResources();
//        }
//        return super.getResources();


        // TODO: 2020/12/1 方案二
//        Resources resources = LoadUtils.getResources(getApplication());
//        // 如果插件 是单独的app 那么 super.getResources()
//        return resources == null ? super.getResources() : resources;
//    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Resources resources = LoadUtils.getResources(getApplication());
        // TODO: 2020/12/1 方案三 创建context,替换resource
        context = new ContextThemeWrapper(getBaseContext(),0);
        Class<? extends Context> clazz = context.getClass();
        try {
            Field mResourcesField = clazz.getDeclaredField("mResources");
            mResourcesField.setAccessible(true);
            mResourcesField.set(context, resources);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


}

使用的时候不再用setContentView(R.layout.main) 因为要是用我们自己的插件资源
插件MainActivity

public class MainActivity extends BaseActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
// TODO: 1.测试启动Activity 加载资源先注释

        // TODO: 2020/12/1 启动资源
//                setContentView(R.layout.activity_main);



        Log.e("zcw_plugin" , "onCreate(),启动插件的Activity");

        Log.e("zcw_plugin" , "插件application:" + getApplication());

        View view = LayoutInflater.from(context).inflate(R.layout.activity_main, null);
        setContentView(view);

    }
}

----> context 是我们自己创建的

DroidPlugin 分析

因为很久没更新,我们只能在6.0上看,学习他的思想

替换系统IActivityManager流程

替换系统IActivityManager流程.png

动态代理初始化流程

动态代理初始化流程.png

可以参考项目

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容