Android 异步任务的6种实现方式详解

阿木木 阿木木 | 104 | 2022-08-19

Android 异步任务的6种实现方式详解
Android UI线程(主线程)有几个特点:

只能在 UI 线程操作 UI 视图,不能在子线程中操作。
不能在 UI 线程中进行耗时操作,否则会阻塞 UI 线程,引起 ANR、卡顿等问题。
在 Android 开发中,我们通常将一些耗时的操作使用异步任务的方式进行处理。例如这样一种这种场景,子线程在后台执行一个异步任务,任务过程中,需要 UI 进程展示进度,这时我们就需要一个工具来实现这种需求。Android 系统为开发人员提供了一个异步任务类(AsyncTask)来实现上面所说的功能,即它会在一个子线程中执行计算任务,同时通过主线程的消息循环来获得更新应用程序界面的机会。

Android 开发中常用的异步任务有哪些呢?

1. 使用 Thread 创建

最直接的方式,就是使用 Java 提供的 Thread 类进行线程创建,从而实现异步。

关于 Thread 的创建方式,请参考《Java 创建线程的三种方式总结》

2. Thread + Looper + handler

Android 提供了 Handler 机制来进行线程之间的通信,我们可以使用 Android 最基础的异步方式:Thread + Looper + handler 来进行异步任务。

关于 Handler 相关机制原理,请参考:《Handler线程通信机制:实战、原理、性能优化!》

示例代码:

Handler mHandler = newHandler(){
    @Override
    publicvoid handleMessage(Message msg){
        if(msg.what == 1){
            textView.setText("Task Done!!");
        }
    }
};
mRunnable = new Runnable() {
    @Override
    publicvoid run() {
        SystemClock.sleep(1000);    // 耗时处理
        mHandler.sendEmptyMessage(1);  
    }
};
private void startTask(){
    new Thread(mRunnable).start();
}

优点:
操作简单,无学习成本。
缺点:
代码规范性较差,不易维护。
每次操作都会开启一个匿名线程,系统开销较大。

3. AsyncTask

较为轻量级的异步类,封装了 FutureTask 的线程池、ArrayDeque 和 Handler 进行调度。AsyncTask 主要用于后台与界面持续交互。

我们来看看 AsyncTask 这个抽象类的定义,当我们定义一个类来继承 AsyncTask 这个类的时候,我们需要为其指定3个泛型参数:

AsyncTask <Params, Progress, Result>

Params: 这个泛型指定的是我们传递给异步任务执行时的参数的类型。
Progress: 这个泛型指定的是我们的异步任务在执行的时候将执行的进度返回给UI线程的参数的类型。
Result: 这个泛型指定的异步任务执行完后返回给UI线程的结果的类型。
我们在定义一个类继承 AsyncTask 类的时候,必须要指定好这三个泛型的类型,如果都不指定的话,则都将其写成 void。

我们来看一个官方给的例子:

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }
     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
}

使用时只需要集成 AsyncTask,创建对象并调用 execute 执行即可:

new DownloadFilesTask().execute(url1, url2, url3);

doInBackground(Params…) 方法里执行耗时逻辑,然后在 onPostExecute(Result) 中将结果更新回UI组件

AsyncTask 的几个主要方法中,doInBackground 方法运行在子线程,execute、onPreExecute、onProgressUpdate、onPostExecute 这几个方法都是在 UI 线程运行的。

使用 AsyncTask 的注意事项
AsyncTask 的实例必须在 UI Thread 中创建。
只能在 UI 线程中调用 AsyncTask 的 execute 方法。
AsyncTask 被重写的四个方法是系统自动调用的,不应手动调用。
每个 AsyncTask 只能被执行一次,多次执行会引发异常。
AsyncTask 的四个方法,只有 doInBackground 方法是运行在其他线程中,其他三个方法都运行在 UI 线程中,也就说其他三个方法都可以进行 UI 的更新操作。
AsyncTask 默认是串行执行,如果需要并行执行,使用接口 executeOnExecutor 方法。
优点:
结构清晰,使用简单,适合后台任务的交互。
异步线程的优先级已经被默认设置成了:THREAD_PRIORITY_BACKGROUND,不会与 UI 线程抢占资源。
缺点:
结构略复杂,代码较多。
每个 AsyncTask 只能被执行一次,多次调用会发生异常。
AsyncTask 在整个 Android 系统中维护一个线程池,有可能被其他进程的任务抢占而降低效率。
注:关于 AsyncTask 的原理及更多详情,请参考《AsyncTask你真的会用吗?实战、原理、最佳实践!(Android Q)》。

4. HandlerThread

HandlerThread 是一个自带 Looper 消息循环的线程类。处理异步任务的方式和 Thread + Looper + Handler 方式相同。

优点:
简单,内部实现了普通线程的 Looper 消息循环。
可以串行执行多个任务。
内部拥有自己的消息队列,不会阻塞 UI 线程。
缺点:
没有结果返回接口,需要自行处理。
消息过多时,容易造成阻塞。
只有一个线程处理,效率较低。
线程优先级默认优先级为 THREAD_PRIORITY_DEFAULT,容易和 UI 线程抢占资源。
注:更多详情请参考:《HandlerThread原理分析、实战、最佳实践!》。

5. IntentService

IntentService 继承自 Service 类,用于启动一个异步服务任务,它的内部是通过 HandlerThread 来实现异步处理任务的。

我们来看下 IntentService 的主要方法:

  @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

优点:
只需要继承 IntentService,就可以在 onHandlerIntent 方法中异步处理 Intent 类型任务了。
任务结束后 IntentService 会自行停止,无需手动调用 stopService。
可以执行处理多个 Intent 请求,顺序执行多任务。
IntentService 是继承自 Service,具有后台 Service 的优先级。
缺点:
需要启动服务来执行异步任务,不适合简单任务处理。
异步任务是由 HandlerThread 实现的,只能单线程、顺序处理任务。
没有返回 UI 线程的接口。

6. 使用线程池来处理异步任务

利用 Executors 的静态方法 newCachedThreadPool()、newFixedThreadPool()、newSingleThreadExecutor() 及重载形式实例化 ExecutorService 接口即得到线程池对象。

动态线程池 newCachedThreadPool():根据需求创建新线程的,需求多时,创建的就多,需求少时,JVM 自己会慢慢的释放掉多余的线程。
固定数量的线程池 newFixedThreadPool():内部有个任务阻塞队列,假设线程池里有2个线程,提交了4个任务,那么后两个任务就放在任务阻塞队列了,即使前2个任务 sleep 或者堵塞了,也不会执行后两个任务,除非前2个任务有执行完的。
单线程 newSingleThreadExecutor():单线程的线程池,这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去。
优点:
线程的创建和销毁由线程池来维护,实现了线程的复用,从而减少了线程创建和销毁的开销。
适合执行大量异步任务,提高性能。
灵活性高,可以自由控制线程数量。
扩展性好,可以根据实际需要进行扩展。
缺点:
代码略显复杂。
线程池本身对系统资源有一定消耗。
当线程数过多时,线程之间的切换成本会有很大开销,从而使性能严重下降。
每个线程都会耗费至少 1040KB 内存,线程池的线程数量需要控制在一定范围内。
线程的优先级具有继承性,如果在 UI 线程中创建线程池,线程的默认优先级会和 UI 线程相同,从而对 UI 线程使用资源进行抢占。
总结
本文详细介绍了 Android 中,实现异步任务的六种方式:

使用 Thread 创建
使用 Thread + Looper + handler
使用 AsyncTask
使用 HandlerThread
使用 IntentService
使用线程池来处理异步任务
同时,我们也分析了,每种方式的使用的场景以及它的优点和缺点。

文章标签: Android
推荐指数:

真诚点赞 诚不我欺~

Android 异步任务的6种实现方式详解

点赞 收藏 评论