Android 开发艺术探索笔记之十一 -- Android 的多线程和线程池

学习内容

  • 线程基本概念
  • 线程的不同形式
    • AsyncTask
    • HandlerThread
    • IntentService
  • 线程池基础

原文开篇部分

  • 主线程主要处理和界面相关的事情,而子线程则往往用于执行耗时操作。
  • 线程的多种形态:
    • AsyncTask:底层封装了线程池和 Handler,目的在于方便开发者在子线程中更新 UI
    • HandlerThread:底层直接使用了线程,它是一个具有消息循环的线程,其内部可以使用 Handler
    • IntentService:底层直接使用了线程,内部采用 HandlerThread 来执行任务,完成后即退出。它是类似后台线程的服务,不易被系统杀死从而保证后台任务的执行。而单纯后台线程的话,如果内部没有活动的四大组件,优先级低,容易被杀死。
  • 线程池:
    • 线程的创建和销毁有相应的开销,因此采用线程池,线程池中会缓存一定数量的线程,通过线程池可以避免因为频繁创建和销毁线程所带来的系统开销。

主线程和子线程

基本介绍

  1. 主线程指进程拥有的线程;子线程也叫工作线程,除了主线程以外的线程都是子线程。
  2. 主线程的作用是运行四大组件以及处理它们和用户的交互,而子线程的作用则是执行耗时任务
  3. Android 3.0 以后,网络访问必须在子线程中进行,否则网络访问会失败,并抛出 NetworkOnMainThreadException 异常,此举是为了避免主线程由于被耗时任务所阻塞而出现 ANR。

Android 中的线程形态

AsyncTask

1. 基本

  • 轻量级
  • 适合执行后台任务以及在主线程中访问 UI,但不适合执行特别好使的后台任务。(特别耗时的,建议线程池)

2.参数

  • AsyncTask 是一个抽象的泛型类,有三个参数,具体声明如下

    public abstract class AsyncTask<Params, Progress, Result>

  • Params:表示参数的类型

  • Progress:表示后台任务的执行进度的类型

  • Result:表示后台任务的返回结果的类型

3.核心方法

  1. onPreExecute():在主线程中执行。在异步任务之前调用此方法,做一些准备工作
  2. doInBackground(Params...params):
    1. 线程池中调用,用于执行异步任务,参数表明异步任务的输入参数。
    2. 内部可以通过 publishProgress 方法更新任务进度,而 publishProgress 方法中会调用 onProgressUpdate方法
    3. 另外该方法返回结果给 onPostExecute 方法。
  3. onProgressUpdate(Progress...progress):在主线程中执行,当后台任务的进度发生改变时此方法被调用。用于更新界面中的进度。
  4. onPostExecute(Result result):在主线程中执行,异步任务执行之后,此方法被调用,result 是后台任务的返回值。用于在任务完成后给出提示。
  5. 补充
    1. 执行顺序:onPreExecute --> doInBackground --> onPostExecute。
    2. onCanceled():在 主线程 中执行,异步任务取消时,该方法被调用,此时 onPoseExecute 方法不会被调用。

4.限制

  1. AsyncTask 的类必须在 主线程 中加载,即第一次访问 AsyncTask 必须发生在主线程,Android 4.1 及以上版本已自动完成。
  2. AsyncTask 的对象必须在 主线程 中创建
  3. execute 方法必须在 UI 线程 调用
  4. 不要在程序中直接调用 onPreExecute、doInBackground、onProgressUpdate、onPostExecute 方法
  5. 一个 AsyncTask 对象只能 执行一次,即只能调用一次 execute 方法,否则报异常
  6. Android 1.6 以前,串行执行任务;Android 1.6 时采用线程池处理并行任务;Andorid 3.0 开始,采用单个线程来串行执行任务,但是可以通过 AsyncTask 的 executeOnExecutor 方法来并行执行任务。

AsyncTask 的工作原理

1.分析

  • execute(Params... params) 入手分析,它会调用 **executeOnExecutor **方法:

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }
    
    @MainThread
        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(&quot;Cannot execute task:&quot;
                                + " 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;
        }
    

    在 executeOnExecutor 方法中,首先 onPreExecute 方法被调用,之后线程池开始执行 exec.execute(mFuture),这里实际上调用的是 SerialExecutor.execute(final Runnable r) 方法:

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

    此处可以分析 AsyncTask 的排队执行的过程。首先系统把 AsyncTask 的 Params 参数封装成 FutureTask 对象,FutureTask 是一个并发类,它充当 Runnable 的作用。接着这个 FutureTask 会交给 SerialExecutor 的 execute 方法处理。

    execute 方法首先会把该 FutureTask 对象插入到任务队列 mTasks,如果此时没有正在活动的 AsyncTask 任务,就会执行下一个 AsyncTask 任务;同时当一个 AsyncTask 任务执行完后,AsyncTask 会继续执行其他任务直到所有任务都执行完为止。(串行执行

    AsyncTask 有两个线程池(THREAD_POOL_EXECUTOR 和 SerialExecutor )和一个 Handler(InternalHandler)。

    • SerialExecutor 用于任务的排队
    • THREAD_POOL_EXECUTOR 用于真正的执行任务
    • InternalHandler 用于将执行环境从线程池切换到主线程

    在上面的分析中,我们看到 SerialExecutor 中调用了 FutrueTask 的 run 方法,而其 run 方法中会调用 mWorker 的 call 方法,call 方法定义在 AsyncTask 的构造函数中

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

    能看到,call 方法中首先将 mTaskInvoked 设为 true,表明当前任务已被调用,然后执行 AsyncTask 的 doInBackground 方法,接着讲返回值传递给 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 方法会通过 getHandler() 方法得到一个 Handler 对象,然后通过该 Handler 发送一个消息;实际上 getHandler 返回了一个 mHandler。

    private Handler getHandler() {
        return mHandler;
    }
    

    而AsyncTask 的构造方法中,对 mHandler 做出如下设置:

    mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
                ? getMainHandler()
                : new Handler(callbackLooper);
    
    
    private static Handler getMainHandler() {
            synchronized (AsyncTask.class) {
                if (sHandler == null) {
                    sHandler = new InternalHandler(Looper.getMainLooper());
                }
                return sHandler;
            }
        }
    

    因此实际最后得到的 Handler 对象即为 sHandler,变相要求了 AsyncTask 的类必须在主线程中加载。这个 sHandler 的定义如下:

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

    可以看到,sHandler 收到 MESSAGE_POST_RESULT 消息后会调用 AsyncTask 的 finish 方法。

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

    如果被取消执行,那么调用 onCancelled 方法,否则调用 onPostExecute 方法,此时 doInBackground 的返回值 result 就传递给了 onPostExecute 方法。

    到此为止,AsyncTask 的整个工作流程就分析完毕了。

2.流程顺序

AsyncTask.execute(Params... params) -> AsyncTask.executeOnExecutor(Executor exec,Params... params) (此方法中调用 onPreExecute)-> SerialExecutor.execute(final Runnable r) -> FutureTask r.run() -> WorkerRunnable<Params,Result> mWorker.call()(此方法中调用 doInBackground) -> AsyncTask.postResult(Result result) -> sHandler 发送消息 -> InternalHandler.handleMessage -> AsyncTask.finish(此方法中调用 onCancelled 或者 onPostExecute )

HandlerThread

介绍

  1. HandlerThread 继承了 Thread,是一种可以使用 Handler 的 Thread。
  2. 实现简单:在 run 方法中通过 Looper.prepare() 来创建消息队列,并通过 Looper.loop() 开启消息循环,这样就可以创建使用 Handler 了。
  3. HandlerThread vs. 普通的 Thread:
    1. 普通 Thread 主要用于在 run 方法中执行一个耗时任务;
    2. HandlerThread 在内部创建了消息队列,外界需要通过 Handler 的消息方式来通知 HandlerThread 执行一个具体的任务
  4. 使用场景之一:IntentService
  5. 建议:和 Looper 类似,当明确不再需要 HandlerThread 时,通过它的 quit 或者 quitSafely 方法来终止线程的执行。

IntentService

1.介绍

  1. IntentService 是一种特殊的 Service,继承了 Service 并且是一个 抽象类
  2. 用于执行后台耗时的任务,任务执行后它会自动停止。同时因为其本质是 Service,导致优先级比普通的线程高,不容易被杀死,所以适合执行一些 高优先级的后台任务

2.原理

  1. IntentService 封装了 HandlerThread 和 Handler,从源码中可以清楚的看到:

    public abstract class IntentService extends Service {
        //...
        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);
            }
        }
        
        @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 时,都会调用 onStartConmmand 方法,而其又会调用 onStart 方法,源码如下:

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

    可以看出,它只是通过 mServiceHandler 发送了一条消息,这条消息包含了 Intent 对象,这个消息会在 HandlerThread 中处理。当 mServiceHandler 收到消息后,会将 Intent 对象传递给 onHandleIntent 方法,而 onHandleIntent 方法是一个抽象方法,通过子类重写该方法从而实现通过 Intent 对象解析外界启动 IntentService 时传入的参数,并针对不同参数区分具体的后台任务。

    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
    

    当 onHandlerIntent 结束后,就会调用 stopSelf(int startId) 方法停止服务,该方法会等待所有的消息都处理完毕后才会终止服务。(stopSelf()方法会立即终止)

    作为补充,IntentService 是顺序执行后台任务的,原因在于 IntentService 内部是通过消息的方式向 HandlerThread 请求执行任务,而 Handler 的 Looper 是顺序处理的,因此 IntentService 也是顺序执行的。


Android 中的线程池

线程池的优点

  1. 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销
  2. 有效控制线程池的最大并发数,避免大量的线程之间因互相抢占系统自ui按导致的阻塞现象
  3. 能够对线程进行简单的管理,并提供定时指定以及指定间隔循环执行等功能。

1.ThreadPoolExecutor

1.基本介绍

  1. 该类是线程池的真正实现,Android 的线程池都是直接或者间接通过配置 ThreadPoolExecutor 来实现的。

  2. 一个比较常用的构造方法:

    public ThreadPoolExector(int corePoolSize,
                            int maximunPoolSize,
                             long keepAliveTime,
                             TimeUnit unit,
                             BlockingQueue<Runnable> workQueue,
                             ThreadFactory threadFactory)
    

    参数说明:

    • corePoolSize:线程池的核心线程数,默认情况下,核心线程会一直存活。如果设置 allowCoreThreadTimeOut 为 true,且等待时间超过 keepAliveTime 所制定的时长后,核心线程会被终止。
    • maximunPoolSize:线程池能容纳的最大线程数,当活动的线程达到这个数值之后,后续任务会被阻塞。
    • keepAliveTime:非核心线程闲置时的超时时长,超过这个时长,非核心线程就会被回收。当设置 allowCoreThreadTimeOut = true 时,同样会作用于核心线程。
    • unit:指定 keepAliveTime 参数的时间单位,这是一个枚举。
    • workQueue:线程池中的任务队列,通过线程池的 execute 方法提交的 Runnable 对象会存储在这个参数中。
    • threadFactory:线程工厂,为线程池提供创建新线程的功能功能。它是个接口,只有一个方法:Thread newThread(Runnable r)

    不常用的参数

    • RejectedExecutionHandler handler:当线程池无法执行新任务时,可能是由于任务队列已满或者时无法成功执行任务,此时 ThreadPoolExecutor 会调用 handler 的 rejectdExecution 方法来通知调用者。

2.执行任务的规则

  1. 线程池中的线程数量未达到核心线程的数量:直接启动一个核心线程来执行任务。
  2. 如果线程池中的线程数量已经大导或者超过核心线程的数量,那么任务会被插入到任务队列等待执行
  3. 如果 2 中无法将任务插入到任务队列中,这往往是由于任务队列已满,此时如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
  4. 如果 3 中线程数量已经达到线程池规定的最大值,那么据拒绝执行此任务,此时上述不常用的参数 handler 发挥作用。

3.AsyncTask 线程池的配置

(针对 THREAD_POOL_EXECUTOR 线程池)

  1. 核心线程数等于 CPU 核心数 + 1
  2. 线程池的最大线程数为 CPU 核心数的 2 倍 + 1
  3. 核心线程无超时机制,非核心线程在闲置时的超时时间为 1 秒
  4. 任务队列的容量为 128

2.线程池的分类

常见的 4 个线程池

  1. FixedThreadPool
    1. 线程池固定的线程池,当线程处于空闲状态时,它们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新任务都会处于等待状态,直到有线程空闲出来。
    2. 只有核心线程并且不会被回收,能够更加快速的响应外界的请求
  2. CachedThreadPool
    1. 线程数量不定的线程池,只有非核心线程,最大线程数为 Integer.MAX_VALUE。
    2. 当线程池中的线程都处于活动状态时,线程池会创建新的线程来处理新任务,否则利用空闲的线程来处理新任务。线程池中的空闲线程具有超时机制,为 60s。
    3. 任务队列相当于一个空集合,导致任何任务都会立即被执行,适合执行大量耗时较少的任务。当整个线程池都处于限制状态时,线程池中的县城都会超时而被停止。
  3. ScheduledThreadPool
    1. 核心线程数量固定,非核心线程数没有限制,并且非核心线程闲置的时候立即回收。
    2. 主要用于执行定时任务和具有固定周期的重复任务
  4. SingleThreadExecutor
    1. 只有一个核心线程,保证所有的任务都在一个线程中顺序执行。
    2. 意义在于不需要处理线程同步的问题。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容