Android进阶学习(六)Android的线程和线程池

文本是阅读《Android开发艺术探索》的学习笔记记录,详细内容可以自行阅读书本。

Android的线程和线程池

  线程在Android中是一个很重要的概念,从用途来说,线程分为主线程和子线程。主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时操作。
  除了Thread本身以外,在Android中可以扮演线程角色的还有很多,比如AsyncTask和IntentService,同时HandlerThread也是一种特殊的线程。

Android线程 底层实现 特性和使用场景
AsyncTask 封装了线程池和Handler 方便开发者在子线程更新UI
IntentService 底层使用线程 内部采用HandlerThread来执行任务,执行完成后IntentService自动退出。但是IntentService是一种服务,不容易被系统杀死。
HandlerThread 底层使用线程 具有消息循环的线程,在它内部可以使用Handler

  线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源。如果需要频繁的创建和销毁线程,正确做法就是使用线程池。Android中的线程池来源于Java,主要是通过Executor来派生特定类型的线程池。

1 主线程和子线程

  Android中的线程除了传统的Thread以外,还包含AsyncTask、HandlerThread以及IntentService。

2 Android中的线程形态

2.1 AsyncTask

  AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。从实现上来说,AsyncTask封装了Thread和handler,通过AsyncTask可以更加方便执行后台任务以及在主线程访问UI,但AsyncTask并不适合进行特别耗时的后台任务,对于特别耗时任务,建议使用线程池。

  AsyncTask是一个抽象的泛型类,它提供了Params、Progress和Result三个泛型参数。

参数 含义
Params 表示参数类型
Progress 表示后台任务的执行进度的类型
Result 表示后台任务的返回结果的类型
public abstract class AsyncTask<Params, Progress, Result> 

  AsyncTask提供了4个核心方法,含义如下

方法名 含义
onPreExecute() 任务开始时执行,用于进行资源的准备和初始化。
doInBackground(Params… params) 在子线程中运行,用于执行耗时异步任务,Params参数表示异步任务的输入参数。此方法中可以通过调用publishProgress方法来更新任务进度,publishProgress会调用onProgressUpdate方法。另外此方法需要返回计算结果给onPostExecute
onProgressUpdate(Progress… values) 在主线程中执行,当后台任务的执行进度发送改变时此方法被调用。
onPostExecute(Result result) 在主线程中执行,在异步任务执行之后,此方法被调用。其中result参数是后台任务的返回值,即doInBackground的返回值。

  AsyncTask使用过程中,需要注意几点:

  1)AsyncTask对象必须在主线程中创建。
  2)execute方法必须在UI线程调用。
  3)不要在程序中直接调用onPreExecute、doInBackground、onProgressUpdate和onPostExecute方法。
  4)一个AsyncTask对象只能执行一次,即调用一次execute方法,否则会报异常。

2.2 AsyncTask的工作原理

  分析AsyncTask的工作原理,我们先从它的execute方法开始分析,execute方法又调用executeOnExecutor方法,它们的实现如下:

    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

  从上面代码中,sDefaultExecutor实际上是一个串行的线程池,一个进程中所有的AsyncTask全部在这个串行的线程池中排队执行,稍后分析。在executeOnExecutor方法中,会对状态进行判断,如果是正在运行或者已经完成则抛出异常。onPreExecute方法会最先执行,然后线程池开始执行。接下来分析线程池的执行过程:

    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

  从SerialExecutor的实现分析AsyncTask的排队执行过程。首先系统会把AsyncTask的Params参数封装为FutureTask对象,FutureTask是个并发类,在这里它充当了Runnable的作用。

  接着SerialExecutor的execute方法首先会把FutureTask对象插入任务队列mTasks中,如果此时没有正在活动的AsyncTask,那么会调用
SerialExecutor的scheduleNext方法来执行下一个AsyncTask任务。同时当一个AsyncTask任务执行完成后,AsyncTask会继续执行其他任务直到所有任务都被执行完成。可以看出,默认情况下,AsyncTask是串行执行的。

  AsyncTask中有两个线程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一个Handler(InternalHandler)。其中SerialExecutor用于任务的排队,而线程池THREAD_POOL_EXECUTOR用于真正地执行任务。InternalHandler用于将执行环境从线程池切换到主线程。

  用于FutureTask的run会调用mWorker的call方法,因此mWorker的call方法最终会在线程池中执行。在AsyncTask的构造方法中,声明mWorker的call方法。

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

  从上述代码可知,首先将mTaskInvoked设为true,表示当前任务已经被调用过了。然后执行AsyncTask的doInBackground方法,接着将其返回值传递给postResult方法。postResult的实现如下:

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }

  从上述代码可知,postResult方法会通过Handler发送一个MESSAGE_POST_RESULT的消息,从代码中可以看出这个getHandler方法返回的就是InternalHandler对象,这个InternalHandler定义如下:

    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

  从上述代码可知,InternalHandler是一个静态Handler对象,为了能够切换到主线程,就需要InternalHandler对象在主线程中创建。用于静态成员会在加载类的时候进行初始化,因此就要求AsyncTask必须在主线程创建。再来看看finish方法定义:

    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

  从上述代码可知,如果AsyncTask取消就调用onCancelled,否则就调用onPostExecute并把doInBackground的返回值传递过来。

2.3 HandlerThread

  HandlerThread继承了Thread,它是一种可以使用Handler和Thread。它在run方法中通过 Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际使用中就允许在HandlerThread中创建Handler了。

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

  从HandlerThread的实现来看,它和普通Thread有着显著不同之处。普通Thread主要用于在run方法中执行一个耗时任务,而HandlerThread在内部创建了消息队列,外界要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。它在Android使用的具体场景是IntentService。
  由于HandlerThread的run是无限循环,因此当明确不需要再使用HandlerThread时,可以通过它的quit或者quitSafely来终止线程的执行。

2.4 IntentService

  IntentService是一个特殊的Service,它继承Service并且是一个抽象类,因此必须创建它的子类才可以使用。IntentService可用于执行后台耗时任务,当任务结束后它会自动停止。因为IntentService是服务的原因,优先级比起单纯线程高很多,所有比较适合执行一些高优先级的后台任务。

  再实现上,IntentService封装了HandlerThread和Handler,可以从oncreate方法看出,如下:

    @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);
    }

  从上述代码可以看出,当IntentService第一次被启动时,它的onCreate方法会被调用,会创建一个HandlerThread,然后使用它的Looper来构造一个Handler对象mServiceHandler。这样通过mServiceHandler发送的消息最终会在HandlerThread中执行。

  每次启动IntentService,它的onStartCommand方法就会调用一次,在onStartCommand方法中处理每个后台任务的Intent。代码如下:

    @Override
    public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
        onStart(intent, startId);
        return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
    }

    @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仅仅是通过mServiceHandler发送了一个消息,这个消息会在HandlerThread中处理。

  然后消息会被mServiceHandler接收,会将Intent对象传给onHandleIntent方法来处理。通过解析Intent对象就可以区分具体的后台任务。当onHandleIntent方法执行完毕后,IntentService会通过stopSelf(int startId)方法来停止服务。不使用stopSelf()方法是因为,立即停止可能会有未处理完成的其他消息。

  qbIntentService的onHandleIntent是一个抽象方法,需要我们在子类中实现。如果目前只有一个存活的后台任务,那么onHandleIntent执行完毕后就会停止服务。

  如果目前存在多个后台任务,由于每次执行一个后台任务就必须启动一次IntentService,而IntentService内部通过消息方式向HandlerThread请求执行任务,Handler中的Looper是顺序处理消息的。这意味着IntentService也是顺序执行后台任务的,后台任务会按照外界发起的顺序排队执行。全部执行完毕后,服务才停止。

3 Android中的线程池

  线程池的三个好处:

  1)重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销

  2)能有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统资源而导致的阻塞现象。。

  3)能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。

  Android的线程池的概念来源于Java中的Executor。Executor是一个接口,真正线程池的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池。从线程池的功能特性来说,Android的线程池主要分为4类。

3.1 ThreadPoolExecutor

  ThreadPoolExecutor是线程池的真正实现,它的构造方法提供了一系列参数来配置线程池。下面是常用的一个构造方法。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
参数名 含义
corePoolSize 线程池的核心线程数。默认情况下,核心线程会一直在线程池中一直存活,即使处于闲置状态。除非将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置核心线程会在超时后终止。
maximumPoolSize 线程池所能容纳的最大线程数,当活动线程数达到这个数值后,后续新任务会被阻塞
keepAliveTime 非核心线程闲置时的超时时长,超过这个时间,非核心线程会被回收。
unit 用于指定keepAliveTime参数的时间单位,常用的有TimeUnit.MILLISECONDS、TimeUnit.SECONDS和TimeUnit.MINUTES
workQueue 线程池中的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个对象参数中
threadFactory 线程工厂,为线程池提供创建新线程的功能。threadFactory是一个接口,它只有一个方法:Thread newThread(Runnable r)
RejectedExecutionHandler 当线程池无法执行新的任务时,ThreadPoolExecutor会调用handler的rejectExecution方法

  ThreadPoolExecutor执行任务大致遵循如下规则:

  1)如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。

  2)如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。

  3)如果在步骤2中无法再插入任务,这个时候如果未达到线程池规定的最大值,那么就会立刻启动一个非核心线程来执行任务。

  4)如果在步骤3也无法再插入任务,那么就拒绝执行此任务,ThreadPoolExecutor会调用RejectedExecutionHandler的rejectedExecution方法来通知开发者。

3.2 线程池的分类

  Android常见的4类具有不同功能特性的线程池,它们都是直接或者间接地通过配置ThreadPoolExecutor来实现自己的功能特性。

3.2.1 FixedThreadPool

  通过Executors的newFixedThreadPool方法来创建。它是一种线程数量固定的线程池,当线程处于空闲状态时,它们不会被回收,除非线程池被关闭。由于FixedThreadPool只有核心线程并且这些核心线程不会被回收,这意味着它能够更加快速地响应外界的请求。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

3.2.2 CachedThreadPool

  通过Executors的newCachedThreadPool方法来创建。它是一种线程数量不定的线程池。它只有非核心线程,并且其最大线程数量为Integer.MAX_VALUE。空闲非核心线程的超时时间为60s。这类CachedThreadPool比较适合执行大量的耗时较少的任务。

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

3.2.3 ScheduledThreadPool

  通过Executors的ScheduledThreadPoolExecutor方法来创建。它的核心线程数量是固定的,而非核心线程数是没有限制的,并且非核心线程闲置时会被立即回收。这类ScheduledThreadPoolExecutor线程池主要用于执行定时任务和具有固定周期的重复任务。

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }

3.2.4 SingleThreadExecutor

  通过Executors的newSingleThreadExecutor方法来创建。这类newSingleThreadExecutor线程池内部只有一个核心线程,它确保所有任务都在同一个线程中按顺序执行,不需要处理线程同步问题。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

  举例了经典用法:

    Runnable runnable = new Runnable() {
        @Override
        public void run() {

        }
    };

        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
        fixedThreadPool.execute(runnable);

        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        cachedThreadPool.execute(runnable);

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
        scheduledExecutorService.schedule(runnable, 2, TimeUnit.SECONDS);
        scheduledExecutorService.scheduleAtFixedRate(runnable, 10, 2, TimeUnit.SECONDS);

        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(runnable);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章