Android的异步任务实现

异步概念

异步作为一种编程模式,它允许应用程序在不阻塞主线程的情况下执行耗时操作。这种任务通常在后台线程中执行,比如网络请求、文件读写、复杂计算等,从而不会影响用户界面的响应性。

几乎所有应用开发都需要实现异步,例如某个网盘中,当用户触发一个下载操作时,应用可以在后台线程中执行下载任务,同时主线程可以继续响应用户的其他操作,如滑动、点击等。下载完成后,再通过回调机制更新UI,显示下载结果。这种方式使得应用在执行耗时操作时仍能保持流畅和响应。

在Android开发中,线程和线程池是处理异步任务的基础,通过将耗时操作从主线程(UI线程)转移到后台线程,可以避免界面冻结,从而提升用户体验。线程池允许用户重复使用有限数量的线程来执行多个任务,而不是为每个任务创建和销毁新线程,显著减少资源消耗的同时提高效率。

有关线程与线程池更加详尽的内容可以参考Android线程与线程池

Android中的异步实现

随着Android技术的发展,异步实现的方法也在不断进步。从传统的线程和线程池,到已经被弃用的AsyncTask工具类,再到更加轻量化的Kotlin协程,开发者有多种工具可以选择来实现异步任务。每种方法都有其独特的优势和适用场景,选择合适的异步实现方式对于优化应用性能至关重要。

线程池实现异步

通过Executors类提供的静态方法,如newCachedThreadPool()newFixedThreadPool()newSingleThreadExecutor(),可以轻松实例化ExecutorService接口,从而获得线程池对象。这些方法允许开发者根据任务特性选择合适的线程池类型。动态线程池newCachedThreadPool()会根据任务需求动态创建新线程,而固定线程池newFixedThreadPool()则保持固定数量的线程执行任务。单线程的newSingleThreadExecutor()则确保所有任务在单个线程中按顺序执行。这种方法使得线程的创建和销毁由线程池统一管理,实现了线程的复用,减少了资源消耗。

以下是使用Executors.newFixedThreadPool()创建固定大小线程池并执行网络请求的示例代码。

import java.io.BufferedReader
import java.io.BufferedInputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.Callable
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future

class NetworkTask(private val urlString: String) : Callable<String> {
    override fun call(): String {
        return try {
            val url = URL(urlString)
            val urlConnection = url.openConnection() as HttpURLConnection
            try {
                val inputStream = BufferedInputStream(urlConnection.inputStream)
                val bufferedReader = BufferedReader(InputStreamReader(inputStream))
                val stringBuilder = StringBuilder()
                bufferedReader.forEachLine { stringBuilder.append(it) }
                stringBuilder.toString()
            } finally {
                urlConnection.disconnect()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            "Error: ${e.message}"
        }
    }
}

fun main() {
    val executor: ExecutorService = Executors.newFixedThreadPool(4) // 创建固定大小的线程池

    val url = "https://api.example.com/data"
    val future: Future<String> = executor.submit(NetworkTask(url)) // 提交网络请求任务

    // 等待任务完成并获取结果
    val result = future.get() // 阻塞直到任务完成

    println("Data fetched: $result")

    executor.shutdown() // 关闭线程池
}

使用Executors.newFixedThreadPool(4)创建了一个固定大小为4的线程池。NetworkTask是一个Callable任务,封装了网络请求的逻辑,通过复用线程,减少了频繁创建和销毁线程的开销。

AsyncTask实现异步

AsyncTask是Android封装的一个异步类,它在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI,它的内部封装了'SerialExecutor','THREAD_POOL_EXECUTOR' 两个线程池一个Handler(InternalHandler)。

AsyncTask的使用方法

AsyncTask的使用涉及三个主要的泛型参数和四个关键方法:

泛型参数:

        Params:执行任务时传入的参数类型。

        Progress:后台任务执行过程中,进度更新时使用的数据类型。

        Result:后台任务执行完成后返回的结果类型。

关键方法:

        onPreExecute():在后台任务开始之前执行,通常用于任务前的初始化操作。

        doInBackground(Params...):在后台线程中执行实际的耗时操作。

        onProgressUpdate(Progress...):在UI线程中更新任务进度。

        onPostExecute(Result):在后台任务完成后,在UI线程中执行结果处理。

除了上面四个方法,AsyncTask还提供了onCancelled()方法,它同样在主线程中执行,当异步任务取消时,onCancelled()会被调用,这个时候onPostExecute()则不会被调用,但是要注意的是,AsyncTask中的cancel()方法并不是真正去取消任务,只是设置这个任务为取消状态,我们需要在doInBackground()判断终止任务。就好比想要终止一个线程,调用interrupt()方法,只是进行标记为中断,需要在线程内部进行标记判断然后中断线程。

下面是一个简单的示例,展示如何使用AsyncTask进行网络请求。

import android.os.AsyncTask
import java.io.BufferedReader
import java.io.BufferedInputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL

class FetchDataAsyncTask(private val callback: (String) -> Unit) : AsyncTask<String, Void, String>() {

    override fun onPreExecute() {
        super.onPreExecute()
        // 任务开始前的初始化操作,例如显示加载动画
    }

    override fun doInBackground(vararg params: String?): String {
        val urlString = params[0]
        return try {
            val url = URL(urlString)
            val urlConnection = url.openConnection() as HttpURLConnection
            try {
                val inputStream = BufferedInputStream(urlConnection.inputStream)
                val bufferedReader = BufferedReader(InputStreamReader(inputStream))
                val stringBuilder = StringBuilder()
                bufferedReader.forEachLine { stringBuilder.append(it) }
                stringBuilder.toString()
            } finally {
                urlConnection.disconnect()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            "Error"
        }
    }

    override fun onPostExecute(result: String) {
        super.onPostExecute(result)
        // 处理任务结果,例如隐藏加载动画并更新UI
        callback(result)
    }
}

// 调用方式
val url = "https://api.example.com/data"
FetchDataAsyncTask { result ->
    // 处理结果,例如更新UI
    println("Data fetched: $result")
}.execute(url)

相比于线程池,AsyncTask的API简单直观,易于理解和使用,并且开发者无需手动创建和管理线程,但从Android 12开始,AsyncTask已被弃用,现在Android处理异步任务通常使用kotlin协程作为实现方式。

Kotlin协程实现异步

Kotlin协程是一种轻量级的异步编程方式,它允许你在不阻塞主线程的情况下执行耗时操作。通过挂起函数(suspend functions)和协程作用域(coroutine scopes),Kotlin协程简化了异步代码的编写,使得代码看起来像是同步执行的,但实际上是异步执行的。

Kotlin协程中有几个重要的概念:

挂起函数(Suspend Functions)

挂起函数可以在等待其他操作完成时挂起,而不会阻塞当前线程。挂起函数通过suspend关键字标记,可以调用其他挂起函数。

非阻塞调用:挂起函数的调用是非阻塞的,它们会在需要等待操作完成时挂起协程,让出CPU给其他任务。

 调用限制:挂起函数只能在协程作用域内或另一个挂起函数中被调用。

协程构建器(Coroutine Builders)

协程构建器是用于启动新协程的函数。它们提供了不同的行为和特性,以适应不同的使用场景。

 launch:启动一个新的协程,并返回一个Job对象。它不等待协程完成,立即返回。

 async:启动一个新的协程,并返回一个Deferred对象。Deferred是一个持有协程结果的容器,可以调用await来获取结果。

 runBlocking:启动一个新的协程,并阻塞当前线程直到协程完成。通常用于主函数或测试中。

Job

Job是Kotlin协程中表示协程执行的类。它允许开发者管理协程的生命周期,比如取消协程或检查协程的状态。

 取消协程:可以通过调用Job.cancel()来取消协程的执行。

 状态检查:可以检查Job的状态,比如是否完成、是否取消等。

Dispatchers

Dispatchers定义了协程执行的线程。它们允许开发者指定协程应该在哪个线程上执行,从而更好地控制并发和性能。

    Dispatchers.Main:用于在主线程(UI线程)上执行协程。

    Dispatchers.IO:用于在后台线程上执行I/O操作,如网络请求或文件读写。

    Dispatchers.Default:用于在默认的后台线程池上执行协程,适合计算密集型任务。

以下是使用Kotlin协程实现网络请求的示例。

import kotlinx.coroutines.*

suspend fun fetchDataFromNetwork(url: String): String {
    return withContext(Dispatchers.IO) {
        val url = URL(url)
        val urlConnection = url.openConnection() as HttpURLConnection
        try {
            val inputStream = BufferedInputStream(urlConnection.inputStream)
            val bufferedReader = BufferedReader(InputStreamReader(inputStream))
            val stringBuilder = StringBuilder()
            bufferedReader.forEachLine { stringBuilder.append(it) }
            stringBuilder.toString()
        } finally {
            urlConnection.disconnect()
        }
    }
}

fun main() = runBlocking {
    val url = "https://api.example.com/data"
    val result = fetchDataFromNetwork(url)
    println("Data fetched: $result")
}

挂起函数fetchDataFromNetwork在后台线程中执行网络请求。

相比于线程池和 AsyncTask,Kotlin协程的协程代码更简洁,易于理解和维护,并且支持多种协程构建器,如launchasync,对于不同的使用场景有着优秀的适应性,并且协程提供了更好的异常处理机制,使得错误管理更加直观。而在资源管理上,协程可以更优雅地处理资源释放,减少内存泄漏的风险。

Kotlin协程是处理异步任务的现代Android解决方案,它提供了比AsyncTask更简洁、更强大的编程模型。通过协程,开发者可以更轻松地编写高效且易于维护的异步代码。

总结

在Android应用开发中,异步任务处理是确保应用响应性和性能的关键。随着用户对流畅体验的期望不断提高,传统的同步执行方式已经无法满足需求。异步执行允许应用在后台处理耗时操作,同时保持前台界面的流畅和响应。

在早期的Android开发中,异步任务通常通过手动创建线程或使用ThreadHandler来实现。这种方式虽然有效,但代码复杂且容易出错。随着AsyncTask的引入,开发者获得了一种更简单的异步任务处理方式。AsyncTask允许在后台线程中执行耗时操作,并在操作完成后将结果回调到主线程。然而,随着应用复杂度的增加,AsyncTask的一些限制(如内存泄漏风险和执行顺序问题)逐渐暴露。

随着谷歌宣布Kotlin成为安卓开发的首选语言,协程成为了一种越来越常见的异步任务处理方式。Kotlin协程通过挂起函数和协程作用域,简化了异步代码的编写。挂起函数允许在等待异步操作完成时挂起当前协程,而不会阻塞线程。协程作用域则管理着协程的生命周期,确保它们在适当的时机启动和取消。这种方式不仅代码更简洁,而且易于理解和维护。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android异步任务是一种常用的执行耗时操作的方式,但是在进行单元测试时,由于异步任务的特性,需要特别注意。下面将介绍如何进行Android异步任务的单元测试。 首先,创建一个测试类,并使用@RunWith和@LargeTest注解来告诉JUnit运行器我们正在进行测试: ``` @RunWith(AndroidJUnit4.class) @LargeTest public class MyTaskTest { } ``` 然后,在测试类中创建一个测试方法,使用@RunWith和@UiThreadTest注解来标记该方法需要在UI线程上运行: ``` @Test @UiThreadTest public void testMyTask() { // 测试代码 } ``` 接下来,创建一个CountDownLatch对象,在异步任务中的onPostExecute方法中,调用countDown()方法来通知主线程异步任务已完成: ``` protected void onPostExecute(Result result) { // 异步任务已完成,通知主线程 countDownLatch.countDown(); } ``` 在测试方法中,需要使用countDownLatch.await()来等待异步任务完成: ``` @Test @UiThreadTest public void testMyTask() throws InterruptedException { final CountDownLatch countDownLatch = new CountDownLatch(1); // 创建异步任务 MyTask myTask = new MyTask() { @Override protected void onPostExecute(Result result) { super.onPostExecute(result); // 异步任务已完成,通知主线程 countDownLatch.countDown(); } }; // 执行异步任务 myTask.execute(); // 等待异步任务完成 countDownLatch.await(); // 进行断言 // ... } ``` 最后,在测试方法中,进行断言来验证异步任务的执行结果是否符合预期。 通过以上步骤,我们可以实现Android异步任务的单元测试,确保其正确性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值