Mobile Development 31 min read

Dynamic Multi-language Resource Replacement and WebView Crash Resolution in Android

To prevent translation errors and post‑release crashes in multilingual Android apps, the article describes a dynamic resource‑replacement system that loads external language packs via AssetManager, proxies Resources through reflection, integrates the proxy with ResourcesManager, and updates WebView assets, eliminating garbled text and long‑press crashes.

vivo Internet Technology
vivo Internet Technology
vivo Internet Technology
Dynamic Multi-language Resource Replacement and WebView Crash Resolution in Android

Recent projects that need to support dozens of languages often encounter translation errors, especially for low‑frequency languages that look like garbled text to users. The translation workflow (Chinese → English → third‑party translation) introduces ambiguities and makes it hard to detect mistakes until users report them after release.

Problem 1: Translation errors caused by polysemy and context‑dependent terms.

Problem 2: Errors are discovered only after the app is shipped, because the translated strings are unreadable to developers.

To solve these issues, a dynamic resource‑replacement scheme is designed. In Android, language strings are stored as XML files in resource‑qualified folders. Updating a language at runtime is essentially swapping the underlying Resources object.

The implementation follows a theme‑switching pattern:

Load an external plug.apk containing the new language resources using AssetManager.addAssetPath and create a Resources instance for it.

Build a ProxyResource (decorator) that first tries to fetch strings from the plug resources and falls back to the original app resources.

Replace the Application and Activity Resources objects with the proxy via reflection.

Hook LayoutInflater.Factory2 to intercept view inflation and redirect attribute‑based text retrieval to the proxy.

Key code snippets:

AssetManager mLoadedAssetManager = AssetManager.class.newInstance();
Reflector.with(mLoadedAssetManager).method("addAssetPath", String.class).call(textResPath);
Resources textResPackResources = new Resources(mLoadedAssetManager, appResources.getDisplayMetrics(), appResources.getConfiguration());

The custom TextRepairProxyResources overrides methods such as getText , getString , and getIdentifier to delegate to the plug resources when available:

@Override
public CharSequence getText(@StringRes int id) throws NotFoundException {
    if (!checkNull()) return super.getText(id);
    if (!checkTextRepairOn()) return mAppResources.getText(id);
    int plugId = getIdentifier(id);
    return plugId == 0 ? mAppResources.getText(id) : mResPackResources.getText(plugId);
}

During development, several investigations were performed:

Analyzing Android 6.0 and 9.0 resource‑management source code to understand how ResourcesManager caches resources.

Printing internal caches (e.g., mResourceImpls , mActivityResourceReferences ) before and after initializing a WebView revealed that on Android 6.x the WebView uses a separate Resources instance, while on Android 9.0 the WebView’s assets are added to the app’s libDir list.

Root cause of WebView crashes on Android 7.0+ was identified: the proxy Resources created by the replacement scheme is not managed by ResourcesManager , so when the system injects WebView assets via ResourcesManager.appendLibAssetForMainAssetPath , the proxy is not updated.

Fix: after creating the proxy resources, add them to ResourcesManager.mResourceReferences (a weak‑reference list) so that the system’s asset‑injection logic also updates the proxy:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    synchronized (ResourcesManager.getInstance()) {
        ArrayList
> list = Reflector.with(ResourcesManager.getInstance()).field("mResourceReferences").get();
        list.add(new WeakReference
(textRepairProxyResourcess));
    }
}

With this integration, the dynamic language replacement works across all Android versions, and the WebView long‑press crash is eliminated.

Additional Q&A sections discuss why resource replacement must happen in attachBaseContext , the necessity of a custom LayoutInflater.Factory2 , and how to extend the approach to apps that already implement skinning.

AndroidwebviewReflectionresource managementHookingDynamic Localization
vivo Internet Technology
Written by

vivo Internet Technology

Sharing practical vivo Internet technology insights and salon events, plus the latest industry news and hot conferences.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.