美文网首页
Android Handler分析 (三) Looper详解和H

Android Handler分析 (三) Looper详解和H

作者: ITRenj | 来源:发表于2021-03-07 00:13 被阅读0次

    在前面的博客中我们介绍了Handler相关的几个类(Handler、Message和MessageQueue),在这一篇博客中,我们介绍Handler机制中最后一个重要的类,Looper类。并介绍2个小知识点。

    再说Looper之前,我们先来看一个相关的类:ThreadLocal类

    ThreadLocal类

    ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。比如对于Handler来说,它需要获取当前线程的Looper,很显然Looper的作用域就是线程并且不同线程具有不同的Looper,这个时候通过ThreadLocal就可以轻松实现Looper在线程中的存取。

    ThreadLocal类的几个特点:

    1. 每个线程都有自己的局部变量
      每个线程都有一个独立于其他线程的上下文来保存这个变量,一个线程的本地变量对其他线程是不可见的
    2. 独立于变量的初始化副本
      ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值的一个副本,这样才能保证不同的线程都有一份相同的拷贝
    3. 状态与某一个线程相关联
      ThreadLocal 是为了方便每个线程处理自己的状态而引入的一个机制

    另外,ThreadLocal有一个内部类ThreadLocalMap,这个类就是真正保存线程自己本地变量的容器。每一个线程都有自己的单独的一个ThreadLocalMap实例,其所有的本地变量都会保存到这一个map中。

    简单示例:

    // 创建一个ThreadLocal对象
    private static ThreadLocal<Integer> integerThreadLocal = new InheritableThreadLocal<Integer>(){
        // 重写方法,指定一个初始值
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    
    // 测试不同多个线程同时访问
    // 定义5个线程
    Thread[] threads = new Thread[5];
    for (int i = 0; i < 5; i++) {
        // 创建多个线程
        threads[i] = new Thread(){
            @Override
            public void run() {
                // 获取当前线程的本地变量,然后累加
                Integer integer = integerThreadLocal.get();
                for (int i1 = 0; i1 < 5; i1++) {
                    integer += 1;
                }
                // 重新设置累加后的本地变量
                integerThreadLocal.set(integer);
                // 重新获取值并打印出来
                Log.i("MainActivity", Thread.currentThread().getName() + " - integer value : " + integerThreadLocal.get());
            }
        };
    }
    // 开启线程
    for (int i = 0; i < threads.length; i++) {
        threads[i].start();
    }
    

    运行结果如图:

    ThreadLocal类测试运行结果.png

    我们可以看到5个线程最终打印的值都相同,也就说明了每一个线程第一次获取的值都是0,也就是定义的初始值,每一个线程拿到了初始值的副本,然后操作的也是各自拿到的副本,操作结果不会对初始值有影响。

    ThreadLocal的几个方法说明:

    // get()方法
    public T get() {
            // 获取当前执行线程
            Thread t = Thread.currentThread();
            // 取得当前线程的ThreadLocalMap实例
            ThreadLocalMap map = getMap(t);
            // 如果map不为空,说明该线程已经有了一个ThreadLocalMap实例
            if (map != null) {
                // map中保存线程的所有的线程本地变量,我们要去查找当前线程本地变量
                ThreadLocalMap.Entry e = map.getEntry(this);
                // 如果当前线程本地变量存在这个map中,则返回其对应的值
                if (e != null)
                    return (T)e.value;
            }
            // 如果map不存在或者map中不存在当前线程本地变量,调用setInitialValue()方法返回初始值
            return setInitialValue();
        }
    
    private T setInitialValue() {
        // 我们创建ThreadLocal时重写的方法,默认返回null
        T value = initialValue();
        // 获取当前线程,然后取得当前线程的ThreadLocalMap实例
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        // 不为null,直接设置初始值
        if (map != null)
            map.set(this, value);
        // 为null,为当前线程创建一个ThreadLocalMap实例,并设置初始值
        else
            createMap(t, value);
        return value;
    }
    
    // 创建一个ThreadLocalMap实例并赋值
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    // set()方法
    public void set(T value) {
        // 大部分和setInitialValue()方法一样,
        // 先获取ThreadLocalMap实例,获取到了设置值,没有获取到就创建一个并设置指定的值
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    // 获取当前线程的ThreadLocalMap实例
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    // 移除当前线程的ThreadLocalMap实例
    public void remove() {
        // 先获取到,不为null就移除
        ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null)
            m.remove(this);
    }
    

    了解完了ThreadLocal,接下来我们就来看一下Looper类。

    Looper 类

    1. 部分成员变量

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); // ThreadLocal对象
    private static Looper sMainLooper;  // 主线程的Looper对象
    final MessageQueue mQueue; // 与Looper绑定的MessageQueue对象
    final Thread mThread; // 当前Looper所在的线程
    

    2. 构造方法

    // 构造方法用private修饰,表示不能其他类不能创建,需要一个boolean类型的参数
    private Looper(boolean quitAllowed) {
        // 在构造方法中与MessageQueue绑定
        // 将boolean类型的参数quitAllowed传给MessageQueue的构造
        // 我们在MessageQueue中说过这个参数的作用,true表示队列不能退出,false表示能退出
        mQueue = new MessageQueue(quitAllowed);
        // 初始化mThread变量为当前线程
        mThread = Thread.currentThread();
    }
    

    3. 初始化方法 prepare()

    // 准备方法,调用带参数的prepare()方法
    public static void prepare() {
        // 参数为true,表示队列可以退出
        prepare(true);
    }
    // 带一个参数 quitAllowed 的prepare()方法
    private static void prepare(boolean quitAllowed) {
        // 先从sThreadLocal获取当前线程的Looper对象,如果获取到了,表示当前线程已经有一个Looper了
        // 抛出一个异常,表示在一个线程当中只能创建一个Looper对象
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 当前线程没有创建过Looper,那么就创建一个Looper并指定与Looper绑定的MessageQueue可以退出
        sThreadLocal.set(new Looper(quitAllowed));
    }
    // 这个方法是专门为主线程创建Looper的
    public static void prepareMainLooper() {
        // 同样调用带参数的prepare()方法创建Looper,但是参数为false,
        // 表示与Looper绑定的MessageQueue可以退出,也就是主线程的MessageQueue不能退出
        prepare(false);
        // 进入同步代码块
        synchronized (Looper.class) {
            // 判断成员变量 sMainLooper 是否为null,如果不为null,表示主线程已经创建过了,抛出异常
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            // sMainLooper 为null,调用myLooper()方法给 sMainLooper赋值
            sMainLooper = myLooper();
        }
    }
    

    4. 获取方法

    // 从sThreadLocal中取出当前线程的Looper对象并返回
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    // 获取与当前线程绑定的MessageQueue对象
    public static @NonNull MessageQueue myQueue() {
        return myLooper().mQueue;
    }
    // 获取当前Looper所在的线程
    public @NonNull Thread getThread() {
        return mThread;
    }
    

    5. loop()方法,Looper类中最重要的一个方法

    public static void loop() {
        // 获取与当前线程绑定的Looper对象
        final Looper me = myLooper();
        // 为null,表示当前线程没有Looper对象
        // 还不是Looper线程,抛出异常,没有调用Looper.prepare()方法
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 是Looper线程,初始化queue指向当前线程的MessageQueue对象
        final MessageQueue queue = me.mQueue;
        // 确保这个线程是运行在本地进程
        Binder.clearCallingIdentity();
        // 保存一个用于跟踪的身份令牌
        final long ident = Binder.clearCallingIdentity();
    
        // 进入无限循环
        for (;;) {
            // 调用MessageQueue的next()方法从消息队列中去消息
            // 有可能会阻塞,next()方法在上一篇博客中有详细说明
            Message msg = queue.next(); // might block
            if (msg == null) {
                // 没有消息表示消息队列是退出状态,直接返回
                return;
            }
    
            // 如果调用了setMessageLogging(@Nullable Printer printer)方法
            // 那么就调用Printer接口中的方法打印日志信息
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
    
            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                // 找到Message了,调用Handler中的dispatchMessage(msg)方法,分发和处理消息
                // msg.target表示Message的目标Handler,前面的博客强调过这个变量
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
    
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
    
            // 获取一个新的身份令牌,和原来的身份令牌进行比较
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                // 如果两个身份令牌不同,打印一个错误级别很高的日志(What The Fuck)
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }
            // 释放/回收消息
            msg.recycleUnchecked();
        }
    }
    

    《Android Handler分析(一) Handler和Message详解》这篇博客中,我们最后说到的了Handler中的dispatchMessage(msg)方法,当时只是说让大家记住,然后说了是在Looper中调用的,在这里就能看到了具体的调用位置及时间。

    6. quit()退出方法

    // 调用MessageQueue中的方法,在上一篇博客中有说明
    public void quit() {
        // 参数为false,表示非安全退出
        mQueue.quit(false);
    }
    // 安全退出MessageQueue,参数为true
    public void quitSafely() {
        mQueue.quit(true);
    }
    

    扩展相关知识

    1. 主线程的Looper是在哪里初始化的

    我们打开ActivityThread.java类,找到main()方法

    public static void main(String[] args) {
        ...
        // 调用Looper中的专门为主线程创建Looper对象的方法
        Looper.prepareMainLooper();
        
        ...
    
        // 开始轮询,取消息
        Looper.loop();
    }
    

    2. 在子线程创建一个Handler并开启轮询

    new Thread(new Runnable() {
        public void run() {
            Looper.prepare(); // 此处获取到当前线程的Looper,并且prepare()
            Handler handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();
                }
            };
            handler.sendEmptyMessage(1);
            Looper.loop(); // 开始轮询
        }
    }).start();
    

    以上代码中注意:如果需要在子线程创建Handler,子线程必须是一个Looper线程, Looper.prepare();Looper.loop(); 都必须调用,否则会报错, handleMessage() 方法运行在子线程。

    如果子线程不是Looper线程,但是还是想在子线程中创建Handler对象,那么就是用如下方式创建:

    new Thread(new Runnable() {
        public void run() {
            //Looper.prepare(); // 此处获取到当前线程的Looper,并且prepare()
            // 在子线程创建Handler,但是绑定到主线程的Looper中
            Handler handler = new Handler(Looper.getMainLooper()) {
                @Override
                public void handleMessage(Message msg) {
                    Toast.makeText(getApplicationContext(), "handler msg", Toast.LENGTH_LONG).show();
                }
            };
            handler.sendEmptyMessage(1);
            //Looper.loop(); // 开始轮询
        }
    }).start();
    

    通过以上方式创建时,虽然Handler还是在子线程创建,但是handleMessage() 方法运行在主线程。

    Android Handler消息机制写到这里基本上写完了,最后在对Handler机制做一个简单的总结:

    1. 我们创建Handler对象并重写handlerMessage(Message msg)方法,然后在线程中使用Handler对象发送一个消息,在Handler类中实际上调用了MessageQueue中的方法将消息根据时间进行排序,然后唤醒Looper,让轮询器不断的获取消息队列中的消息(调用MessageQueue中的next()方法),取到消息之后就通过Message的目标Handler(也就是Message中的target变量)调用Handler的handlerMessage(Message msg)方法根据Message的what不同来分别处理消息;
    2. 如果在创建Handler时指定了一个callback,并且回调中重写的handlerMessage(Message msg)方法返回了true,那么,就不会再调用Handler中的handlerMessage(Message msg)方法了;
    3. 一个线程只能创建一个Looper对象,如果创建多个就会报错;
    4. 主线程的Looper是系统创建的,并且MessageQueue不能退出;
    5. 在子线程中我们需要使用Handler,那就要调用Looper.prepar()方法初始化Looper,要想执行Handler的handlerMessage(Message msg)方法,那还要调用Looper.loop()方法开启轮询,我们也可以在子线程中创建Handler并绑定到主线程的Looper上;
    6. MessageQueue对象不能创建,它是在初始化Looper时就自动创建的,并和Looper绑定到一起。

    相关文章

      网友评论

          本文标题:Android Handler分析 (三) Looper详解和H

          本文链接:https://www.haomeiwen.com/subject/ptteqltx.html