专栏首页字节流动Android 多线程实现方式

Android 多线程实现方式

最近打算关掉博客,专注公众号,发现瘫痪的博客上面有一些 3 年前写的技术文章,看了一下还是不舍得扔掉,暂且迁移到这里。微信上贴代码的技术文章一般不受人待见,注意前方高能预警。

0. Android 多线程实现方式

通常来说,一个应用至少有一个进程,而一个进程至少有一个线程。 线程是 CPU 调度的基本单位,进程是系统资源分配的基本单位。

进程独享内存资源,一个进程可以看作一个 JVM ,一个进程崩溃后,在保护模式下一般不会对其它进程产生影响。 同一个进程中的线程共享内存资源,一个线程死掉就导致整个进程死掉。

Android 提供了四种常用的多线程实现方式:

  • AsyncTask
  • 异步消息机制
  • IntentService
  • ThreadPoolExcutor

1. AsyncTask

Android AsyncTask 类,它是封装好的线程池,操作 UI 线程极其方便。 AsyncTask 的三个泛型参数:

public abstract class AsyncTask<Params, Progress, Result>

- Params ,传入参数类型,即 doInBackground() 方法中的参数类型; - Progress,异步任务执行过程中返回的任务执行进度类型

即 publishProgress() 和onProgressUpdate() 方法中传入的参数类型; - Result,异步任务执行完返回的结果类型\,即 doInBackground() 方法中返回值的类型。 四个回调方法: - onPreExecute(),在主线程执行,做一些准备工作。 - doInBackground(),在线程池中执行,该方法是抽象方法,在此方法中可以调用 publishProgress() 更新任务进度。 - onProgressUpdate(),在主线程中执行,在 publishProgress() 调用之后被回调,展示任务进度。 - onPostExecute(),在主线程中执行,异步任务结束后,回调此方法,处理返回结果。

注意

  • 当AsyncTask 任务被取消时,回调 onCanceled(obj) ,此时 onPostExecute(),不会被调用,AsyncTask 中的 cancel() 方法并不是真正去取消任务,只是设置这个任务为取消状态,需要在 doInBackground() 中通过 isCancelled() 判断终止任务。
  • AsyncTask 必须在主线程中创建实例,execute() 方法也必须在主线程中调用。
  • 每个 AsyncTask 实例只能执行一次 execute() ,多次执行会报错,如需执行多次,则需创建多个实例。
  • Android 3.0 之后, AsyncTask 对象默认执行多任务是串行执行,即 mAsyncTask.execute() ,并发执行的话需要使用 executeOnExecutor()。
  • AsyncTask 用的是线程池机制和异步消息机制(基于 ThreadPoolExecutor 和 Handler )。Android 2.3 以前,AsyncTask 线程池容量是 128 ,全局线程池只有 5 个工作线程,如果运用 AsyncTask 对象来执行多个并发异步任务,那么同一时间最多只能有 5 个线程同时运行,其他线程将被阻塞。
  • Android 3.0 之后 Google 又进行了调整,新增接口 executeOnExecutor() ,允许自定义线程池(那么核心线程数以及线程容量也可自定义),并提供了 SERIAL_EXECUTOR 和 THREAD_POOL_EXECUTOR 预定义线程池。后来 Google 又做了一些调整(任何事物都不完美),将线程池的容量与 CPU 的核心数联系起来,如目前 SDK 25 版本中,预定义的核心线程数量最少有 2 个,最多 4 个,线程池容量范围 5 ~ 9 。

改动如下:

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;

2. 异步消息机制

异步消息机制的三大主角: Handler ,MessageLooper 。 Looper 负责创建 MessageQueue 消息对列,然后进入一个无限 for 循环中,不断地从消息队列中取消息,如果消息队列为空,当前线程阻塞,Handler 负责向消息队列中发送消息。

Looper

Looper 有两个重要的方法: prepare() 和 loop()。 prepare() , Looper 与当前线程绑定,一个线程只能有一个 Looper 实例和一个 MessageQueue 实例。

public static final void prepare() {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(true)); 保证 Looper 对象在当前线程唯一
}

// Looper 的构造方法
private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mRun = true;
        mThread = Thread.currentThread();
}

loop ,进入一个无限 for 循环体中,不断地从消息队列中取消息,然后交给消息的 target 属性的 dispatchMessage 方法去处理。

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();


        // 无限循环体,有没有想过在 UI 线程里,有这样一个死循环,为什么界面没卡死??
        for (;;) { 
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            msg.target.dispatchMessage(msg);

            msg.recycle();
        }
}

Handler

Handler 负责向消息队列中发送消息。 在 Activity 中我们直接可以 new Handler ,那是因为在 Activity 的启动代码中,已经在当前 UI 线程中调用了 Looper.prepare() 和 Looper.loop() 方法。 在子线程中 new Handler 必须要在当前线程(子线程)中创建好 Looper 对象和消息队列,代码如下

    //在子线程中

    Looper.prepare();

    handler = new Handler() {

        public void handleMessage(Message msg) {
            //处理消息
        };
    };

    Looper.loop();   

之后,你拿着这个 Handler 对象就可以在其他线程中,往这个子线程的消息队列中发消息了。

HandlerThread

HandlerThread 可以看作在子线程中创建一个异步消息处理机制的简化版,HandlerThread 对象自动帮我们在工作线程里创建 Looper 对象和消息队列。

使用方法:

mHandlerThread = new HandlerThread("MyHandlerThread");
mHandlerThread.start();

mHandler = new Handler(mHandlerThread.getLooper()){

    @Override
    public void handleMessage(Message msg) {
        //处理消息
    }
};

之后你就可以使用 Handler 对象往工作线程中的消息队列中发消息了。

看一下源码片段:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    protected void onLooperPrepared() {
    }

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

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
}

注意:handler 在 UI 线程中初始化的,looper 在一个子线程中执行,我们必须等 mLooper 创建完成之后,才能调用 getLooper ,源码中是通过 wait 和 notify 解决两个线程的同步问题。

3. IntentService

IntentService 可以看成是 Service 和 HandlerThread 的合体。它继承自 Service ,并可以处理异步请求,其内部有一个 WorkerThread 来处理异步任务,当任务执行完毕后,IntentService 自动停止。

如果多次启动 IntentService 呢? 看到 HandlerThread ,你就应该想到多次启动 IntentService ,就是将多个异步任务放到任务队列里面,然后在 onHandlerIntent 回调方法中串行执行,执行完毕后自动结束。

下面对源码进行简单的解析,IntentService 源码:

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }
        @Override
        public void handleMessage(Message msg) {
            //onHandleIntent 方法在工作线程中执行,执行完调用 stopSelf() 结束服务。
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
    /**
     * Creates an IntentService.  Invoked by your subclass's constructor.
     *
     * @param name Used to name the worker thread, important only for debugging.
     */
    public IntentService(String name) {
        super();
        mName = name;
    }

    /**
     * enabled == true 时,如果任务没有执行完,当前进程就死掉了,那么系统就会令当前进程重启。
     * 任务会被重新执行。
     */
    public void setIntentRedelivery(boolean enabled) {
        mRedelivery = enabled;
    }
    @Override
    public void onCreate() {
        super.onCreate();

        // 上面已经讲过,HandlerThread 对象 start 之后,会在工作线程里创建消息队列 和 Looper 对象。
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        // 获得 Looper 对象初始化 Handler 对象。
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }
    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        // IntentService 每次启动都会往工作线程消息队列中添加消息,不会创建新的线程。
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

    // 官方建议 IntentService onStartCommand 方法不应该被重写,注意该方法会调用 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 onDestroy() {  
        //服务停止会清除消息队列中的消息,除了当前执行的任务外,后续的任务不会被执行。
        mServiceLooper.quit();
    }
    /**
     * 不建议通过 bind 启动 IntentService ,如果通过 bind 启动 IntentService ,那么 onHandlerIntent 方法不会被回调。Activity 与 IntentService 之间的通信一般采用广播的方式。
     */
    @Override
    @Nullable
    public IBinder onBind(Intent intent) {
        return null;
    }
    /**
     * 子类必须要实现,执行具体的异步任务逻辑,由 IntentService 自动回调。
     */
    @WorkerThread
    protected abstract void onHandleIntent(@Nullable Intent intent);
}

IntentService 源码很容易理解,你也可以就自己的应用场景封装自己的 IntentService 。

几种场景:

  • 正常情况下,启动 IntentService ,任务完成,服务停止;
  • 异步任务完成前,停止 IntentService ,服务停止,但任务还会执行完成,完成后,工作线程结束;
  • 多次启动 IntentService ,任务会被一次串行执行,执行结束后,服务停止;
  • 多次启动 IntentService ,在所有任务执行结束之前,停止 IntentService ,服务停止,除了当前执行的任务外,后续的任务不会被执行;

4. ThreadPoolExcutor

图片来自 Jakob Jenkov 博客

ThreadPool

用来管理一组工作线程,任务队列( BlockingQueue )中持有的任务等待着被线程池中的空闲线程执行。

常用构造方法:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue
);
  • corePoolSize 核心线程池容量,即线程池中所维持线程的最低数量。corePoolSize 初始值为 0 ,当有新任务加入到任务队列中,新的线程将被创建,这个时候即使线程池中存在空闲线程,只要当前线程数小于 corePoolSize ,那么新的线程依然被创建。
  • maximumPoolSize 线程池中所维持线程的最大数量。
  • keepAliveTime 空闲线程在没有新任务到来时的存活时间。
  • unit 参数 keepAliveTime 的时间单位。
  • workQueue 任务队列,必须是 BlockingQueue 。

简单使用

创建 ThreadFactory ,当然也可以自定义。

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        public Thread newThread(Runnable r) {
            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };

创建 ThreadPoolExecutor 。

// 根据 CPU 核心数确定线程池容量。
public static final int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); 

mThreadPoolExecutor = new ThreadPoolExecutor(
        NUMBER_OF_CORES * 2, 
        NUMBER_OF_CORES * 2 + 1,
        60L,
        TimeUnit.SECONDS,
        new LinkedBlockingQueue<Runnable>(),
        backgroundPriorityThreadFactory
);

执行。

mThreadPoolExecutor.execute(new Runnable() { 

    @Override  
    public void run() {  
         //do something  
    } 

});

Future future = mThreadPoolExecutor.submit(new Runnable() { 

    @Override  
    public void run() {  
         //do something  
    } 

});

//任务可取消
future.cancel(true);

Future<Integer> futureInt = mThreadPoolExecutor.submit(new Callable<Integer>() {
    @override
    public Integer call() throws Exception {
        return 0;
    }

});

//获取执行结果
futureInt.get();

FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>(){
    @override
    public Integer call() throws Exception {
        return 0;
    }    

});

mThreadPoolExecutor.submit(task);
task.get();

本文分享自微信公众号 - 字节流动(google_developer),作者:字节流动

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-05-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 关于多线程,你必须知道的那些玩意儿

    进程和线程作为必知必会的知识,想来读者们也都是耳熟能详了,但真的是这样嘛?今天我们就来重新捋一捋,看看有没有什么知识点欠缺的。

    字节流动
  • 你真的了解 Java volatile 关键字吗?

    happens-before 规则中有一条是 volatile 变量规则:对一个 volatile 域的写,happens-before 于任意后续对这个 vo...

    字节流动
  • OpenGL ES 多目标渲染(MRT)

    OpenGL ES 多目标渲染(MRT),即多重渲染目标,是 OpenGL ES 3.0 新特性,它允许应用程序一次渲染到多个缓冲区。

    字节流动
  • Java并发编程笔记——J.U.C之executors框架:executors框架设计理念

    juc-executors框架是整个J.U.C包中类/接口关系最复杂的框架,真正理解executors框架的前提是理清楚各个模块之间的关系,高屋建瓴,从整体到局...

    须臾之余
  • JAVA中volatile、synchronized和lock解析

    在研究并发程序时,我们需要了解java中关键字volatile和synchronized关键字的使用以及lock类的用法。

    哲洛不闹
  • 史上最难的一道Java面试题:分析篇

    无意中了解到如下题目,觉得蛮好。 题目如下: ? 该程序的输出结果? 在java中,多线程的程序最难理解、调试,很多时候执行结果并不像我们想象的那样执行。所以在...

    CSDN技术头条
  • Spring Boot使用@Async实现异步调用:自定义线程池

    在之前的Spring Boot基础教程系列中,已经通过《Spring Boot中使用@Async实现异步调用》一文介绍过如何使用 @Async注解来实现异步调用...

    程序猿DD
  • 传统多线程开发Android开发高级进阶

    之前的文章里写过了AsyncTask的一些坑,这次就不讲它了,使用传统的 Handler和Message来进行线程的使用,并且第一次添加了CallBack方式的...

    爱因斯坦福
  • 线程优化

    Process中定义,值越小,优先级越高,默认是THREAD_PRIORITY_DEFAULT 0

    Yif
  • Java线程池使用说明

    现在是资源共享的时代,同样也是知识分享的时代,如果你觉得本文能学到知识,请把知识与别人分享。

    互扯程序

扫码关注云+社区

领取腾讯云代金券