原生机制剖析

Handler机制原理

为什么需要?

  • 解决安卓中的多线程并发问题。

    通常UI页面的操作都只能在主线程进行,安卓中将主线程称为UI线程。当我们需要创建子线程去处理耗时任务时,就需要协调UI线程和子线程之间的通信

  • 避免ANR

    ANR(Application Not Responding)即应用无响应。避免ANR最核心的一点就是在主线程减少耗时操作。

ANR出现的原因有三种:

  1. KeyDispatchTimeout(5 seconds)主要类型按键或触摸事件在特定时间内无响应
  2. BroadcastTimeout(10 seconds)BoradcastReceiver在特定的时间内无法处理
  3. ServiceTimeout(20 seconds)小概率类型Service在特定的时间内无法处理完成

Handler 机制

Handler机制涉及的三个核心类:LooperHandlerMessage。此外还有一个Message Queue(消息队列),但被封装到Looper里,我们并不会直接与消息队列打交道。

Looper

Looper的字面意思是“循环者”,被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在GUI开发中,通常都需要一个线程不断循环执行任务。有新任务时执行,执行完成则继续等待下一个任务。

public class LooperThread extends Thread {
    @Override
    public void run() {
        // 将当前线程初始化为Looper线程
        Looper.prepare();
        // ...其他处理

        // 开始循环处理消息队列
        Looper.loop();
    }
}

上面两行核心代码将普通线程升级为Looper线程。来看一下Looper内部源码:

public class Looper {
    // 线程本地存储对象
    private static final ThreadLocal sThreadLocal = new ThreadLocal();
    // Looper内的消息队列
    final MessageQueue mQueue;
    // 当前线程
    Thread mThread;
    // ....省略部分源码

    // 每个Looper对象中有它的消息队列,和它所属的线程
    private Looper() {
        mQueue = new MessageQueue();
        mRun = true;
        mThread = Thread.currentThread();
    }

    // 调用该方法会在调用线程的中创建Looper对象
    public static final void prepare() {
        if (sThreadLocal.get() != null) {
            // 试图在有Looper的线程中再次创建Looper将抛出异常
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper());
    }
    // ......
}

特别注意,一个Thread只能有一个Looper对象

Looper.loop()调用的原理

调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MessageQueue中取出队头的消息(也叫任务)执行

Looper除了prepare()loop()方法,还提供了一些有用的方法

  • Looper.myLooper():获取当前线程的Looper实例对象
  • getThread():获取Looper对象所属的线程
  • quit():结束looper循环

Handler

Handler扮演了往MessageQueue中添加消息和处理消息的角色(只处理由自己发出的消息),即通知MessageQueue它要执行一个任务(sendMessage),并在Looper循环到该任务时,回调自己来执行该任务(handleMessage),这整个过程都是异步的。Handler创建时会关联一个Looper,默认的构造方法将关联当前线程的Looper。

一个线程可以有多个Handler,但是只能有一个Looper:

Handler提供了如下一些方法向MessageQueue中发送消息

  • post(Runnable)
  • postAtTime(Runnable, long)
  • postDelayed(Runnable, long)
  • sendEmptyMessage(int)
  • sendMessage(Message)
  • sendMessageAtTime(Message, long)
  • sendMessageDelayed(Message, long)

Handler通过核心方法dispatchMessage)(Message msg)与handleMessage)(Message msg)完成消息的处理,任务的执行。相关源码剖析如下:

    // 处理消息,该方法由looper调用
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) { // 如果message设置了callback,,处理callback
            handleCallback(msg);
        } else {
            if (mCallback != null) { // 如果handler本身设置了callback,则执行callback
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 如果message没有callback,则调用handler的子类所实现的handleMessage方法
            handleMessage(msg);
        }
    }

    // 处理runnable消息
    private final void handleCallback(Message message) {
        message.callback.run();  //直接调用run方法
    }
    // 由子类实现该回调方法
    public void handleMessage(Message msg) {}

Handler发送消息图示:

Handler处理消息图示:

Message

在Handle消息处理机制中,Message又被称作Task,它封装了任务携带的信息和处理该任务的Handle。

使用Message时需要注意几点:

  1. 尽管Message有默认构造方法,但通常都使用Message.obtain()来从消息对象池中取一个空消息对象,避免每次创建新对象从而节省资源。

  2. 如果Message只用来携带简单的int信息,应优先使用Message.arg1Message.arg2来传递信息

  3. 使用Message.what来标识消息的类型,以便处理不同类型消息。

Handler 机制整体图示:

代码实例:

public class SampleTask implements Runnable {
    private static final String TAG = "SampleTask";
    Handler handler;

    public SampleTask(Handler handler) {
        super();
        this.handler = handler;
    }

    @Override
    public void run() {
        try {  
            Thread.sleep(5000);  // 模拟执行耗时任务
            // 任务完成后通知UI更新
            Message msg = handler.obtainMessage();
            msg.obj = "task completed!"
            // 发送消息
            handler.sendMessage(msg);
        } catch (InterruptedException e) {
            Log.d(TAG, "interrupted!");
        }
    }
}

在Activity中使用:

public class TestActivity extends Activity {
    private TextView textview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        textview = (TextView) findViewById(R.id.textview);
        // 创建并启动线程
        Thread workerThread = new Thread(new SampleTask(new MyHandler()));
        workerThread.start();
    }

    class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            String result = (String)msg.obj;
            // 更新UI
            textview.setText(result);
        }
    }
}

注意:

  • Handler机制,既可以实现主线程和子线程之间的通信,也可以实现子线程和子线程之间的通信,当我们需要将主线程的任务交给子线程执行时,还可以使用HandlerThread类来简化代码。HandlerThread继承于Thread,它本质就是个Thread。与普通Thread的差别就在于,其内部封装了Looper

  • 安卓的主线程(UI线程)就是一个HandlerThread,其内部已经包含了Looper

  • 由于UI线程已经包含了Looper,因此Activity中封装了一个简化方法runOnUiThread(),该方法可在子线程中调用,将一段代码post到UI线程中执行,从而简单的完成在子线程中更新UI。

    // 简单示例
    new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                // 此处更新UI
                                text.setText("")
                            }
                        });
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
    • 在Activity中使用Handler,要记得在onDestroy中清理消息队列,避免内存泄漏

线程池原理

可简单理解为装有线程的池子。我们可以把要执行的异步任务交给线程池来处理,通过维护一定数量的线程池来达到多个线程的复用。

如果不使用线程池,每个线程都要通过new Thread(xxRunnable).start()的方式来创建并运行一个线程,线程少或许不是问题,但真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数量大多时会耗尽系统的CPU和内存资源,也会造成GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的,如果为每个异步任务都创建线程这无疑是一个很大的性能瓶颈。所以,线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。

JDK中的java.util.concurrent包定义了线程池,其中ThreadPoolExecutor是线程池核心类:

public ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler
) {
    ....
}

参数的理解:

  • corePoolSize:线程池的核心大小,也可以理解为最小的线程池大小,即线程最大并发数
  • maximumPoolSize:最大线程池大小,即队列满后线程能够达到的最大并发数
  • keepAliveTime:空闲线程存活时间,即多久被回收。指的是超过corePoolSize的空闲线程达到多长时间才进行销毁
  • unitkeepAliveTime 的时间单位
  • workQueue:存储等待执行线程的任务的队列
  • handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常

Executors是JDK提供的创建线程池的工厂类,它默认提供了4种线程池:

  1. newSingleThreadExecutor 创建一个单线程的线程池,核心线程数和最大线程数均为1。它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO优先级)执行。
  2. newFixedThreadPool 创建一个固定线程池,核心线程数和最大线程数固定相等,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。
  4. newCachedThreadPoo 创建一个带缓冲线程池,核心线程数为0,最大线程数为Integer的最大值,一旦有空闲线程就会在60秒后销毁,如果没有可用线程则新建一个线程。 需谨慎使用,易造成内存溢出。

一个示例:

// 创建固定大小的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
    final int ii = i;
    fixedThreadPool.execute(new Runnable(){
        @Override
        public void run() {
            System.out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}

AsyncTask

它相当于安卓中的线程池,用于更简单的执行异步任务。

首先简单理解一下异步和同步概念

  • 同步:必须等前一件事做完才能做下一件事,当前线程是阻塞的。
  • 异步:和同步是相对的,当我们执行某个任务后,并不想要立即得到结果,我们可以继续做其他任务,当之前的任务完成后,可以发通知来告知我们结果。实际上生活中处处是异步的例子,例如后台下载,用户不可能一直等待下载完成才去做别的事,而是开启下载就不管了,去处理自己的其他事务。

AsyncTask内部线程池参数的配置:

AsyncTask的使用示例与详细信息

总结:

  1. AsyncTask实例必须在主线程中创建
  2. execute方法必须在主线程中调用
  3. 不要手动调用AsyncTask中的回调(如:onPreExecute等)
  4. Task只能被执行一次,需要多次执行,则每次都需要创建一个AsyncTask实例
  5. AsyncTask默认是串行执行,如需要并行执行,应调用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)替代execute方法
  6. AsyncTask会为当前应用创建一个全局线程池,因此在任何地方调用都会使用同一个线程池
  7. AsyncTask被声明为Activity的非静态内部类,会因AsyncTask持有对Activity的引用 而导致Activity无法被及时回收,引起内存泄露问题。因此AsyncTask应被声明为Activity的静态内部类。可参考本人博客中内存泄漏部分

公众号“编程之路从0到1”

20190301102949549

Copyright © Arcticfox 2021 all right reserved,powered by Gitbook文档修订于: 2022-05-01 12:20:20

results matching ""

    No results matching ""