• Android插件化学习之加载插件资源


    前言

    在前面两篇我们学习了类加载机制的原理以及如何启动插件activity;
    Android插件化学习之初识类加载机制
    Android插件化学习之启动插件Activity

    前面也提到过启动插件activity时如果直接加载layout资源会出错,那是因为什么原因呢?

    想要了解这个问题的原因,就需要对资源加载流程有一定的认知,接下来我们就来一起看下系统是如何加载resource资源的;

    资源加载流程

    我们先跟进下getString方法是如何访问string资源的;
    Resources.getString

         @NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
            CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
            if (res != null) {
                return res;
            }
            throw new NotFoundException("String resource ID #0x"
                    + Integer.toHexString(id));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    上面mResourcesImpl.getAssets()AssetManager对象,它会根据资源ID查找对应的资源文件名;AssetManager对象既可以通过文件名访问那些被编译过的,也可以访问没有被编译过的资源文件;
    raw文件夹和assets文件夹有什么区别?
    raw:Android会自动的为这目录中的所有资源文件生成一个ID,这意味着很容易就可以访问到这个资源,甚至在xml中都是可以访问的,使用ID访问速度是最快的;
    assets:不会生成ID,只能通过AssetsManager访问,xml中不能访问,访问速度相对慢点,操作更加方便;

    接下来,我们来了解下apk中的资源的加载流程,这样我们才能在不编译插件apk的情况下,实现插件apk资源的加载,而从进行访问;
    加载activity资源流程图
    从上面流程图可以看出,最终是调用ResourceManager.addAssetPath方法完成资源加载的;
    那我们是不是可以手动加载插件资源,从而就可以达到访问插件资源的目的呢?

    实现思路

    1.自己创建一个Resouces对象,用来加载插件资源;
    2.Resources创建需要AssetManager对象;
    3.AssetManager对象创建的时候,需要指定资源路径,那我们就可以指定插件apk路径实现插件资源加载;

    代码流程

    • 定义ResoucesUtils工具类,通过反射实现加载插件apk资源功能;
    public class ResourcesUtils {
       private final String PLUGIN_APK_PATH = "/sdcard/plugin.apk";
    
       private static volatile ResourcesUtils instance;
    
       private ResourcesUtils() {
    
       }
    
    
       public static ResourcesUtils getInstance() {
           if (instance == null) {
               synchronized (ResourcesUtils.class) {
                   if (instance == null) {
                       instance = new ResourcesUtils();
                   }
               }
           }
           return instance;
       }
    
       public Resources loadResources(Context context) {
           try {
               //通过反射调用addAssetPath方法进行插件apk资源加载
               AssetManager assetManager = AssetManager.class.newInstance();
               Method addAssetPathMethod = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
               addAssetPathMethod.invoke(assetManager, PLUGIN_APK_PATH);
               return new Resources(assetManager, context.getResources().getDisplayMetrics(), context.getResources().getConfiguration());
           } catch (Exception e) {
               Log.e(ResourcesUtils.class.getSimpleName(), e.toString());
               e.printStackTrace();
           }
           return null;
       }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 定义BaseActivity,重写onCreate方法,实现自定义Resources对象替换;
    public class BaseActivity extends AppCompatActivity {
       protected Context mContext;
    
       @Override
       protected void onCreate(@Nullable Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           //这里context传getApplication;1.避免递归调用activity.getResources方法;2.避免内存泄漏
           Resources resources = ResourcesUtils.getInstance().loadResources(getApplication());
           //这里避免使用宿主中的context对象,否则会存在资源找不到的情况,因为去找的是宿主中的资源【使用宿主context】,自己新建context,去加载自己的资源
           mContext = new ContextThemeWrapper(getBaseContext(), 0);
           try {
           	//反射替换自定义context的resources对象
               Field mResourcesField = ContextThemeWrapper.class.getDeclaredField("mResources");
               mResourcesField.setAccessible(true);
               mResourcesField.set(mContext, resources);
           } catch (Exception e) {
               e.printStackTrace();
           }
    
       }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 插件PluginActivity加载对应资源xml
    class PluginActivity : BaseActivity() {
       override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           //setContentView(R.layout.activity_plugin)
          	//需要使用自定义的context进行加载,否则调用的还是宿主的context对象,则会报找不到资源异常!!!
           val inflate: View = LayoutInflater.from(mContext).inflate(R.layout.activity_plugin, null)
           setContentView(inflate)
           Log.e(PluginActivity::class.java.simpleName, "onCreate:启动插件Activity")
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 打包插件apk,更新至sdcard目录,自测验证通过;

    总结

    通过上面的学习发现,归根到底还是对源码的阅读以及反射的运用,这里感叹一下,反射真是太强大了,许多黑科技都是通过反射实现的,当然类加载机制原理才是整个插件化的核心思想,只有明白了双亲委派机制,了解dex加载原理才能基于dex加载思想,完成插件化方案的实现;

    结语

    如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )

  • 相关阅读:
    决策树算法学习笔记
    小米汽车上市进入倒计时,已开启内部试驾
    自动化测试教程(20)了解PageObject模式
    java计算机毕业设计基于安卓Android微信的消防安全宣传巡查评估小程序 uniAPP
    java游戏制作-拼图游戏
    【Linux】系统进程的概念
    2023最新SSM计算机毕业设计选题大全(附源码+LW)之java电子书店管理系统ya226
    【C++14保姆级教程】数位分割符、函数返回值推导
    vmware esxi 7 直通GPU配置
    Python:range、np.arange和np.linspace的区别与联系
  • 原文地址:https://blog.csdn.net/a734474820/article/details/126102051