Android 开发进阶:在非 ComponentActivity 中实现协程自动取消

157 阅读4分钟

一、 背景

在 Android 开发中,由于集成第三方 SDK(如华为 HMS Scan Kit),我们有时必须继承非 ComponentActivity 的类。这类 Activity 通常未实现 LifecycleOwner,导致开发者无法直接利用 lifecycleScope

作为一个严谨的开发者,我们不仅要修复这一功能缺失,还要提供可验证、可测试的优雅方案。本文将分享两种实现思路及其验证方法。


二、 方案一:基于接口的手动注册方案

这是最基础且通用的方案,通过定义一个 LifecycleAction 接口,依靠 ActivityLifecycleCallbacks 监听并分发事件。

1. 核心实现

该方案通过接口生命周期监听,可以在 ON_DESTROY 时自动反注册,从而有效避免内存泄漏,确保方案的严谨性。


/**
 * author : A Lonely Cat
 * github : https://github.com/anjiemo/SunnyBeach
 * time   : 2024/01/26
 * desc   : 生命周期行为接口,用于非 ComponentActivity 的子类(如第三方 SDK 的 Activity)实现 LifecycleOwner
 */
interface LifecycleAction : LifecycleOwner {
    
    fun getLifecycleRegistry(): LifecycleRegistry
    
    override val lifecycle: Lifecycle get() = getLifecycleRegistry()

    fun initLifecycle(activity: Activity) {
        activity.application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
            private fun handle(targetActivity: Activity, event: Lifecycle.Event) {
                if (activity === targetActivity) {
                    getLifecycleRegistry().handleLifecycleEvent(event)
                    if (event == Lifecycle.Event.ON_DESTROY) {
                        activity.application.unregisterActivityLifecycleCallbacks(this)
                    }
                }
            }

            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = handle(activity, Lifecycle.Event.ON_CREATE)
            override fun onActivityStarted(activity: Activity) = handle(activity, Lifecycle.Event.ON_START)
            override fun onActivityResumed(activity: Activity) = handle(activity, Lifecycle.Event.ON_RESUME)
            override fun onActivityPaused(activity: Activity) = handle(activity, Lifecycle.Event.ON_PAUSE)
            override fun onActivityStopped(activity: Activity) = handle(activity, Lifecycle.Event.ON_STOP)
            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
            override fun onActivityDestroyed(activity: Activity) = handle(activity, Lifecycle.Event.ON_DESTROY)
        })
    }
}

2. 验证代码与预期日志

接入完成后,我们可以通过在业务代码中埋点日志来验证方案的有效性。

测试代码:

class ScanCodeActivity : ScanKitActivity(), LifecycleAction {
    
    private val mLifecycleRegistry = LifecycleRegistry(this)
    
    override fun getLifecycleRegistry() = mLifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) {
        initLifecycle(this) // 手动初始化
        super.onCreate(savedInstanceState)
        
        // 使用 lifecycleScope 开启协程
        lifecycleScope.launch {
            Timber.tag("_Test").d("协程已启动")
            delay(5000)
            Timber.tag("_Test").d("协程任务完成")
        }.invokeOnCompletion { cause ->
            if (cause is CancellationException) {
                Timber.tag("_Test").d("验证成功:方案一协程已自动取消")
            }
        }
    }
}

预期测试日志(Logcat):

D/_Test: 协程已启动
// (此时关闭 Activity)
D/_Test: 验证成功:方案一协程已自动取消

操作验证步骤:

  1. 运行应用并进入该 Activity。
  2. 观察 Logcat(过滤 TAG 为 _Test),确认输出“协程已启动”。
  3. 在 5 秒内(协程任务完成前)按下返回键关闭 Activity。
  4. 检查 Logcat,若输出“验证成功...”,则说明协程随 Activity 销毁而正常取消。

三、 方案二:基于 Kotlin 属性委托的“声明式”方案

这是方案一的升级版,充分发挥了 Kotlin 的语言优势,实现了“零侵入”初始化。

1. Kotlin 风格方案:属性委托

为了极致的简洁,我们可以利用 Kotlin 属性委托,通过手动实现一个安全的属性委托扩展来注入生命周期监听:

fun Activity.lifecycleRegistry() = lazy(LazyThreadSafetyMode.NONE) {
    require(this is LifecycleOwner) { "Activity 必须实现 LifecycleOwner 接口" }
    LifecycleRegistry(this).also { registry ->
        application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
            private fun handle(targetActivity: Activity, event: Lifecycle.Event) {
                if (targetActivity === this@lifecycleRegistry) {
                    registry.handleLifecycleEvent(event)
                    if (event == Lifecycle.Event.ON_DESTROY) {
                        targetActivity.application.unregisterActivityLifecycleCallbacks(this)
                    }
                }
            }

            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) = handle(activity, Lifecycle.Event.ON_CREATE)
            override fun onActivityStarted(activity: Activity) = handle(activity, Lifecycle.Event.ON_START)
            override fun onActivityResumed(activity: Activity) = handle(activity, Lifecycle.Event.ON_RESUME)
            override fun onActivityPaused(activity: Activity) = handle(activity, Lifecycle.Event.ON_PAUSE)
            override fun onActivityStopped(activity: Activity) = handle(activity, Lifecycle.Event.ON_STOP)
            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
            override fun onActivityDestroyed(activity: Activity) = handle(activity, Lifecycle.Event.ON_DESTROY)
        })
    }
}

2. 避免 JVM 签名冲突

注意:在 Kotlin 中,属性会自动生成 Getter。如果属性名设为 lifecycleRegistry,会与接口生成的 getLifecycleRegistry() 产生冲突。因此建议使用 mLifecycleRegistry 等名称。

测试代码:

class ScanCodeActivity : ScanKitActivity(), LifecycleAction {
    
    // 使用属性委托,同时也需要避免方法名冲突
    private val mLifecycleRegistry by lifecycleRegistry()
    
    // 显式实现接口要求的方法
    override fun getLifecycleRegistry() = mLifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 触发协程测试
        lifecycleScope.launch {
            Timber.tag("_Test").d("方案二协程运行中...")
            delay(5000)
            Timber.tag("_Test").d("方案二任务完成")
        }.invokeOnCompletion { cause ->
            if (cause is CancellationException) {
                Timber.tag("_Test").d("验证成功:方案二协程已自动取消")
            }
        }
    }
}

预期测试日志(Logcat):

D/_Test: 方案二协程运行中...
// (此时关闭 Activity)
D/_Test: 验证成功:方案二协程已自动取消

操作验证步骤:

  1. 启动 ScanCodeActivity
  2. 观察 Logcat(过滤 TAG 为 _Test),确认输出“方案二协程运行中...”。
  3. 在任务结束(5 秒)前退出该页面。
  4. 验证 Logcat 是否输出了“验证成功...”日志。

四、 总结

无论是选择“手动注册”还是“属性委托”,核心目标都是通过 LifecycleRegistry 准确分发生命周期事件。通过 invokeOnCompletion 回调并配合日志输出,我们可以直观地验证协程是否已随 Activity 的销毁而及时释放。

结语

好啦,就写到这里吧 ,如果你想看更多的项目代码,请查看我在 Github 上的 阳光沙滩APP项目 ,你也可以在这里点击下载我写的 阳光沙滩APP客户端 进行体验。

如果对你有帮助的话,欢迎一键三连+关注哦~