如何在attach()中更改活动标题

Dav*_*jak 11 android android-activity android-testing android-instrumentation android-uiautomator

我想运行一个参数化的Instrumentation Test,它具有不同的语言环境,可以使用所有支持的语言运行相同的测试.

观察到的行为是,对于每个后续运行,活动都将具有第一次测试运行本地化标题.因此,无论我的手机使用哪种语言,标题都将针对第一个参数化测试运行进行正确本地化,并且对于每个后续版本仍然相同.

虽然覆盖区域设置本身适用于任何资源,它会工作只有一次活动标题,如果设置由AndroidManifest.xml.

活动似乎得到了他们的标题设置attach,无论调用附件似乎是缓存应用程序首次启动的区域设置中的标题.

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
   ---> CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
    attachBaseContext(context);
Run Code Online (Sandbox Code Playgroud)

由于资源总是被正确地本地化,因此解决方法是调用setTitle(R.string.title)或只是getActionBar().setTitle(R.string.setTitle),但我不想仅为了测试目的而更改活动.

问题:如何第一次测试运行更改活动的标题?如上所述,这似乎被缓存并且没有正确更新,并且杀死应用程序以重新启动它将无法进行检测测试.

测试设置

整个测试项目可以在GitHub上找到(Localization.java包含当前失败的单元测试和此处描述的问题)并且正在使用参数化单元测试UIAutomator.

我们的目标是在不了解应用程序本身(UIAutomator)的情况下拍摄一批截图,并且不需要为测试修改应用程序.

更改区域设置:

我在每次测试之前都成功地更改了语言环境,并通过执行以下操作正确显示了我的文本,同时我还有多个断言确保资源实际上是正确的语言环境.

public LocalizationTest(Locale locale) {
    mLocale = locale;
    Configuration config = new Configuration();
    Locale.setDefault(mLocale);
    config.setLocale(mLocale);

    Resources resources = InstrumentationRegistry.getTargetContext().getResources();
    resources.updateConfiguration(config, resources.getDisplayMetrics());

    resources.flushLayoutCache();
}
Run Code Online (Sandbox Code Playgroud)

什么行不通:

我显然尝试在目标上下文,应用程序上下文和活动上以相同的方式设置语言环境(反正可能为时已晚).

我看到它attach被调用Instrumentation,但只是创建一个新的App并尝试启动活动也不会本地化标题.

Intent intent = context.getPackageManager().getLaunchIntentForPackage(BuildConfig.APPLICATION_ID);
context = InstrumentationRegistry.getInstrumentation().newApplication(App.class,
                InstrumentationRegistry.getTargetContext());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
Run Code Online (Sandbox Code Playgroud)

Dav*_*jak 2

ApplicationPackageManager标题字符串以static 形式缓存在包管理器中sStringCache

虽然有一种方法static void configurationChanged()可以清除缓存,但它似乎不会在手动更改时被调用。因此,所描述的问题是在第一次调用后活动标题本地化错误。

解决这个问题的方法是使用反射来加载类并自行调用方法。这有点脏,因为它正在访问私有方法,但它有效。

// as before
Configuration config = new Configuration();
Locale.setDefault(mLocale);
config.setLocale(mLocale);

Resources resources = context.getResources();
resources.updateConfiguration(config, resources.getDisplayMetrics());

// CLEAR the cache!
Method method = getClass().getClassLoader()
        .loadClass("android.app.ApplicationPackageManager")
        .getDeclaredMethod("configurationChanged");
method.setAccessible(true);
method.invoke(null);
Run Code Online (Sandbox Code Playgroud)

或者,您可以在另一个非公共 API 上使用公共方法,该 API 反过来也会调用上述方法。仍然很脏,但不调用私有方法。

看来您可以resources.updateConfiguration(...);通过使用此方法来省略,因为它也会解决这个问题。

// Clear the cache. 
Object thread = getClass().getClassLoader()
        .loadClass("android.app.ActivityThread")
        .getMethod("currentActivityThread")
        .invoke(null);
Method method = getClass().getClassLoader()
        .loadClass("android.app.ActivityThread")
        .getMethod("applyConfigurationToResources", Configuration.class);
method.invoke(thread, config);
Run Code Online (Sandbox Code Playgroud)