Android中的AsyncTask与Kotlin

51

如何在Android中使用Kotlin进行API调用?

我听说过 Anko ,但我想使用Kotlin提供的方法,就像在Android中我们有Asynctask来进行后台操作。


2
你也可以在 Kotlin 中使用 AsyncTask。 - AlexTa
11个回答

77

AsyncTask是一个AndroidAPI,而不是由Java或Kotlin提供的语言特性。如果您想要,可以像这样使用它们:

class someTask() : AsyncTask<Void, Void, String>() {
    override fun doInBackground(vararg params: Void?): String? {
        // ...
    }

    override fun onPreExecute() {
        super.onPreExecute()
        // ...
    }

    override fun onPostExecute(result: String?) {
        super.onPostExecute(result)
        // ...
    }
}

Anko的doAsync并不是由Kotlin提供的,因为Anko是一个使用Kotlin语言特性简化代码的库。请在此处查看:

如果您使用Anko,您的代码将类似于这样:

doAsync {
    // ...
}

4
我们如何防止出现“可能发生泄漏,请将异步任务设置为静态”的警告? - Shubham AgaRwal
@Killer 不要把它放在可能因为AsyncTask(如Activity)而停留太久的类中,除非你控制好它们,并在你的类应该被销毁时取消所有AsyncTask的实例。 - android developer

36

您可以轻松获得与Anko类似的语法。如果您只想要后台任务,可以使用以下方法:

class doAsync(val handler: () -> Unit) : AsyncTask<Void, Void, Void>() {
    override fun doInBackground(vararg params: Void?): Void? {
        handler()
        return null
    }
}

然后像下面这样使用:

doAsync {
    yourTask()
}.execute()

1
我最近迁移到了Kotlin。所以我有一些疑问.. 当您从Activity或Fragment调用此doAsync时,如何确保不会发生内存泄漏?结果将在哪里获得? Anko的doAsync是生命周期感知的,对吗?我们如何将其引入到我们的doAsync中? - Mohanakrrishna
4
这是一个旧答案,我永远不会使用它。使用例如 LiveData 等可解决所有问题。而且由于这是 Kotlin - 使用协程也可以解决所有问题。 - Algar

26

这里有一个示例,它还允许您更新向用户显示的任何UI或进度。

异步类

class doAsync(val handler: () -> Unit) : AsyncTask<Void, Void, Void>() {
    init {
        execute()
    }

    override fun doInBackground(vararg params: Void?): Void? {
        handler()
        return null
    }
}

简单使用

doAsync {
    // do work here ...

    myView.post({
        // update UI of myView ...
    })
}

1
谢谢,伙计。这对使用 Kotlin 的人来说非常有效。救了我的一天。 - Thiago Silva

14

AsyncTask 在 API 级别 30 中已被弃用。为了实现类似的行为,我们可以使用 Kotlin 并发工具(协程)

CoroutineScope 上创建扩展函数:

fun <R> CoroutineScope.executeAsyncTask(
        onPreExecute: () -> Unit,
        doInBackground: () -> R,
        onPostExecute: (R) -> Unit
) = launch {
    onPreExecute()
    val result = withContext(Dispatchers.IO) { // runs in background thread without blocking the Main Thread
        doInBackground()
    }
    onPostExecute(result)
}

现在它可以用于任何CoroutineScope实例,例如在ViewModel中:

class MyViewModel : ViewModel() {

      fun someFun() {
          viewModelScope.executeAsyncTask(onPreExecute = {
              // ...
          }, doInBackground = {
              // ...
              "Result" // send data to "onPostExecute"
          }, onPostExecute = {
              // ... here "it" is a data returned from "doInBackground"
          })
      }
  }

或者在 Activity/Fragment 中:

lifecycleScope.executeAsyncTask(onPreExecute = {
      // ...
  }, doInBackground = {
      // ...
      "Result" // send data to "onPostExecute"
  }, onPostExecute = {
      // ... here "it" is a data returned from "doInBackground"
  })

要使用viewModelScopelifecycleScope,请将以下行添加到应用程序的build.gradle文件的依赖项中:

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope

感谢您编写Kotlin版本! - eggsloveoats

9
package com.irontec.kotlintest

import android.os.AsyncTask
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*
import org.json.JSONObject
import java.io.BufferedInputStream
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GetWeatherTask(this.text).execute()
    }

    class GetWeatherTask(textView: TextView) : AsyncTask<Unit, Unit, String>() {

        val innerTextView: TextView? = textView

        override fun doInBackground(vararg params: Unit?): String? {
            val url = URL("https://raw.githubusercontent.com/irontec/android-kotlin-samples/master/common-data/bilbao.json")
            val httpClient = url.openConnection() as HttpURLConnection
            if (httpClient.responseCode == HttpURLConnection.HTTP_OK) {
                try {
                    val stream = BufferedInputStream(httpClient.inputStream)
                    val data: String = readStream(inputStream = stream)
                    return data
                } catch (e: Exception) {
                    e.printStackTrace()
                } finally {
                    httpClient.disconnect()
                }
            } else {
                println("ERROR ${httpClient.responseCode}")
            }
            return null
        }

        fun readStream(inputStream: BufferedInputStream): String {
            val bufferedReader = BufferedReader(InputStreamReader(inputStream))
            val stringBuilder = StringBuilder()
            bufferedReader.forEachLine { stringBuilder.append(it) }
            return stringBuilder.toString()
        }

        override fun onPostExecute(result: String?) {
            super.onPostExecute(result)

            innerTextView?.text = JSONObject(result).toString()

            /**
             * ... Work with the weather data
             */

        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val id = item.itemId
        if (id == R.id.action_settings) {
            return true
        }
        return super.onOptionsItemSelected(item)
    }
}

link - Github Irontec


1
看起来非常不对。即使asynctask是一个内部类...我没有看到你在onPostExecute中检查活动的状态?此外,停止使用Asynctask。 - Radu

5

这是我在项目中避免内存泄漏的做法:

我创建了一个抽象的基础异步任务类 Async Task 用于异步加载。

import android.os.AsyncTask

abstract class BaseAsyncTask(private val listener: ProgressListener) : AsyncTask<Void, Void, String?>() {

    interface ProgressListener {
        // callback for start
        fun onStarted()

        // callback on success
        fun onCompleted()

        // callback on error
        fun onError(errorMessage: String?)

    }

    override fun onPreExecute() {
        listener.onStarted()

    }

    override fun onPostExecute(errorMessage: String?) {
        super.onPostExecute(errorMessage)
        if (null != errorMessage) {
            listener.onError(errorMessage)
        } else {
            listener.onCompleted()
        }
    }
}

用法:

现在每次我需要在后台执行某个任务时,我都会创建一个新的LoaderClass并将其扩展为我的BaseAsyncTask类,如下所示:

class LoadMediaTask(listener: ProgressListener) : BaseAsyncTask(listener) {

    override fun doInBackground(vararg params: Void?): String? {

        return VideoMediaProvider().allVideos
    }
}

现在你可以在应用程序的任何地方使用你的新AsyncLoader类。
以下是一个示例,展示/隐藏进度条并处理错误/成功的情况:
   LoadMediaTask(object : BaseAsyncTask.ProgressListener {
            override fun onStarted() {
                //Show Progrss Bar
                loadingBar.visibility = View.VISIBLE
            }

            override fun onCompleted() {
                // hide progress bar
                loadingBar.visibility = View.GONE
                // update UI on SUCCESS
                setUpUI()
            }

            override fun onError(errorMessage: String?) {
                // hide progress bar
                loadingBar.visibility = View.GONE
                // Update UI on ERROR
                Toast.makeText(context, "No Videos Found", Toast.LENGTH_SHORT).show()
            }

        }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)

如何读取任务返回的值?我调用了HTTP,得到了字符串响应。谢谢。 - shareef
不是最新的解决方案库,所以它适合我的旧代码!谢谢 - shareef

3

我经常使用这种表格:

open class LoadingProducts : AsyncTask<Void, Void, String>() {

private var name = ""

    override fun doInBackground(vararg p0: Void?): String {

        for (i in 1..100000000) {
            if (i == 100000000) {
                name = "Hello World"
            }
        }
        return name
    }
}

您可以使用以下方法进行调用:
loadingProducts = object : LoadingProducts() {
        override fun onPostExecute(result: String?) {
            super.onPostExecute(result)

            Log.e("Result", result)
        }
    }

loadingProducts.execute()

我使用open以便可以调用onPostExecute方法来获取结果。


3

我花了一整天的时间,试图找出如何获取Async Task生成的结果:协程是我的解决方案!!!

首先,创建您的AsyncTask对象...不要忘记使用正确的参数类型而不是全部使用Any。

@SuppressLint("StaticFieldLeak")
class AsyncTaskExample(private var activity: MainActivity?) : AsyncTask<Any, Int, Any?>() {

    override fun onPreExecute() {
        super.onPreExecute()
        // do pre stuff such show progress bar
    }

    override fun doInBackground(vararg req: Any?): Any? {

        // here comes your code that will produce the desired result
        return result 

    }

    // it will update your progressbar
    override fun onProgressUpdate(vararg values: Int?) {
        super.onProgressUpdate(*values)

    }


    override fun onPostExecute(result: Any?) {
        super.onPostExecute(result)

        // do what needed on pos execute, like to hide progress bar
        return
    }

}

然后,调用它(在这种情况下,是从主活动调用)

var task = AsyncTaskExample(this)
var req = { "some data object or whatever" }

GlobalScope.launch( context = Dispatchers.Main){

   task?.execute(req)
}

GlobalScope.launch( context = Dispatchers.Main){

   println( "Thats the result produced by doInBackgorund: " +  task?.get().toString() )
}

0

如果你想不使用Anko来完成它,正确的方法是使用以下方式

open class PromotionAsyncTask : AsyncTask<JsonArray, Void, MutableList<String>>() {

private lateinit var out: FileOutputStream
private lateinit var bitmap: Bitmap
private lateinit var directory: File
private var listPromotion: MutableList<String> = mutableListOf()

override fun doInBackground(vararg params: JsonArray?): MutableList<String> {

    directory = Environment.getExternalStoragePublicDirectory("Tambo")

    if (!directory.exists()) {
        directory.mkdirs()
    }

    for (x in listFilesPromotion(params[0]!!)) {
        bitmap = BitmapFactory.decodeStream(URL(x.url).content as InputStream)
        out = FileOutputStream(File(directory, "${x.name}"))

        bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
        out.flush()
        out.close()

        listPromotion.add(File(directory, "${x.name}").toString())
    }

    return listPromotion
}

private fun listFilesPromotion(jsonArray: JsonArray): MutableList<Promotion> {
    var listString = mutableListOf<Promotion>()

    for (x in jsonArray) {
        listString.add(Promotion(x.asJsonObject.get("photo")
                .asString.replace("files/promos/", "")
                , "https://tambomas.pe/${x.asJsonObject.get("photo").asString}"))
    }

    return listString}
}

而且执行它的方法如下所示

promotionAsyncTask = object : PromotionAsyncTask() {
                    override fun onPostExecute(result: MutableList<String>?) {
                        super.onPostExecute(result)
                        listFile = result!!

                        contentLayout.visibility = View.VISIBLE
                        progressLottie.visibility = View.GONE
                    }
                }
                promotionAsyncTask.execute(response!!.body()!!.asJsonObject.get("promos").asJsonArray)

0

我在一个组合中使用了LaunchedEffect

LaunchedEffect ("http_get") {
    withContext (Dispatchers.IO) {
        http_get() }}

在回调函数中使用rememberCoroutineScope

val scope = rememberCoroutineScope()
Button (
    onClick = {
        scope.launch {
            withContext (Dispatchers.IO) {
                http_get() }}})

看起来能工作,但我不知道为什么。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接