【问题标题】:How to use Coroutine in singleton properly如何在单例中正确使用协程
【发布时间】: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


【解决方案1】:

根据定义,单例永远存在,所以我不确定取消它的范围是否有意义。如果您需要同时使用来自多个组件的单例怎么办?他们会取消其他组件的工作。

为确保您不会泄露已损坏组件的作业,您可以为每个组件创建一个子作业并将所有任务放在其下,或者根本不定义自定义范围并重用调用者的协程上下文。

【讨论】:

  • 感谢您的建议。讨论后,我提出了一个想法并更新了我的问题。如果您能对此实施发表评论,我将不胜感激。谢谢!
猜你喜欢
  • 2021-09-08
  • 2019-09-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-08
  • 2019-06-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多