Webview stops working after Google automatic update

227 Views Asked by At

I am working on an app which is actually a hybrid one, just showing all the detail in a webView, so I just want to keep the app in the foreground. It is an Android 11 10-inch room Panel (from a third-party company), so I don't have any problem with battery usage.

My main issue is about the automatic updates of webview by Google (that is somehow important, mostly for security), although without hitting the onPause or onDestroy it closed my app, something like a Crash, but with no clues to be sent to Crashlytics or Sentry.

I ran the app for about 5 days, connecting to a ADB constantly to find the main cause of closing the app, it is like this:

2024-01-11 07:51:56.665 ActivityManager      system_server  I  Killing 4372:com.example.myapp/u0a167 (adj 0): stop com.google.android.webview due to installPackageLI
2024-01-11 07:51:56.670 ActivityTaskManager  system_server  W  Force removing ActivityRecord{7fad574 u0 com.example.myapp/.ui.MainActivity t100 f}}: app died, no saved state

and I searched a lot, found so many similar reports of this issue:

Google Play Services' kill app with term 9 signal when it updates without calling onPause & onStop

Automatic update to Android System Webview causes active Android app to exit without warning

How can I stop Chrome background updates killing my app?

My app gets killed due to Service App update

So in one of them, I noticed this comment:

In my case, I created a Sticky Foreground Service, and my app gets restarted even when Chrome updates my phone and crashes.

IgorGanapolsky

Oct 6, 2020 at 19:48

and I have also a Sticky ForegroundService in my code, to check if the app is in background, brings it back to foreground.

class ForegroundCheckService : Service() {

    private val handler = Handler(Looper.getMainLooper())
    private val foregroundCheckRunnable = object : Runnable {
        override fun run() {
            val isAppInForeground = OBApp.instance.isAppInForeground()

            if (!isAppInForeground) {
                bringAppToForeground()
            }

            handler.postDelayed(this, TimeUnit.MINUTES.toMillis(Constants.FOREGROUND_CHECK_INTERVAL))
        }
    }

    override fun onCreate() {
        super.onCreate()
        startForeground(1, createForegroundNotification())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        try {
            handler.post(foregroundCheckRunnable)
        } catch (e: Exception) {
            Log.e("ForegroundCheckService", "Error in onStartCommand: $e")
        }
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacks(foregroundCheckRunnable)
    }

    private fun createForegroundNotification(): Notification {
        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("App Foreground Service")
            .setContentText("Running in the background")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setSilent(true)
            .build()
    }

    private fun bringAppToForeground() {
        val intent = Intent(this, MainActivity::class.java)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        startActivity(intent)
    }

    override fun onBind(intent: Intent?): IBinder? {
        return null
    }

    companion object {
        const val CHANNEL_ID = "ForegroundServiceChannel"
    }
}

AndroidManifest.xml

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

    <service
            android:name=".helper.ForegroundCheckService"
            android:foregroundServiceType="dataSync"
            android:stopWithTask="false" />

and ForegroundManager.kt

class ForegroundManager : Application.ActivityLifecycleCallbacks {
    private var isForeground = false

    fun isForeground(): Boolean {
        return isForeground
    }

    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
        // No-op
    }

    override fun onActivityStarted(activity: Activity) {
        // No-op
    }

    override fun onActivityResumed(activity: Activity) {
        if (activity::class.java == MainActivity::class.java) {
            isForeground = true
            Log.d(Constants.TAG, "App to foreground (onResume)}")
        }
    }

    override fun onActivityPaused(activity: Activity) {
        if (activity::class.java == MainActivity::class.java) {
            isForeground = false
            Log.d(Constants.TAG, "App to background (onPause)}")
        }
    }

    override fun onActivityStopped(activity: Activity) {
        if (activity::class.java == MainActivity::class.java) {
            isForeground = false
            Log.d(Constants.TAG, "App to background (onStop)}")
        }
    }

    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
        // No-op
    }

    override fun onActivityDestroyed(activity: Activity) {
        // No-op
    }
}

and the Application Activity:

class OBApp : DaggerApplication() {
    companion object {
        lateinit var instance: OBApp
    }

    private val foregroundManager = ForegroundManager()

    override fun onCreate() {
        super.onCreate()
        Log.d("OBApp", "OnCreate() ")
        instance = this
        registerActivityLifecycleCallbacks(foregroundManager)
        createNotificationChannel()
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                ForegroundCheckService.CHANNEL_ID,
                "Foreground Service Channel",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            channel.setSound(null, null)
            val notificationManager = getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannel(channel)
        }
    }

    override fun onTerminate() {
        super.onTerminate()
        unregisterActivityLifecycleCallbacks(foregroundManager)
    }

    override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
        return DaggerAppComponent.factory().create(this)
    }

    fun isAppInForeground(): Boolean {
        return foregroundManager.isForeground()
    }

}

but the issue is in ForegroundManager.kt I set a flag isForeground true or false to know when the app goes to background, and use it later in ForegroundCheckService to bring app back to foreground.

and it really works perfectly, whenever the app goes to background, except for this situation of automatic update of webview that it doesn't go to the whole lifeCycle of onActivityPaused or onActivityStopped!

so I wonder if I made a mistake in any step of defining the ForegroundService or so, because I just need to get the app backs to foreground, no matter why it is not there.

Thanks for you help.

1

There are 1 best solutions below

1
Yakubu On

Can you follow this steps

  1. Update your ForegroundService to restart the app if it's not running

  2. In ForegroundCheckService, modify onStartCommand:

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    if (!isAppRunning()) {
        restartApp()
    }
    return START_STICKY
}

private fun isAppRunning(): Boolean {
    // Implement logic to check if app is running
}

private fun restartApp() {
    val restartIntent = Intent(this, MainActivity::class.java)
    restartIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    startActivity(restartIntent)
}

  1. Implement isAppRunning to check if your app's main activity is running.