多线程优化

多线程基础

多线程基础

AsyncTask

执行流程

new AsyncTask()

public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                Result result = doInBackground(mParams);
                Binder.flushPendingCommands();
                return postResult(result);
            }
        };

        mFuture = new FutureTask<Result>(mWorker) {
            @Override
            protected void done() {
                try {
                    postResultIfNotInvoked(get());
                } catch (InterruptedException e) {
                    android.util.Log.w(LOG_TAG, e);
                } catch (ExecutionException e) {
                    throw new RuntimeException("An error occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

会初始化一个Callable和一个FutureTask,当然Callable是要被传到FutureTask的构造方法中去的,最终线程池执行的FutureTask,这两个都是成员变量。

在Callable中可以看到:Result result = doInBackground(mParams); 然后在FutureTask中它有一个执行完成的回调方法done()

之所以用FutureTask就是因为它是负责执行多线程可以返回执行结果的。

然后:

在这里面,它就是把执行的结果给发到主线程,里面有当前AsyncTask引用和result对象。

这个是因为在doBackground中去调用publishProgress(Progress... values),然后可以在主线程中接收到它的处理逻辑。

所有任务都是最后由它来分发执行的。

最后关键的来了,最后肯定是要调用执行的

重点看下:

我们看下sDefaultExecutor是个什么东西

也就是说exec.execute(mFuture);这句话会调用SerialExecutor的方法,每次调用execute都会把futureTask给加进去,然后放到一个双端队列里面,如果当前没有执行的Runnable就从队列中取出一个任务去执行,如果有任务的话,那么就只是先单纯的加到队列里,并不会执行。因为每次执行完一个Runnable后,它有一个finally方法,最后一次会再次调用scheduleNext方法,然后再从队列中去取出一个任务去执行,当然前提是队列不能为空,所以,它的逻辑是其实是执行一个任务结束后,然后就去队列里去取任务,如果有就放到线程池里去执行,就是这么简单。所以它是串行的。它是通过先把任务缓存到一个队列里,然后执行完一个就从队列中取出一个放到线程池中去执行,也就是说,队列里有多个任务,但是,线程池始终只有一个任务。

不过线程的无界队列 new LinkedBlockingQueue(128)这个128有点让人恍惚,其实它是指如果你配置成多线程并行的情况下,缓冲队列最多有128个,否则就执行Reject策略(默认是AbortPolicy)。

我们再来看下配置线程池的相关代码

CORE_POOL_SIZE 核心线程数 MAXIMUM_POOL_SIZE 最大线程数量 KEEP_ALIVE 1s闲置回收 TimeUnit.SECONDS 时间单位 sPoolWorkQueue 异步任务队列 sThreadFactory 线程工厂

首先你要明白,这个128的限制是指你用executeOnExecutor(Executor exec,Params... params)时,并行才会用到这个128的队列,也就是说它是一个有容量限制的无界队列(故意指定的容量大小)。

关于线程池,先会把CORE_POOL_SIZE这个填满,填满之后,再往缓存队列里放,缓存队列也满了,就增加线程,直到增加到MAXIMUM_POOL_SIZE,然后如果再有新的任务,那么 就只能执行拒绝策略了。

如果你自己配置一个无界队列,那么 任务就随便放了直到把系统干爆为止。

上面的代码是每隔1s打印一次,你一次new出来200个也不是说都把放到线程池中的BlockingQueue中,而是放到Executor中的ArrayDeque这个是会不断扩容的队列,明白了吧。

AsyncTask有什么缺点:

  1. 内存泄露 — 这是因为匿名内部类持有外部类的引用,所以如果子线程没有执行完毕,

  2. 线程池容量不够抛出异常。这个异常其实基本不会出现,因为默认情况下是串行的,而且如果你要自定义线程池的话,可以设置一个更大的BlockingQueue啊。所以这点基本不存在。

Android AsyncTask完全解析,带你从源码的角度彻底理解

HandlerThread

HandlerThread本质上就是一个普通Thread,只不过内部建立了Looper.

HandlerThread的特点:

  • HandlerThread将loop转到子线程中处理,说白了就是将分担MainLooper的工作量,降低了主线程的压力,使主界面更流畅

  • 开启一个线程起到多个线程的作用。处理任务是串行执行,按消息发送顺序进行处理。HandlerThread本质是一个线程,在线程内部,代码是串行处理的

  • 但是由于每一个任务都将以队列的方式逐个被执行到,一旦队列中有某个任务执行时间过长,那么就会导致后续的任务都会被延迟处理

  • HandlerThread拥有自己的消息队列,它不会干扰或阻塞UI线程

  • 对于网络IO操作,HandlerThread并不适合,因为它只有一个线程,还得排队一个一个等着。

IntentService

IntentService内部会创建一个HandlerThread,onHandleIntent在HandlerThread线程中执行

IntentService源码:

看明白了吧,在onCreate中去初始化HandlerThread和用HandlerThread的Looper初始化的Handler,在onStart中发送消息,在handlerMessage中去调用onHandleIntent,然后我们重写onHandleIntent方法即可。

IntentService(本质:Service+HandlerThread+Intent) startService 至少要有一个空的构造方法

优点

  1. 提高子线程的优先级

  2. 减轻主线程的压力

它有一个很大的缺点就是,它无法直接和主线程进行通信,其实很多业务是后台做完请求之后要在UI上更新,其实它无法实现,虽然可以用消息机制通信

Loader

在 Android 配备的几大异步利器中,Loader 是3.0之后才出现的,主要为了方便在 Activity 和 Fragment 中异步加载数据。它有如下的特点:

  • 每个Activity 和 Fragment 都可以使用 (3.0之前可以使用 support-lib)

  • 拥有异步加载数据的功能

  • 监视数据源,如果数据发生变化了,会自动传回更新后的结果

  • 如果被重新创建(比如屏幕选装)了会自动连接到上次的Cursor,这样不需要重新查询数据

上面说过了,Android提供了很多后台任务的机制,不过除了AsyncTask,无法把后台执行的结果给调用者(当然FutureTask可以,AsyncTask也是这样做的),还有一个就是Loader可以在后台加载数据,然后和调用者通信。

Loader保证子线程与Activity或者Fragment的生命周期一致 Activity和Fragment自带LoaderManager

优点: 1.方便 2.Activity或者Fragment的生命周期一致 3.数据缓存与更新通知

Activity中启动子线程的缺点

  1. 内存泄露

  2. 无效的更新UI

其实Loader后台的执行是靠AsyncTask

Android之Loader介绍

Last updated

Was this helpful?