Как использовать Coroutine внутри события FloatingActionButton / Button Click - PullRequest
1 голос
/ 01 августа 2020

У меня есть FloatingActionButton, который при нажатии загружает несколько URL-адресов из Интернета. Я хочу запустить эту функцию изнутри setOnClickListener, а затем позволить ей выполнять всю обработку в фоновом режиме.

Я уже сделал то, что хотел, с помощью AsyncTask, но затем кто-то предложил использовать сопрограммы.

Внутри активности:

findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view ->
           
            val currentChapUrl = "https://www.novel.com/chap1.html"  
            GlobalScope.launch { downloadChapters(applicationContext,currentChapUrl) } 
        }

Функция downloadChapters выглядит так:

suspend fun downloadChapters(context : Context, startingChapUrl : String) : String {
   //download chapters.
   //show some toast messages

}

Приложение вылетает при нажатии кнопки.

Logcat:

2020-08-01 17:03:38.559 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:38.559 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.095 6937-6937/com.example.daoreader I/Choreographer: Skipped 32 frames!  The application may be doing too much work on its main thread.
2020-08-01 17:03:39.124 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.125 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.163 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.163 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.208 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.209 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
    
    
    --------- beginning of crash
2020-08-01 17:03:39.211 6937-7086/com.example.daoreader E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
    Process: com.example.daoreader, PID: 6937
    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:200)
        at android.os.Handler.<init>(Handler.java:114)
        at android.widget.Toast$TN$2.<init>(Toast.java:336)
        at android.widget.Toast$TN.<init>(Toast.java:336)
        at android.widget.Toast.<init>(Toast.java:103)
        at android.widget.Toast.makeText(Toast.java:256)
        at com.example.daoreader.WebviewActivity.downloadChapters(WebviewActivity.kt:65)
        at com.example.daoreader.WebviewActivity$onCreate$1$1.invokeSuspend(WebviewActivity.kt:32)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:241)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:740)
    
    
    --------- beginning of system
2020-08-01 17:03:39.257 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.257 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.307 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.307 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.394 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.394 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.487 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.487 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.565 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.566 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.581 6937-6985/com.example.daoreader D/EGL_emulation: eglMakeCurrent: 0xaa205000: ver 2 0 (tinfo 0xaa2034c0)
2020-08-01 17:03:39.595 6937-6985/com.example.daoreader D/EGL_emulation: eglMakeCurrent: 0xaa205000: ver 2 0 (tinfo 0xaa2034c0)
2020-08-01 17:03:39.658 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.658 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:39.714 6937-6985/com.example.daoreader D/EGL_emulation: eglMakeCurrent: 0xaa205000: ver 2 0 (tinfo 0xaa2034c0)
2020-08-01 17:03:39.727 6937-6985/com.example.daoreader D/OpenGLRenderer: endAllActiveAnimators on 0x9232d500 (RippleDrawable) with handle 0x92021280
2020-08-01 17:03:40.051 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:40.051 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:40.094 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5
2020-08-01 17:03:40.094 6937-6985/com.example.daoreader E/eglCodecCommon: glUtilsParamSize: unknow param 0x000085b5

Ответы [ 2 ]

3 голосов
/ 01 августа 2020

Это сбой, потому что вы показываете Toast не в основном потоке.

GlobalScope.launch по умолчанию запускать код в другом потоке, который Dispatchers.Default.

Итак, что вы необходимо сделать, прежде чем показывать Toast, вы должны изменить поток на основной поток следующим образом

suspend fun downloadChapters(context : Context, startingChapUrl : String) : String {
   //download chapters.
   
   withContext(Dispatchers.Main) {
       // Show Toast here
   }
}
1 голос
/ 01 августа 2020

Избегайте использования GlobalScope. Вы должны использовать lifecycleScope, чтобы запустить сопрограмму внутри вашего представления.

Сначала вам нужно добавить androidx.lifecycle:lifecycle-runtime-ktx:{version} в свой файл gradle.

Предполагая, что вы хотите показать в тосте, что downloadChapters возвращается, тогда я бы сделал это так:

Внутри действия:

findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view ->
    lifecycleScope.launch {
        val message = downloadChapters(this@MyActivity, "https://www.novel.com/chap1.html")
        //Show toast here
    }
}

Внутри фрагмента:

findViewById<FloatingActionButton>(R.id.fab).setOnClickListener { view ->
    viewLifecycleOwner.lifecycleScope.launch {
        val message = downloadChapters(context, "https://www.novel.com/chap1.html")
        //Show toast here
    }
}

Поскольку lifecycleScope.launch работает на Основной поток вы должны изменить его контекст на Dispatchers.IO во время downloadChapters, поэтому ваш метод должен выглядеть примерно так:

suspend fun downloadChapters(context : Context, startingChapUrl : String) : String = withContext(Dispatchers.IO) {
    //download chapters.
    "This is my message" //Return your message here
}

Вы не можете обновить свое представление внутри downloadChapters, если вы не перейдете на Dispatchers.Main перед тем, как вы это сделаете, иначе будет сгенерировано исключение. Лучше сделаю это после вызова этого метода.

...