You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: Blank white screen on Ulefone Android 14 devices after code changes; fixed by clearing app cache #8171
After making any code change (e.g., adding a simple
or variable to a component) and rebuilding/reinstalling the app, it launches to a completely blank white screen on specific Ulefone Android 14 devices (tested on Armor series) (MediaTek Helio G36 8 Core processor with com.google.android.webview version 126-140). No console errors or crashes occur—the app just fails to render any content.
Clearing the app's cache (Settings > Apps > [App] > Storage > Clear Cache) fixes it temporarily, and the app works until the next rebuild. This does not happen on other Android 14 devices (e.g., Samsung, Pixel) or emulators.
Logcat shows WebView initialization issues before cache clear. After cache clear, WebView loads cleanly, and Capacitor bridge starts successfully.
Expected Behavior
The app should load and render normally after any rebuild/install, without requiring manual cache clearing. WebView should initialize consistently across devices.
Create a new Ionic Angular tabs starter app: ionic start test-blank tabs --type=angular --capacitor.
Make a minor change, e.g., in src/app/explore-container/explore-container.component.html, add
a small change for testing {{testVariable}}
and in .ts, add testVariable = "test variable".
Build and sync: ionic build && npx cap sync android.
Open in Android Studio, build, and install APK on a Ulefone Android 14 device.
Launch the app → blank white screen.
Clear app cache → app works.
Follow-up for anyone who lands here with the same symptom: we found the real cause, and it isn't the cache, the WebView version, Ionic or Capacitor. It's DuraSpeed, the background/memory optimiser that MediaTek ships on many of its ROMs (Ulefone Armor X12 Pro and Armor X13 in our case).
What's actually happening
Whenever an app is force-stopped, the ROM's ActivityManagerService.forceStopPackage adds the package to a DuraSpeed suppression list. Installing a new build while the app is running counts as a force stop, and so do "Force stop" in Settings and DuraSpeed itself reclaiming memory. That's why the repro was "make any code change and reinstall".
While the package is on that list, the ROM refuses to start the package's services in processes that aren't already running. Logcat shows suppress to start service! / process is bad.
The WebView renderer is one of those services: it's a separate process started on behalf of your app. It never starts, so you get cr_ChildProcessConn: Failed to establish the service connection, no JavaScript runs, there are no console errors, and the screen stays white.
The package only leaves the list when DuraSpeed allows one of its components to start. Until then it stays on the list across launches, so the white screen keeps coming back.
Clearing the cache was a red herring: it just happens to take the app off the list. We later noticed that opening the app's App info screen and going back was often enough on its own, without clearing anything. So deleting cache/code_cache on startup (the workaround suggested above) won't help on these devices. The cr_ChildProcessConn line @thetaPC pointed out was the right clue, though.
How to check if you're affected
adb shell getprop persist.vendor.duraspeed.support # "1" means DuraSpeed is present
adb logcat | grep -iE "suppress to start service|process is bad|cr_ChildProcessConn"
The fix
DuraSpeed lets a service start go through when the intent carries one of a few exempt actions, and android.service.dreams.DreamService is one of them. A start that goes through also takes the package off the suppression list. So, before the WebView is created, start an empty service in its own process with that action:
The intent is explicit (it names the class), so Android doesn't treat it as a screensaver. The action is only there to get past DuraSpeed's check.
The service needs its own process (android:process). The check only runs when a service has to start a process that isn't running yet, and your main process is already running.
DuraSpeed decides inside the startService call, so by the time it returns the package is already off the list and the WebView can start its renderer.
packageio.ionic.starter;
importandroid.app.Service;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.os.IBinder;
importandroid.os.Process;
importandroid.util.Log;
importjava.io.File;
importjava.lang.reflect.Method;
/** * Takes the app off MediaTek DuraSpeed's suppression list. While the app is on that list, * the ROM won't start the WebView renderer process and the app shows a blank white screen. */publicclassDuraSpeedUnblockServiceextendsService {
privatestaticfinalStringTAG = "DuraSpeedUnblock";
/** An action DuraSpeed exempts. The intent is explicit, so no screensaver is involved. */privatestaticfinalStringEXEMPT_ACTION = "android.service.dreams.DreamService";
/** Call before the WebView is created, while the app is in the foreground. */publicstaticvoidunblock(Contextcontext) {
if (!isDuraSpeedPresent()) {
return;
}
try {
context.startService(newIntent(context, DuraSpeedUnblockService.class).setAction(EXEMPT_ACTION));
} catch (RuntimeExceptionerror) {
Log.w(TAG, "Could not start the DuraSpeed unblock service", error);
}
}
publicstaticbooleanisDuraSpeedPresent() {
try {
Class<?> systemProperties = Class.forName("android.os.SystemProperties");
Methodget = systemProperties.getMethod("get", String.class);
if ("1".equals(get.invoke(null, "persist.vendor.duraspeed.support"))) {
returntrue;
}
} catch (ReflectiveOperationException | RuntimeExceptionignored) {
// Fall back to looking for the framework jar.
}
returnnewFile("/system_ext/framework/duraspeed.jar").exists()
|| newFile("/system/framework/duraspeed.jar").exists();
}
@OverridepublicintonStartCommand(Intentintent, intflags, intstartId) {
stopSelf(startId);
returnSTART_NOT_STICKY;
}
/** The process only existed so the start would be checked; free it. */@OverridepublicvoidonDestroy() {
super.onDestroy();
Process.killProcess(Process.myPid());
}
@OverridepublicIBinderonBind(Intentintent) {
returnnull;
}
}
MainActivity.java:
@OverridepublicvoidonCreate(BundlesavedInstanceState) {
// Must run before super.onCreate(), which creates the WebView and asks for the renderer.DuraSpeedUnblockService.unblock(this);
super.onCreate(savedInstanceState);
}
On devices without DuraSpeed, unblock() does nothing.
Optional: a safety net
We also kept a single recovery attempt for the rare case where the WebView still comes up empty. On DuraSpeed devices only, ~10 s after onCreate we ask the page whether the app has rendered (for Angular: document.querySelector('app-root')?.children.length > 0). If there's no positive answer within 2 s (with no renderer, evaluateJavascript never calls back), we call unblock() again and restart the process. Recreating the activity isn't enough, because every WebView in the process shares the failed renderer connection and none of them asks for a new one. Relaunch the activity with FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK and an extra that marks the retry, then Process.killProcess(Process.myPid()). If the extra is already there, don't retry, so it can never loop.
With this in place, our Armor X12 Pro no longer gets the white screen after reinstalls or force stops, and we haven't needed to clear the cache since. This is a ROM behaviour, so there's nothing to fix in Ionic or Capacitor.
Activity
tiagoarroz commentedon Oct 20, 2026
Follow-up for anyone who lands here with the same symptom: we found the real cause, and it isn't the cache, the WebView version, Ionic or Capacitor. It's DuraSpeed, the background/memory optimiser that MediaTek ships on many of its ROMs (Ulefone Armor X12 Pro and Armor X13 in our case).
What's actually happening
ActivityManagerService.forceStopPackageadds the package to a DuraSpeed suppression list. Installing a new build while the app is running counts as a force stop, and so do "Force stop" in Settings and DuraSpeed itself reclaiming memory. That's why the repro was "make any code change and reinstall".suppress to start service!/process is bad.cr_ChildProcessConn: Failed to establish the service connection, no JavaScript runs, there are no console errors, and the screen stays white.Clearing the cache was a red herring: it just happens to take the app off the list. We later noticed that opening the app's App info screen and going back was often enough on its own, without clearing anything. So deleting
cache/code_cacheon startup (the workaround suggested above) won't help on these devices. Thecr_ChildProcessConnline @thetaPC pointed out was the right clue, though.How to check if you're affected
The fix
DuraSpeed lets a service start go through when the intent carries one of a few exempt actions, and
android.service.dreams.DreamServiceis one of them. A start that goes through also takes the package off the suppression list. So, before the WebView is created, start an empty service in its own process with that action:android:process). The check only runs when a service has to start a process that isn't running yet, and your main process is already running.startServicecall, so by the time it returns the package is already off the list and the WebView can start its renderer.AndroidManifest.xml, inside<application>:DuraSpeedUnblockService.java:MainActivity.java:On devices without DuraSpeed,
unblock()does nothing.Optional: a safety net
We also kept a single recovery attempt for the rare case where the WebView still comes up empty. On DuraSpeed devices only, ~10 s after
onCreatewe ask the page whether the app has rendered (for Angular:document.querySelector('app-root')?.children.length > 0). If there's no positive answer within 2 s (with no renderer,evaluateJavascriptnever calls back), we callunblock()again and restart the process. Recreating the activity isn't enough, because every WebView in the process shares the failed renderer connection and none of them asks for a new one. Relaunch the activity withFLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASKand an extra that marks the retry, thenProcess.killProcess(Process.myPid()). If the extra is already there, don't retry, so it can never loop.With this in place, our Armor X12 Pro no longer gets the white screen after reinstalls or force stops, and we haven't needed to clear the cache since. This is a ROM behaviour, so there's nothing to fix in Ionic or Capacitor.