【发布时间】:2021-07-09 09:26:15
【问题描述】:
我想用协程创建一个单例来从网络加载图像。我已经实现了单例并且可以将网络图像加载到 imageView 中。这是我的单身课程。
class Singleton(context: Context) {
private val TAG = "Singleton"
private val scope =
CoroutineScope(SupervisorJob() + Dispatchers.Main + CoroutineExceptionHandler { _, exception ->
Log.e(TAG, "Caught $exception")
})
private var job:Job? = null
companion object {
private var INSTANCE: Singleton? = null
@Synchronized
fun with(context: Context): Singleton {
require(context != null) {
"ImageLoader:with - Context should not be null."
}
return INSTANCE ?: Singleton(context).also {
INSTANCE = it
Log.d("ImageLoader", "First Init")
}
}
}
private fun onAttachStateChange(imageView: ImageView, job: Job) {
imageView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
}
override fun onViewDetachedFromWindow(v: View?) {
job.cancel()
}
})
}
fun loadImage(url: String, imageView: ImageView) {
job = scope.launch {
try {
updateData(URL(url), imageView)
} catch (e: CancellationException) {
Log.d(TAG, "work cancelled!")
}
}.also {
onAttachStateChange(imageView, it)
}
}
suspend fun updateData(url: URL, imageView: ImageView) = run {
fetchImage(url)?.apply { imageView.setImageBitmap(this) }
?: imageView.setImageResource(R.drawable.ic_launcher_background)
}
fun stopUpdate() {
scope.cancel()
}
private suspend fun fetchImage(url: URL): Bitmap? {
return withContext(Dispatchers.IO) {
try {
val connection = url.openConnection() as HttpURLConnection
val bufferedInputStream = BufferedInputStream(connection.inputStream)
BitmapFactory.decodeStream(bufferedInputStream)
} catch (e: Exception) {
Log.e("TAG", e.toString())
null
}
}
}
}
我的问题是,当我在 ActivityB 中取消 onDestroy() 中的协程范围,然后在 ActivityA 中再次使用我的单例时,它不会做任何事情,因为范围已被取消()。那么,当活动为onDestroy() 时,有什么方法可以通过 scope.cancel() 在单例中正确使用协程。这是一个演示:
class MainActivityA : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_activity)
Singleton.with(this).updateData(url, imageView)
}
}
class MainActivityB : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_activity)
}
override fun onDestroy() {
super.onDestroy()
// Do not need to call scope.cancel(). Cause when the view is
// detached it will cancel the job.
// Singleton.with(this).stopUpdate()
}
}
已编辑
我想出了一个想法并添加到 Singleton 类中。使用view.onAttachStateChange 检测视图是否仍然附加到窗口。如果是分离的,那么我们可以取消作业。这是一个好方法吗?
【问题讨论】:
-
如果您确实需要将任务附加到调用者的生命周期,那么在单例中定义范围的原因是什么?为什么不使用调用者的协程上下文?
-
另外,这是单例的原因是什么?您是否需要全局图像缓存或其他东西?我问是因为如果不是单例,问题就不会真正存在(而且你不需要使用这种丑陋的
with(Context)方法;-P) -
我使用单例的原因是因为需要全局图像缓存。我不太确定您使用调用者的协程上下文是什么意思
-
我的意思是在活动中使用
lifecycleScope.launch(),而不是在单例中创建自定义范围。但这实际上取决于您认为谁是图像获取任务的“所有者”。您计划在活动被销毁时取消范围,所以在我看来,您真的认为活动是这些任务的所有者。那么问题来了,为什么要在 Singleton 中创建自定义作用域呢? -
但是,虽然这个单例具有缓存等功能,但将其视为获取任务的所有者可能是有意义的。那么,当 Activity 被销毁时,它的作用域不应该被真正取消。
标签: android android-studio singleton kotlin-coroutines