zoukankan      html  css  js  c++  java
  • Handler消息处理

    Handler 在 Android 中的应用很广泛,基本上每个 Android 开发人员都会使用到它。本篇文章将会介绍 Handler 和异步消息机制相关的使用方法,下一篇会从源码的角度分析 Android 中异步消息处理的流程。

    Android 中的异步消息处理框架由 Handler 、MessageQueue、Looper 和 ThreadLocal 等组成。Handler 是我们使用最多的一个类,主要负责发送和处理消息,MessageQueue 是一个队列,用来存储 Handler 发送来的消息,而 Looper 则是一个死循环。

    Handler 的使用场景

    由于 Android 系统不允许在主线程进行耗时任务,因此网络请求等一般都会开新的线程执行,然而,Android 中的控件不是线程安全的,因此 Android 系统要求只能在主线程中访问 UI 控件(当然如果你非要在子线程中访问,也是能运行的)。那么从子线程中得到的数据怎么返回到主线程给 UI 使用呢?答案很简单,大家都知道,可以使用 Handler 将数据返回到主线程。

    但有时候你会发现,有些项目里面没有开启子线程,却在使用 Handler,这就是 Handler 的另一个使用场景:消息处理。如果你有一个个的任务需要排队处理,那么使用 Handler 是很合适的。

    Handler 的创建

    Handler 必须与一个 Looper 关联才能使用。怎么样关联呢?你可以手动传入一个 Looper 对象,让 Handler 关联你传入的 Handler。也可以什么都不传,这时候 Handler 会自己去找当前线程的 Looper,如果找到就万事大吉,如果当前线程没有 Looper,那么就会报错。

    1
    2
    3
    4
    5
    // 不传入 Looper,系统会自己去获取当前线程中的 Looper。
    Handler handler = new Handler();
    
    // 传入自定的 Looper。
    Handler  handler = new Handler(Looper);

    由于主线程(UI 线程)是唯一一个默认自带了 Looper 的线程,所以在主线程中你可以直接用上面第一种方式创建一个 Handler 对象而不用担心 Looper 的存在性问题。

    另外有一点,在创建 Looper 的时候,系统会检查该线程是否已经有 Looper 对象了,如果已经有 Looper 对象了,你再塞一个进去,它就会报错,因此一个线程中最多只能有一个 Looper。所以在主线程中不能自己创建一个 Looper,但是你可以传入当前线程的 Looper 进去:

    1
    2
    3
    4
    5
    6
    // 在主线程中,你还可以这么创建 Handler。虽然有点多余。
    // 获取主线程的 Looper
    Handler handler = new Handler(getMainLooper());
    
    // 获取当前线程的 Looper
    Handler handler = new Handler(Looper.myLooper());

    再次强调:Handler 必须和 Looper 相关联才能使用。一个 Handler 只能关联一个 Looper,而且一旦关联上了,就不能更改。当然,一个 Looper 可以关联多个 Handler。

    在子线程中创建 Handler

    一般情况下,我们都是在主线程中创建 Handler,但是有时候也需要在子线程中处理消息队列。由于主线程是唯一一个自带了 Looper 的线程。因此在主线程中创建和使用 Handler 相对是比较简单的,也是最常见的。但是还有些情况是需要在子线程中创建一个消息队列的,在子线程中使用 Handler 需要提供一个 Looper,于是代码就应该像下面那样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 为异步消息处理框架准备一个线程
    class LooperThread extends Thread {
    	public Handler mHandler;
    	public void run() {
    		// 准备一个 Looper
    		Looper.prepare();
    		// Handler 对象会和该线程的 Looper 关联起来。
    		mHandler = new Handler() {
    			public void handlerMessage(Message msg) {
    				// 在这里处理传入的消息
    			}
    		};
    		// 使用该 Looper,启动一个循环的消息队列。
    		Looper.loop();
    	}
    }

    这大概应该是一个固定的模板了。

    使用 HandlerThread

    可以使用 HandlerThread 来简化在子线程中创建 Handler 的流程。HalderThread 是一个自带了 Looper 的线程类,

    1
    2
    3
    4
    5
    6
    7
    public class MyHandlerThread extends HandlerThread {
    	// 你只需要添加一个 Handler
    	private Handler handler;
    	public MyHandlerThread(String name) {
    		super(name)
    	}
    }

    HandlerThread 也并不神秘,它只是帮你调用了 Looper.prepare() 方法和 Looper.loop()方法而已。也就是说如果你一个类继承了 ThreadHandler,你可以像在主线程那样使用 Handler。

    发送和处理消息

    准备好 Handler 的环境后,就可以使用 Handler 来发送和处理消息了。处理消息是在 Handler 的 handleMessage() 方法中进行 的,而发送消息有两种方法:发送一个 Message 对象,和投递一个 Runnable 对象。下面分别介绍这两类方法。

    发送 Message 对象

    Message 对象可以包含一些简单的数据,并且可以通过 Handler 进行发送和处理。Message 对象可以通过 Message.what(int) 方法中的 int 参数来标志该对象。Message 对象使用两个 int 类型的字段来存储需要发送的信息,也可以使用一个 Object 类型的对象来存储一个简单对象的信息。

    • Message.what:标识一个 Message 对象。

    • Message.arg1:需要传递的 int 类型的数据。

    • Message.arg2:需要传递的 int 类型的数据。

    • Message.obj:存储任意数据类型(Object)的对象。

    怎么创建一个 Message 对象呢?你当然可以使用 Message msg = new Message() 方法来创建一个 Message 对象,但是不建议这么做。因为我们有更高效的方法来获得 Message 对象:使用 Message msg = MMessage.obtain() 或 Handler.obtainMessage() 来获取一个 Message。这个 Message 对象被 Handler 发送到 MessageQueue 之后,并不会被销毁,可以重复利用,因此比使用 new 方法来创建一个 Message 对象效率更高。

    当你需要将消息传递到一个后台线程时,建议使用 Handler.obtainMessage 来创建一个 Message 对象:

    1
    2
    3
    4
    5
    6
    int what = 0;
    String hello = "Hello!";
    // 获取一个和后台线程关联的 Message 对象
    Message msg = mHandler.obtainMessage(what, hello);
    // 发送一个消息到后台线程。
    mHandler.sendMessage(msg);

    根据使用场景不同,Android 系统提供了几种常用的封装:

    • Handler.sendMessage( Message msg ):在 MessageQueue 中添加一个 Message 对象。

    • Handler.sendMessageAtFrontOfQueue( Message msg ):添加一个 Message 对象到 MessageQueue 的前面。

    • Handler.sendMessageAtTime ( Message msg, long timeInMills ):在指定的时间发送一个 Message 对象。

    • Handler.sendMessageDelayed( Message msg, long timeInMillis ):在指定的时间之后,发送 Message 对象。

    发送完一个消息后,怎么处理发送的消息任务呢?在我们创建 Handler 对象的时候,可以实现它的 handleMessage() 方法,在这个方法里面处理 handler 发送的 Message 对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // 根据 msg.what 字段的标志处理不同的消息 
            // 如果 Message 比较简单,你也可以不使用 msg.what 来作为区分。
            switch (msg.what) {
    	        ......
            }
        }
    };

    当然,还有很多处理 Message 消息的方法,比如你想取消一个 Message 的发送,可以调用 handler.removeMessages(int what); 方法。其他的方法可以自行查阅 API 文档。

    投递一个 Runnable 对象

    使用 Handler 发送任务的另一个方法就是投递一个 Runnable 对象(“投递”一词,主要是翻译 post() 方法)。创建一个 Runnable 对象必须要实现 run() 方法,因此,我们需要投递执行的任务就要写在 run() 方法中,而不用像处理 Message 对象一样,在 handleMessage() 方法里面实现。

    1
    2
    3
    4
    5
    6
    7
    // 声明一个 Runnable
    Runnable r = new Runnable() {
    	@Override
    	public void run() {
    		// 任务的具体内容
    	}
    }

    在 Handler 中,有多种方式投递一个 Runnable 对象:

    • Handler.post(Runnable r):在 MessageQueue 中添加一个 Runnable 对象。

    • Handler.postAtFrontOfQueue:在 MessageQueue 的头部添加一个 Runnable 对象。

    • Handler.postAtTime(Runnable r, long timeMillis):在指定的时间将 Runnable 对象添加到 MessageQueue 中。

    • Handler.postDelay(Runnable r, long delay):经过了指定的时间后,将 Runnable 对象添加到 MessageQueue 中。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 投递一个 Runnable 对象
    Handler handler = new Handler();
    handler.post(
    	new Runnable() {
    		@Override
    		public void run() {
    			// 任务的具体内容
    		}
    	});

    当然,你也可以使用 Activity.runOnUiThread() 来投递一个 Runnable 对象。

    1
    2
    3
    4
    5
    6
    7
    Activity.runOnUiThread (
    	new Runnable() {
    		@Override
    		public void run() {
    			// 任务的具体内容
    		}
    	});

    该方法如果是在主线程上调用的,那么会立即执行。如果是在其他线程上使用的,那么就会将该 Runnable 对象投递到主线程的消息队列中去。

    甚至,你会发现在 View 对象中,也可以调用 post() 方法和 postDelay() 方法!比如你可以在 Activity 的 onCreate() 方法里面调用 view.post() 来获取控件的大小:

    1
    2
    3
    4
    5
    6
    mTextView.post(new Runnable() {
        @Override
        public void run() {
            mTextView.getHeight();    
        }
    });

    比如你也可以使用 view.postDelay() 方法来防止控件的连续点击:

    1
    2
    3
    4
    5
    6
    7
    mButton.setEnabled(false);
    mButton.postDelayed(new Runnable() {
               @Override
               public void run() {
                  view.setEnabled(true); 
               }
           },300);

    至于为什么在 onCreate() 方法里面调用 view.post() 方法可以获取控件大小,虽然和 Handler 及消息队列相关,但比较复杂,给个链接地址,自己去看:Android应用启动优化:一种DelayLoad的实现和原理(下篇)

    有一点需要记住,Runnable 对象和 Message 对象不同,Runnable 对象不能重复使用。

    两种方法的区别

    那么,为什么 Android 系统会提供两种方法来发送消息任务呢?这当然和使用场景相关了,根据不同的使用场景,这两种方法还是有区别的。当然,在平常的使用过程中或许你不会考虑这么多,因为在具体的使用场景中,你很可能“不经意”地就将两种方法的使用场景给区别开了:

    • 当发送者知道如何处理消息的话,使用 Handler.post() 方法。发送者直接在 run() 方法中处理任务。

    • 当接收者知道如何处理消息的话,使用 Handler.sendMessage() 方法。发送者将 Message 对象发射出去后,handler (接收者)调用 handleMessage() 方法处理任务。

    实例:倒计时

    下面,我们用 Handler 来实现倒计时功能。先来分析一下倒计时的相关功能需求:

    • 需要知道总时间和每一步的间隔时间。

    • 每次到了间隔时间就回调通知,总时间结束后也要回调通知。

    根据这个需求,我们可以大致猜想一下如何实现:

    • 需要提供两个构造参数的构造方法,一个是总时间,一个是间隔时间。

    • 需要提供两个抽象方法供子类实现,一个是每次到了间隔时间的回调通知,另一个是总时间结束后的回调通知。

    有了这个思路,实现就很简单了,下面直接给出代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    public abstract class Timer {
    
        private long mMillisInFuture;
        private long mCountdownInterval;
        private long mStopTimeInFuture;
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                // 计算剩余时间
                long millisLeft = mStopTimeInFuture - SystemClock.elapsedRealtime();
                if (millisLeft <= 0) {
                    // 如果剩余时间小于或等于 0, 则回调 onFinish() 方法。
                    onFinish();
                } else if (millisLeft < mCountdownInterval) {
                    // 当剩余时间小于一次间隔时间时,不回调 onTick() 方法,直接等到最后再发送
                    sendMessageDelayed(obtainMessage(), millisLeft);
                } else {
                    // 回调 onTick() 方法,并在一次间隔时间后再次发送消息。
                    onTick(millisLeft);
                    sendMessageDelayed(obtainMessage(), mCountdownInterval);
                }
            }
        };
    
        public Timer(long millisInFuture, long countdownInterval) {
            mMillisInFuture = millisInFuture;
            mCountdownInterval = countdownInterval;
        }
    
        /**
         * 调用 start() 方法以启动倒计时
         * @return
         */
        public Timer start() {
            if (mMillisInFuture < 0) {
                onFinish();
                return this;
            }
            // 获取倒计时完成的系统时间
            mStopTimeInFuture = SystemClock.elapsedRealtime() + mMillisInFuture;
            // 发送第一个消息
            mHandler.sendMessage(mHandler.obtainMessage());
            return this;
        }
    
        /**
         * 每经过一次间隔时间就回调一次 onTick 方法
         * @param millionLeft   剩余时间
         */
        abstract void onTick(long millionLeft);
    
        /**
         * 倒计时完成后,回调 onFinish() 方法
         */
        abstract void onFinish();
    }

     

    其实上面的代码是 Android 系统中 CountDownTimer 类的简化版,CountDownTimer 增加了线程安全机制并处理了一些特殊情况,有兴趣的朋友可以自行查阅。


    文章转载自:Handler与异步消息处理_慕课手记 (imooc.com)

  • 相关阅读:
    SDUT 2772 数据结构实验之串一:KMP简单应用
    SDUT 3346 数据结构实验之二叉树七:叶子问题
    SDUT 3342 数据结构实验之二叉树三:统计叶子数
    SDUT 3345 数据结构实验之二叉树六:哈夫曼编码
    SDUT 3343 数据结构实验之二叉树四:还原二叉树
    SDUT 3340 数据结构实验之二叉树一:树的同构
    SDUT 3344 数据结构实验之二叉树五:层序遍历
    SDUT 3341 数据结构实验之二叉树二:遍历二叉树
    Jmeter入门14 后置处理器JSON Extractor 提取json的多个值
    Jmeter入门13 jmeter发送application/octet-stream二进制流数据
  • 原文地址:https://www.cnblogs.com/ltw222/p/14903647.html
Copyright © 2011-2022 走看看