Android 开发进阶:在非 ComponentActivity 中实现协程自动取消
一、 背景
在 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客户端 进行体验。
如果对你有帮助的话,欢迎一键三连+关注哦~
