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));
    }

上面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;
   }

}
  • 定义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();
       }

   }

}
  • 插件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")
   }
}
  • 打包插件apk,更新至sdcard目录,自测验证通过;

总结

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

结语

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值