• Android studio Handler消息处理1


    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 对象不能重复使用。

  • 相关阅读:
    【PAT甲级】1043 Is It a Binary Search Tree (25 分)(判断是否为BST的先序遍历并输出后序遍历)
    Educational Codeforces Round 73 (Rated for Div. 2)F(线段树,扫描线)
    【PAT甲级】1042 Shuffling Machine (20 分)
    【PAT甲级】1041 Be Unique (20 分)(多重集)
    【PAT甲级】1040 Longest Symmetric String (25 分)(cin.getline(s,1007))
    【PAT甲级】1039 Course List for Student (25 分)(vector嵌套于map,段错误原因未知)
    Codeforces Round #588 (Div. 2)E(DFS,思维,__gcd,树)
    2017-3-9 SQL server 数据库
    2017-3-8 学生信息展示习题
    2017-3-5 C#基础 函数--递归
  • 原文地址:https://www.cnblogs.com/1329197745a/p/14905698.html
Copyright © 2020-2023  润新知