前言
在前面两篇我们学习了类加载机制的原理以及如何启动插件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资源的加载,而从进行访问;
从上面流程图可以看出,最终是调用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加载思想,完成插件化方案的实现;
结语
如果以上文章对您有一点点帮助,希望您不要吝啬的点个赞加个关注,您每一次小小的举动都是我坚持写作的不懈动力!ღ( ´・ᴗ・` )