1.插件化原理
原理可以直接查看参考
2.强制指定某种特定区域语言
这部分其实和插件化无关,有时我们的APK有多种strings.xml
语言包,但我们就只想强制使用一种特定语言包,有两种方式:
方式1. 在MainActivity
的attachBaseContext
中改变Context
:
protected void attachBaseContext(Context newBase) {
Context context = LanContextWrapper.wrap(newBase);
super.attachBaseContext(context);
}
--->LanContextWrapper封装如下:
public class LanContextWrapper extends ContextWrapper {
public LanContextWrapper(Context ctx) {
super(ctx);
}
public static ContextWrapper wrap(Context context) {
Locale newLocale = Locale.ENGLISH;
context = getLanContext(context, newLocale);
return new ContextWrapper(context);
}
private static Context getLanContext(Context context, Locale pNewLocale) {
Resources res = context.getApplicationContext().getResources();//1、获取Resources
Configuration configuration = res.getConfiguration();//2、获取Configuration
//3、设置Locale并初始化Context
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(pNewLocale);
LocaleList localeList = new LocaleList(pNewLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
configuration.setLocale(pNewLocale);
context = context.createConfigurationContext(configuration);
}
return context;
}
}
方式2. 强制改变Resources
资源:
private void forceResLocal(Locale locale) {
Context hostContext = HostHelper.getHostAppContext();
Resources hostRes = hostContext.getResources();
Configuration hostConfig = hostRes.getConfiguration();
DisplayMetrics hostMetrics = hostRes.getDisplayMetrics();
hostConfig.setLocale(locale);
hostRes.updateConfiguration(hostConfig, hostMetrics);
}
3.插件Apk资源
3.1 加载成资源及国际化
这里使用/mnt/sdcard/ff.apk
插件,我是直接从assert
目录把ff.apk
拷贝到了/mnt/sdcard
下, 加载成资源及国际化代码如下:
public class PluginHelper {
private static Resources sResources;
public static Resources current(Context context) {
if (sResources != null) {
return sResources;
}
try {
String apkPath = "/mnt/sdcard/ff.apk"; // 插件,自行放置到指定目录
File apkFile = new File(apkPath);
if (apkFile.exists()) {
AssetManager assetManager = createAssetManager(apkPath);
Resources resources = context.getResources();
Configuration hostConfig = resources.getConfiguration();
DisplayMetrics hostMetrics = resources.getDisplayMetrics();
sResources = new Resources(assetManager, hostMetrics, hostConfig);
Configuration pluginConfig = sResources.getConfiguration();
pluginConfig.setTo(hostConfig); // 内部调用了fixUpLocaleList来修正
sResources.updateConfiguration(pluginConfig, hostMetrics);
} else {
sResources = context.getResources();
}
return sResources;
} catch (Throwable e) {
}
return null;
}
}
这里关键代码是 pluginConfig.setTo(hostConfig)
。
不然就可能出现多语言切换bug,表现为:如果插件apk有两种语言包:en
, pt
,我们把手机先切换到pt
,再切换到一种插件没有的语言zh
,如果没有 pluginConfig.setTo(hostConfig)
,sResources
它会显示上一种语言pt
,而不是默认语言en
。
可以单步跟踪看出原始
和new Resource
两者mLocaleList
的差异:
两者对比:
[da_DK,pt_PT,zh_CN_#Hans,en_MY,vi_VN,in_ID,bo_CN,ms_MY]
[pt_PT,da_DK,zh_CN_#Hans,en_MY,vi_VN,in_ID,bo_CN,ms_MY]
pluginConfig.setTo(hostConfig);
所做的操作是内部调用 o.fixUpLocaleList();
来修正。fixUpLocaleList
会判断locale
和mLocaleList.get(0)
是否相同,如果不同,则强制以locale
为起始生成新的mLocaleList
。
最后记得要调用
updateConfiguration
来更新!!!
3.2 插件资源获取
插件的@
资源不能直接获取,只能通过getIdentifier
得到id
再转换,简单封装如下所示:
com.freefirehook
为插件apk的包名。
public static XmlResourceParser loadPluginResLayout(String name) {
Context hostContext = HostHelper.getHostAppContext();
Resources pluginRes = PluginHelper.current(hostContext);
int id = pluginRes.getIdentifier(name, "layout", "com.freefirehook");
return pluginRes.getLayout(id);
}
public static int loadPluginResId(String name) {
Context hostContext = HostHelper.getHostAppContext();
Resources pluginRes = PluginHelper.current(hostContext);
int id = pluginRes.getIdentifier(name, "id", "com.freefirehook");
return id;
}
public static Drawable loadPluginResDrawable(String name) {
Context hostContext = HostHelper.getHostAppContext();
Resources pluginRes = PluginHelper.current(hostContext);
int id = pluginRes.getIdentifier(name, "drawable", "com.freefirehook");
return pluginRes.getDrawable(id);
}
public String loadPluginResString(String name) {
Context hostContext = HostHelper.getHostAppContext();
Resources pluginRes = PluginHelper.current(hostContext);
int strId = pluginRes.getIdentifier(name, "string", "com.freefirehook");
return pluginRes.getString(strId);
}
使用方式如下:
final XmlResourceParser parser = loadPluginResLayout("guide");
ImageView imageView = view.findViewById(loadPluginResId("img_bg"));
closeBtn.setImageDrawable(loadPluginResDrawable("close"));
tvFirst.setText(loadPluginResString("free_fashion_skins"));
当然得到插件资源id
还有一种方式,插件的资源id是做为插件R.java
的子类的静态成员
存在的,所以还可以通过反射的方式得到, 如下得到plugin1
中R.drawable.robert
的id
:
Class drawableClass = cl.loadClass("com.power.plugin1.R$drawable");
int resId2 = (int) RefInvoke.getStaticFieldObject(drawableClass, "robert");
参考:
Android 插件化原理解析——插件加载机制
Android插件化完美实现代码资源加载及原理讲解 附可运行demo
Android应用程序插件化研究之AssetManager
Android动态加载插件资源