Skip to content

[Bug]: Blank white screen on Ulefone Android 14 devices after code changes; fixed by clearing app cache #8171

Closed

Description

@tiagoarroz

Capacitor Version

💊 Capacitor Doctor 💊

Latest Dependencies:

@capacitor/cli: 7.4.3
@capacitor/core: 7.4.3
@capacitor/android: 7.4.3
@capacitor/ios: 7.4.3

Installed Dependencies:

@capacitor/ios: not installed
@capacitor/cli: 7.4.3
@capacitor/core: 7.4.3
@capacitor/android: 7.4.3

[success] Android looking great! 👌

Other API Details

npm --version output: 11.2.0
node --version output: v22.14.0

Platforms Affected

  • iOS
  • Android
  • Web

Current Behavior

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.

Project Reproduction

https://github.com/tiagoarroz/testes-ecra-branco

Additional Information

Steps to Reproduce

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.

Logcat before cleaning cache
https://pastebin.com/raw/bnScJT0j

Logcat after cleaning cache
https://pastebin.com/raw/tGzf39Y3

Activity

  1. added theissue type on Sep 26, 2025
  2. tiagoarroz commented on Oct 20, 2026

    @tiagoarroz
    Author

    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

    1. 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".
    2. 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.
    3. 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.
    4. 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.

    AndroidManifest.xml, inside <application>:

    <service
        android:name=".DuraSpeedUnblockService"
        android:exported="false"
        android:process=":duraspeed" />

    DuraSpeedUnblockService.java:

    package io.ionic.starter;
    
    import android.app.Service;
    import android.content.Context;
    import android.content.Intent;
    import android.os.IBinder;
    import android.os.Process;
    import android.util.Log;
    import java.io.File;
    import java.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.
     */
    public class DuraSpeedUnblockService extends Service {
    
      private static final String TAG = "DuraSpeedUnblock";
    
      /** An action DuraSpeed exempts. The intent is explicit, so no screensaver is involved. */
      private static final String EXEMPT_ACTION = "android.service.dreams.DreamService";
    
      /** Call before the WebView is created, while the app is in the foreground. */
      public static void unblock(Context context) {
        if (!isDuraSpeedPresent()) {
          return;
        }
        try {
          context.startService(new Intent(context, DuraSpeedUnblockService.class).setAction(EXEMPT_ACTION));
        } catch (RuntimeException error) {
          Log.w(TAG, "Could not start the DuraSpeed unblock service", error);
        }
      }
    
      public static boolean isDuraSpeedPresent() {
        try {
          Class<?> systemProperties = Class.forName("android.os.SystemProperties");
          Method get = systemProperties.getMethod("get", String.class);
          if ("1".equals(get.invoke(null, "persist.vendor.duraspeed.support"))) {
            return true;
          }
        } catch (ReflectiveOperationException | RuntimeException ignored) {
          // Fall back to looking for the framework jar.
        }
        return new File("/system_ext/framework/duraspeed.jar").exists()
          || new File("/system/framework/duraspeed.jar").exists();
      }
    
      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
        stopSelf(startId);
        return START_NOT_STICKY;
      }
    
      /** The process only existed so the start would be checked; free it. */
      @Override
      public void onDestroy() {
        super.onDestroy();
        Process.killProcess(Process.myPid());
      }
    
      @Override
      public IBinder onBind(Intent intent) {
        return null;
      }
    }

    MainActivity.java:

    @Override
    public void onCreate(Bundle savedInstanceState) {
      // 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions