Android Handler分析 (三) Looper详解和Handler其他知识

在前面的博客中我们介绍了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绑定到一起。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容