文章目录

  • 1.插件化原理
  • 2.强制指定某种特定区域语言
  • 3.插件Apk资源
  • 3.1 加载成资源及国际化
  • 3.2 插件资源获取


1.插件化原理

原理可以直接查看参考

2.强制指定某种特定区域语言

这部分其实和插件化无关,有时我们的APK有多种strings.xml语言包,但我们就只想强制使用一种特定语言包,有两种方式:
方式1. 在MainActivityattachBaseContext中改变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的差异:

android xml 国际化 android 国际化插件_封装


两者对比:

[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会判断localemLocaleList.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子类的静态成员存在的,所以还可以通过反射的方式得到, 如下得到plugin1R.drawable.robertid:

Class drawableClass = cl.loadClass("com.power.plugin1.R$drawable");
 int resId2 = (int) RefInvoke.getStaticFieldObject(drawableClass, "robert");