美文网首页Android开发
Android 异步操作

Android 异步操作

作者: 事多店 | 来源:发表于2017-03-04 18:10 被阅读456次

    Android为我们提供了几种异步线程操作:AsyncTask, HandlerThread, IntentService, ThreadPoolLoader。了解这几种操作的不同之处,有助于我们开发的时候使用正确的异步操作,避免不必要的坑。

    AsyncTask

    AsyncTask是我们平时使用频率最高的,利用它,我们可以很容易的将工作扔到异步线程中去,然后在工作完成之后再在UI线程中处理结果或者处理过程中与UI线程交互。但另一方面,它也很容易造成一些潜在的问题,比如说内存泄露。

    应用场景:

    与UI相关的工作,不太耗时的工作。例如,下载一张图片,并且在下载过程中需要不断更新进度条,然后下载完成后显示出来。

    需要注意的坑:

    1. 默认情况下,所有创建的AsyncTask会共享一个线程,换句话说,AsyncTask是以串行的方式执行的。如果有某个任务耗时太长,将会影响后面的任务。解决方法是将任务放到线程池中去执行。AsyncTask.executeOnExecutor,或者按照官方(性能优化视频)的建议,直接用线程池ThreadPoolExecutor代替AsyncTask
    2. 异步任务的取消:虽然AsyncTask提供了cancel方法,但是你直接调用该方法并不能让任务停止下来,因为线程没有办法直接停止正在运行的代码。所以我们还需要在doInBackground方法中,在恰当的时机中去检查标志位。
    doInBackground(...) {
        while (!isCanceled()) {
            // do your work.
        }
    }
    

    设置完标志位后,调用cancel方法才能使得线程中的任务提前终止。需要注意的一点是被取消掉的任务会回调onCancelled而不是onPostExecute
    3.很容易出现内存泄露:

    public class MainActivity extends AppCompatActivity {
        private class MyAsyncTask extends AsyncTask<Void, Integer, String> {
    
        @Override
        protected String doInBackground(Void... params) {
            // ...
            return null;
        }
    
        @Override
        protected void onPostExecute(String s) {
            // ...
        }
    }
    

    这是典型的内部类引用外部类问题。内部类MyAsyncTask隐形的持有了MainActivity的引用。假如Activity退出后,AsyncTask中的任务还没处理完,Activity还会被AsyncTask持有着,这将导致Activity无法被释放,直到AsyncTask中的任务处理完。并且,如果这个时候,你在onPostExecute中去操作UI,还有可能会报错。

    HandlerThread

    HandlerThread继承自Thread,内部封装了一个属于该线程的Looper,源码加上注释也就是150行左右。可以看出还是非常简单的一个类。有了Looper之后,我们就可以利用Handler发送消息到该线程中去执行了。

    应用场景:

    与UI无关的或者耗时的工作。例如,后台压缩一张大图片。与AsyncTask对比可以看出,HandlerThread是对后者的补充。

    需要注意的坑:

    1. 需要为HandlerThread设置线程优先级,默认情况下,它是和UI线程拥有相同优先级的,根据需要降低优先级可以保证UI线程不被阻塞。
    2. HandlerThread使用完成之后记得调用quitSafely或者quit方法完成退出,否则由于Looper是一个无限循环,会导致线程无法正常退出。

    ThreadPool

    线程池,顾名思义,一个池子里面躺着很多的线程。在开始的时候就将这个池里的线程初始化完,待到需要使用的时候,就从池子里取出线程。在Android中我们可以使用ThreadPoolExecutor来实现线程池。

    应用场景:

    适合并发工作的。假设你有40张图片需要处理,每张图片需要4ms,放在HandlerThread中的话需要160ms,而如果将这些图片处理放到5个线程中去同时处理的话,则只需要32ms,整整提高了5倍速度。

    需要注意的坑:

    理论上来说,你可以为一个线程池创建很多很多的线程,但是CPU只能同时执行一定数量的线程,当线程超过这个数量时,就需要进行线程调度了,即根据每个线程的优先级来分配时间片,所以线程并不是越多越好的。而且,创建线程也是有代价的,每一个线程至少要占用64k内存,还很容易就上升。所以我们需要为线程池找到一个合理的线程数。我们可以根据每个机器当前可用的CPU个数来确定线程数,如:

    // 需要注意的是,该方法并不是返回你手机物理上的CPU个数,因为有些CPU可能处于未激活状态,这取决于系统的负载。
    private static int NUMBER_OF_THREAD = Runtime.getRuntime().availableProcessors();
    

    IntentService

    IntentServiceServiceHandlerThread的结合体。虽然Service是运行在后台的,但是实际上它还是运行在UI线程中的,所以它并不适合用来执行耗时的操作。好在Android为我们提供了IntentService,它继承自Service,但在内部封装了HandlerThread。它在收到Intent时,会把Intent发送到HandlerThread中,让它来完成相应的耗时任务。同时,IntentService又拥有Service的优点(其中之一是当系统资源紧张的时候,它被回收掉的可能性在处于前台的app与处于后台的app之间)。

    应用场景:

    假设你在UI线程收到了一个Intent,但处理这个Intent的工作又会比较耗时,这时候就可以利用IntentService来处理。

    需要注意的坑:

    1. HandlerThread内部是通过消息队列来处理任务的,这意味着如果有某个任务耗时太长的话,会影响其它任务的执行。因为Service是可以接收很多的Intent的,即消息队列很可能会有很多的消息等待着处理。这与我们直接使用HandlerThread还是有点不同的(因为直接使用可能消息队列中就只有一个任务需要处理)。
    2. 用到IntentService的时候,我们经常会使用BroadcastReceiver来将结果返回到UI线程中,建议使用LocalBroadcastReceiver来代替,它比较轻量级。

    Loader

    Loader是Android 3.0之后才出现的,也是官方推荐的异步操作:它的优点有

    1. 不用担心内存泄露,这是因为Loader会帮我们同步Activity或者Fragment的生命周期。这是其它异步线程操作所不具备的。
    2. 缓存结果,所以当结果已经获得之后再次查询可以立即返回结果。

    应用场景

    Activity和Fragment中需要异步操作的地方。

    总结

    这篇文章并没有对每一种异步操作进行详细的分析,只是为了对比几种操作的优缺点、应用场景以及需要注意的地方。了解这些异步操作的不同之处,有助于帮助我们在开发过程中正确的选择相应的异步操作,减少不必要的麻烦。

    参考资料

    来源于Android官方的性能优化视频(需要梯子):

    1. Good AsyncTask Hunting.
    2. Getting a HandlerThread
    3. Swimming in ThreadPools
    4. The Zen of IntentService
    5. Threading and Loaders

    相关文章

      网友评论

        本文标题:Android 异步操作

        本文链接:https://www.haomeiwen.com/subject/tbsjgttx.html