Handler学习笔记

目录

学习目录

1. Handler的作用

  • 简单说Handler用于同一个进程的线程之间通信。(可以是不同线程,也可以是同一个线程,比如像做延时操作)

  • 基本原理是,Handler发送Message,并放入对应线程的MessageQueue中,Looper让对应线程无限循环地从自己的MessageQueue拿出消息处理。(Handler和Looper持有的是同一个MessageQueue)

  • 使用的最多的场景就是,我们在UI线程创建好Handler实例,然后在子线程做完耗时操作后,想要更新UI内容时,通过mHander sendMessage通知UI更新。而正如1所说,理论上我们也完全可以由UI线程发送Message,由子线程接收并处理,只是比较少见。另一个多见的场景是延时任务,往往是UI线程自己跟自己通信。

2. 为什么需要Handler?

  • 一般来说,我们只要在子线程把信息放进主线程的MessageQueue里就可以了。因为,在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到主线程的MessageQueue 的实例,就可以放入消息,主线程的Looper在轮询MessageQueue时,就可以取出该消息并处理。

  • 主线程的MessageQueue的实例是可以拿到的(在主线程下 Looper.myLooper().mQueue),但是Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类.只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用,Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。

  • Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。

// Looper.loop轮询方法
public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        // 获取MessageQueue
        final MessageQueue queue = me.mQueue;
        // 省略
        // 具体的轮询逻辑,无限for循环
        for (;;) {
        // 取出Message
            Message msg = queue.next(); // might block
            // 省略
            try {
            // target为发送message的Handler实例
            // Handler处理
                msg.target.dispatchMessage(msg);
            } 
            // 省略
        }
    }

所以说,引入Handler只是为了大家使用方便以及代码的清晰简洁。并没有大家想的那么高深。


3. 具体的使用

3.1 主线程使用Handler刷新UI

Handler handler = new Handler()

实际会调用

 public Handler(Callback callback, boolean async) {
        // 省略
        // 这里也验证了,Handler在哪个线程创建,他就会持有哪个线程的Looper
        // 我们一般在UI线程初始化,Handler就会持有UI线程的Looper和MessageQueue
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 这里也验证了,Handler在哪个线程创建,他就会持有哪个线程的MessageQueue
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

在其他线程创建想在主线程处理事情的Handler,可以用以下代码可以达到相应效果

// 传入UI线程的Looper
Handler handler = new Handler(Looper.getMainLooper);

3.2 LooperThread子线程使用(官网的文档)

class LooperThread extends Thread {
       //其他线程可以通过mHandler这个引用给该线程的消息队列添加消息
       public Handler mHandler;
       public void run() {
            Looper.prepare();
            //需要在线程进入死循环之前,创建一个Handler实例供外界线程给自己发消息
            mHandler = new Handler() {
                public void handleMessage(Message msg) {
                    //Handler 对象在这个线程构建,那么handleMessage的方法就在这个线程执行
                }
            };
            // loop方法里会用到Handler实例
            // 所以必须先初始化Handler
            // 如果在loop方法之后初始化Handler,那么loop方法执行中会报错
            Looper.loop();
            // loop之后才初始化Handler,代码是无效的,loop是死循环,正常情况下这行代码就不会执行了
            // mHandler = new Handler()......
        }
    }

需要说明的是,上面写到的Looper.prepare,创建Handler和Looper.loop方法的顺序并不一定不能改。如果你想的话,也完全可以loop执行之后创建Handler,只是创建的流程不能写在loop后面。因为loop里的死循环会导致你的代码不执行,你可以在主线程通过LooperThread.mHander这样的引用,来创建实例,效果也是一样的。

注意,其实UI线程也有类似的代码,如下:

public final class ActivityThread {
    public static final void main(String[] args) {
        ......
        Looper.prepareMainLooper();
        ......
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {    
            sMainThreadHandler = thread.getHandler();
        }
        ......
        Looper.loop();
        ......
    }
}

与上面的例子类似,系统在这里也为我们初始化了一个Handler。我们每次使用Handler mHandler = new Handler();都是额外创建了一个Handler,与原有的不冲突。msg.target.dispatchMessage(msg)这句代码会判断target。


4.Handler发送消息的两种方式

Handler,它直接继承自Object,一个Handler允许发送和处理Runnable或者Message对象,并且会关联到主线程的MessageQueue中。所以Handler把消息压入MessageQueue也有两种方式,post(new Runnable)和sendMessage(Message msg)。

4.1 post

post允许把一个Runnable对象入队到消息队列中。它的方法有:

  • post(Runnable)
  • postAtTime(Runnable,long)
  • postDelayed(Runnable,long)。

4.2 sendMessage

sendMessage允许把一个包含消息数据的Message对象压入到消息队列中。它的方法有:

  • sendEmptyMessage(int)
  • sendMessage(Message)
  • sendMessageAtTime(Message,long)
  • sendMessageDelayed(Message,long)。

从上面的各种方法可以看出,不管是post还是sendMessage都具有多种方法,它们可以设定Runnable对象和Message对象被入队到消息队列中,是立即执行还是延迟执行。

4.3 post和sendMessage方法的联系和区别

先看源码

public final boolean post(Runnable r) {
   return  sendMessageDelayed(getPostMessage(r), 0);
}

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

代码很好理解,post方法其实还是把Runnable对象转化成了Message,区别在于普通的sendMessage不会使用callBack参数,它具体的处理逻辑在Handler的handleMessage里。而post会使用Message的callback,callback就是Runnable对象,所以使用post方法的话,无需再去写具体的handleMessage逻辑。源码如下:

// dispatchMessage方法是在Looper.loop开启循环,开始处理MessageQueue里的每个Message时调用的,可以发现,默认先调用callback,没有callback才会使用handleMessage
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {                        
            return;
            }
        }
        handleMessage(msg); 
    }
}

5. 了解Message

Message是一个final类,所以不可被继承。Message封装了线程中传递的消息,如果对于一般的数据,Message提供了getData()和setData()方法来获取与设置数据,其中操作的数据是一个Bundle对象,这个Bundle对象提供一系列的getXxx()和setXxx()方法用于传递基本数据类型的键值对,对于基本数据类型,使用起来很简单,这里不再详细讲解。而对于复杂的数据类型,如一个对象的传递就要相对复杂一些。在Bundle中提供了两个方法,专门用来传递对象的,但是这两个方法也有相应的限制,需要实现特定的接口,当然,一些Android自带的类,其实已经实现了这两个接口中的某一个,可以直接使用。方法如下:

putParcelable(String key,Parcelable value):需要传递的对象类实现Parcelable接口。

pubSerializable(String key,Serializable value):需要传递的对象类实现Serializable接口。

还有另外一种方式在Message中传递对象,那就是使用Message自带的obj属性传值,它是一个Object类型,所以可以传递任意类型的对象,Message自带的有如下几个属性:

int arg1:参数一,用于传递不复杂的数据,复杂数据使用setData()传递。

int arg2:参数二,用于传递不复杂的数据,复杂数据使用setData()传递。

Object obj:传递一个任意的对象。

int what:定义的消息码,一般用于设定消息的标志。

注意

对于Message对象,一般并不推荐直接使用它的构造方法得到,而是建议通过使用Message.obtain()这个静态的方法或者Handler.obtainMessage()获取。Message.obtain()会从消息池中获取一个Message对象,如果消息池中是空的,才会使用构造方法实例化一个新Message,这样有利于消息资源的利用。并不需要担心消息池中的消息过多,它是有上限的,上限为10个。Handler.obtainMessage()具有多个重载方法,如果查看源码,会发现其实Handler.obtainMessage()在内部也是调用的Message.obtain()。

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

推荐阅读更多精彩内容

  • 前言 在Android开发的多线程应用场景中,Handler机制十分常用 今天,我将手把手带你深入分析Handle...
    BrotherChen阅读 471评论 0 0
  • 1. 前言 在之前的图解Handler原理最后留下了几个课后题,如果还没看过那篇文章的,建议先看那篇文章,课后题如...
    唐江旭阅读 5,935评论 5 45
  • 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息...
    cxm11阅读 6,422评论 2 39
  • 【Android Handler 消息机制】 前言 在Android开发中,我们都知道不能在主线程中执行耗时的任务...
    Rtia阅读 4,820评论 1 28
  • 在最后一期《向往的生活》中,谢娜和赵丽颖做客“蘑菇屋”,娜姐无意间提到,“丽颖一年只休息了三天!” 365天只休息...
    无尾熊自成长阅读 370评论 0 3