fix: android skip posting hash response after detached from engine (#23192)

fix: native cancellations for hashing

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2025-10-24 19:26:49 +05:30 committed by GitHub
parent 0a6b2ad26e
commit 221e0ef02f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 52 additions and 7 deletions

View File

@ -9,6 +9,7 @@ import app.alextran.immich.background.BackgroundWorkerFgHostApi
import app.alextran.immich.background.BackgroundWorkerLockApi import app.alextran.immich.background.BackgroundWorkerLockApi
import app.alextran.immich.connectivity.ConnectivityApi import app.alextran.immich.connectivity.ConnectivityApi
import app.alextran.immich.connectivity.ConnectivityApiImpl import app.alextran.immich.connectivity.ConnectivityApiImpl
import app.alextran.immich.core.ImmichPlugin
import app.alextran.immich.images.ThumbnailApi import app.alextran.immich.images.ThumbnailApi
import app.alextran.immich.images.ThumbnailsImpl import app.alextran.immich.images.ThumbnailsImpl
import app.alextran.immich.sync.NativeSyncApi import app.alextran.immich.sync.NativeSyncApi
@ -42,6 +43,14 @@ class MainActivity : FlutterFragmentActivity() {
flutterEngine.plugins.add(BackgroundServicePlugin()) flutterEngine.plugins.add(BackgroundServicePlugin())
flutterEngine.plugins.add(HttpSSLOptionsPlugin()) flutterEngine.plugins.add(HttpSSLOptionsPlugin())
flutterEngine.plugins.add(backgroundEngineLockImpl) flutterEngine.plugins.add(backgroundEngineLockImpl)
flutterEngine.plugins.add(nativeSyncApiImpl)
}
fun cancelPlugins(flutterEngine: FlutterEngine) {
val nativeApi =
flutterEngine.plugins.get(NativeSyncApiImpl26::class.java) as ImmichPlugin?
?: flutterEngine.plugins.get(NativeSyncApiImpl30::class.java) as ImmichPlugin?
nativeApi?.detachFromEngine()
} }
} }
} }

View File

@ -2,12 +2,13 @@ package app.alextran.immich.background
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import app.alextran.immich.core.ImmichPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.embedding.engine.plugins.FlutterPlugin
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
private const val TAG = "BackgroundEngineLock" private const val TAG = "BackgroundEngineLock"
class BackgroundEngineLock(context: Context) : BackgroundWorkerLockApi, FlutterPlugin { class BackgroundEngineLock(context: Context) : BackgroundWorkerLockApi, ImmichPlugin() {
private val ctx: Context = context.applicationContext private val ctx: Context = context.applicationContext
companion object { companion object {
@ -41,12 +42,14 @@ class BackgroundEngineLock(context: Context) : BackgroundWorkerLockApi, FlutterP
} }
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
super.onAttachedToEngine(binding)
checkAndEnforceBackgroundLock(binding.applicationContext) checkAndEnforceBackgroundLock(binding.applicationContext)
engineCount.incrementAndGet() engineCount.incrementAndGet()
Log.i(TAG, "Flutter engine attached. Attached Engines count: $engineCount") Log.i(TAG, "Flutter engine attached. Attached Engines count: $engineCount")
} }
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
super.onDetachedFromEngine(binding)
engineCount.decrementAndGet() engineCount.decrementAndGet()
Log.i(TAG, "Flutter engine detached. Attached Engines count: $engineCount") Log.i(TAG, "Flutter engine detached. Attached Engines count: $engineCount")
} }

View File

@ -190,6 +190,9 @@ class BackgroundWorker(context: Context, params: WorkerParameters) :
private fun complete(success: Result) { private fun complete(success: Result) {
Log.d(TAG, "About to complete BackupWorker with result: $success") Log.d(TAG, "About to complete BackupWorker with result: $success")
isComplete = true isComplete = true
if (engine != null) {
MainActivity.cancelPlugins(engine!!)
}
engine?.destroy() engine?.destroy()
engine = null engine = null
flutterApi = null flutterApi = null

View File

@ -0,0 +1,29 @@
package app.alextran.immich.core
import androidx.annotation.CallSuper
import io.flutter.embedding.engine.plugins.FlutterPlugin
abstract class ImmichPlugin : FlutterPlugin {
private var detached: Boolean = false;
@CallSuper
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
detached = false;
}
fun detachFromEngine() {
detached = true
}
@CallSuper
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
detachFromEngine()
}
fun <T> completeWhenActive(callback: (T) -> Unit, value: T) {
if (detached) {
return;
}
callback(value);
}
}

View File

@ -7,6 +7,7 @@ import android.database.Cursor
import android.provider.MediaStore import android.provider.MediaStore
import android.util.Base64 import android.util.Base64
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import app.alextran.immich.core.ImmichPlugin
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -27,7 +28,7 @@ sealed class AssetResult {
} }
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
open class NativeSyncApiImplBase(context: Context) { open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
private val ctx: Context = context.applicationContext private val ctx: Context = context.applicationContext
private var hashTask: Job? = null private var hashTask: Job? = null
@ -237,7 +238,7 @@ open class NativeSyncApiImplBase(context: Context) {
callback: (Result<List<HashResult>>) -> Unit callback: (Result<List<HashResult>>) -> Unit
) { ) {
if (assetIds.isEmpty()) { if (assetIds.isEmpty()) {
callback(Result.success(emptyList())) completeWhenActive(callback, Result.success(emptyList()))
return return
} }
@ -253,10 +254,10 @@ open class NativeSyncApiImplBase(context: Context) {
} }
}.awaitAll() }.awaitAll()
callback(Result.success(results)) completeWhenActive(callback, Result.success(results))
} catch (e: CancellationException) { } catch (e: CancellationException) {
callback( completeWhenActive(
Result.failure( callback, Result.failure(
FlutterError( FlutterError(
HASHING_CANCELLED_CODE, HASHING_CANCELLED_CODE,
"Hashing operation was cancelled", "Hashing operation was cancelled",
@ -265,7 +266,7 @@ open class NativeSyncApiImplBase(context: Context) {
) )
) )
} catch (e: Exception) { } catch (e: Exception) {
callback(Result.failure(e)) completeWhenActive(callback, Result.failure(e))
} }
} }
} }