Android 线程通信之Handler -- 一篇文章彻底弄懂Handler

标签: android 线程 handler


1 什么是handler

Handler中文翻译为“处理者”,这里的“处理”顾名思义是在某种条件发生之后所要进行的操作。

2 handler的作用

  • Handler主要用于异步线程进行通信。这种方式也是Android官方推荐的方式,比较节省性能。我们知道,Android主线程(UI线程)主要负责UI绘制,耗时操作不能放在主线程执行,要不然容易造成卡顿,严重时会ANR。在Android中最常使用Handler的场景就是主线程调用子线程去进行网络访问,子线程在获取到网络访问的返回结果并处理数据之后,通知主线程去更新UI。例如下拉刷新。Handler在这里充当的是消息的发送者和接收者的角色。所谓消息,也就是前面讲的某种特定条件发生之后的状态。例如获取完网络数据之后,获取数据成功或者失败,就是不同的状态,需要发送不同的消息。
  • 另外,Handler还用于延时处理某个任务。

3 Handler的使用原理

  • Handler既是发送者也是接收处理者,一个消息发送出来,交由哪个Handler去处理,这个完全取决于这个消息的发送者是哪个Handler。即一个Handler发送的消息,只能交由自己去处理。要做到异步线程进行通信,关键在于在A线程中拿到B线程的Handler,拿到之后就可以使用B线程的Handler去发送消息,交由B线程去处理消息了。线程与Handler之间是多对一的关系,一个线程可以有多个Handler,一个Handler只能且必须绑定一个线程。一个线程有多个Handler(使用场景会在后面介绍),用于处理不用类型的消息。
  • 使用Handler的表现形式有两种:
    (1)通过发送消息(sendMessage方法)接收消息(接收消息即回调该Handler的handleMessage方法)并处理的形式;
    (2)通过发送Runnable对象的形式,直接让该Handler所在线程执行其run方法。即调用post(Runnabler)方法。需要注意的是:(1)该Runnable也会被包装成一个消息的形式进行传递。(2)整个过程并没有另外新建线程,该Runnable在Handler所在线程执行。

4 Handler相关概念

4.1 Message

Message用来记录需要传递的信息。该类继承自Parcelable类。
官方描述为:

/**

  • Defines a message containing a description and arbitrary data object that can be

  • sent to a {@link Handler}. This object contains two extra int fields and an

  • extra object field that allow you to not do allocations in many cases.

  • While the constructor of Message is public, the best way to get

  • one of these is to call {@link #obtain Message.obtain()} or one of the

  • {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull

  • them from a pool of recycled objects.
    */

  • Message类默认定义了两个int和一个Object(传递对象)用于传输常用的数据,如果不够用,则使用Bundle。

public final class Message implements Parcelable {
/**
* User-defined message code so that the recipient can identify
* what this message is about. Each {@link Handler} has its own name-space
* for message codes, so you do not need to worry about yours conflicting
* with other handlers.
*/
public int what;

/**
 * arg1 and arg2 are lower-cost alternatives to using
 * {@link #setData(Bundle) setData()} if you only need to store a
 * few integer values.
 */
public int arg1; 

/**
 * arg1 and arg2 are lower-cost alternatives to using
 * {@link #setData(Bundle) setData()} if you only need to store a
 * few integer values.
 */
public int arg2;

/**
 * An arbitrary object to send to the recipient.  When using
 * {@link Messenger} to send the message across processes this can only
 * be non-null if it contains a Parcelable of a framework class (not one
 * implemented by the application).   For other data transfer use
 * {@link #setData}.
 * 
 * <p>Note that Parcelable objects here are not supported prior to
 * the {@link android.os.Build.VERSION_CODES#FROYO} release.
 */
public Object obj;
  • 另外,还定义了一个target,表示与该Message绑定的Handler。一个callback,当使用post(Runnable r)时,r会被包装成员一个Message,这个Message的callback就是传入的r。

4.2 MessageQueue

Handler发送的消息,可以立即处理也可以延时处理,肯定需要一个容器来存放消息。MessageQueue就是这样一个消息队列。

4.3 Looper

Message被Handler发送出来,会被放入MessageQueue中,放入其中的Message在条件成熟的时候会被取出来,交由Handler去处理。整个过程中还需要一个对象来执行获取并放入,取出并交给对应Handler的工作。这个工作就由Looper来完成。
Looper需要执行两个过程:(1)与线程进行绑定prepare方法(2)读取消息队列中的消息并交由Handler进行处理loop方法

      private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

** 4.4 整个过程

  • 一个线程对应一个Looper、一个MessageQueue以及多个Handler、多个Message
  • 一个Message对象,在被发送的时候将发送该Message对象的Handler对象的引用保存到target成员变量上,looper在轮询Message的时候,就直接把该Message交给message.target这个Handler去处理就可以了。
  • 在调用Handler的sendMessage后,会把Message对象放入一个MessageQueue队列,该队列属于某个Looper对象,每个Looper对象通过 ThreadLocal.set(new Looper())跟一个Thread绑定了。Looper对象所属的线程在Looper.Loop方法中循环执行如下步骤:从MessageQueue队列读取 Message对象,并把Message对象交由Handler处理,调用Handler的dispatchMessage方法。

5 Handler的使用步骤

5.1 获取Handler

  • 当前Thread已经有Looper的情况(主线程默认已经在ActivityThread的main方法中初始化Looper),则直接new一个Handler。可以选择重写或者不重写handleMessage方法(不重写将只能使用post的方式调用)。此时该Handler会自动绑定到当前线程。
    官方解释为:

/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/

public Handler() {
        this(null, false);
    }

/**
* Subclasses must implement this to receive messages.
*/

public void handleMessage(Message msg) {
    }

实例:

public class MyActivity extends BaseActivity{
    private static final String TAG = "MyActivity";
    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case CommonConstants.MSG_REFRESH_VIDEO_ITEM:
                    doSomething();
                    break;
                default:
                    break;
            }
        }
    };
  • 当前线程还没有Looper的情况,则需要先初始化当前线程的Looper,然后new一个Handler。
    初始化当前Looper的方式为:
  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
     /** Initialize the current thread as a looper.
      * This gives you a chance to create handlers that then reference
      * this looper, before actually starting the loop. Be sure to call
      * {@link #loop()} after calling this method, and end it by calling
      * {@link #quit()}.
      */
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
   /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
  *          Looper.loop();
  *      }
  *  }

在初始化Handler之前需要调用Lopper.prepare()方法初始化Looper,初始化Handler之后调用Looper.loop()开始轮询消息。

  • 如果在子线程使用主线程的Handler,可以使用new Handler(Looper.getMainLooper())的方法得到。

5.2 使用Handler发送消息

handler发送消息的方法是比较多,常用的就包括
获取消息包括Runnable对象和message对象两种,获取Message的方式包括:
1 直接new一个Message,设置what等参数(不推荐)。
2 通过handler获取复用的Message(推荐)

  • handler.obtainMessage()
  • handler.obtainMessage(int what)
  • obtainMessage(int what, Object obj)
  • ......

3 直接发送空Message,只有what

  • sendEmptyMessage(int what)

发送消息的方法:

post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message) 将消息发送到消息队列
msg.sendToTarget() 将消息发送到消息队列
sendMessageAtTime(Message,long) 定时将消息发送到消息队列
sendMessageDelayed(Message,long) 延迟一定时间后,将消息发送到消息队列

5.3 使用Handler接收消息并处理

在Handler的handlerMessage中根据what去处理相应逻辑。

6 发送Runnable和发送Message的区别

post方式:发送runnable,实际上是把runnable对象设置为一个Message的成员变量callback之后,发送该Message。最后都是走到sendMessageDelayed方法当中。唯一的区别在于,post方式的Message的callback不是null,send方式发送的Message的callback是null。
1

 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;
    }

2

  public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

send方式:
1

public final boolean sendMessage(Message msg)
    {
        return sendMessageDelayed(msg, 0);
    }

2

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

Looper在处理的时候:如果msg.callback的是null,则直接调用其run方法;如果为空,则交由handlerMessage去处理。
1

 public static void loop() {
        ...
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
    }

2

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

7 实例

实例1:handler定时器
需求:在主线程每隔一段时间执行一次
代码:
1

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

推荐阅读更多精彩内容