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

大话插件化系列目录
插件化(一) 插件化思想与类加载
插件化(二) 插件化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.(Window.java:671)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.policy.PhoneWindow.(PhoneWindow.java:304)
2020-12-01 22:48:23.881 13708-13708/top.zcwfeng.zcwplugin W/System.err:     at com.android.internal.policy.PhoneWindow.(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 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

可以参考项目

你可能感兴趣的:(插件化(三) 插件资源加载)