消息传递机制之Handler使用总结

一.Handler基础知识

       Handler消息处理机制是一个功能强大的数据传递机制,主要功能是用来把子线程的数据传递给主线程,让主线程进行UI操作。
       android的消息处理有三个核心类:Looper,Handler和Message。其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与Message Queue打交道,因此我没将其作为核心类。下面一一介绍:

(一)线程的魔法师 Looper

       Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓 Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

public class LooperThread extends Thread { 
       @Override 3.         
public void run() { 
     // 将当前线程初始化为Looper线程 
      Looper.prepare();
        // ...其他处理,如实例化handler 
// 开始循环处理消息队列 
 Looper.loop(); 
  } 
}

       通过上面两行核心代码,你的线程就升级为Looper线程了!
       在主线程中,上面这两个Looper的方法,系统已经帮我们做好了,直接实例化Handler对象就可以使用了,但是在子线程中,需要我们自己写这两个方法来升级为Looper线程。

注意: Looper.loop()之后的方法不会再执行到

1.Looper.prepare()

Looper预处理操作,效果如下图所示:
h1

通过上图可以看到,现在你的线程中有一个Looper对象,它的内部维护了一个消息队列MQ。

注意,一个Thread只能有一个Looper对象。

Looper.prepare() 方法的简单代码:

public static final void prepare() {
  if (sThreadLocal.get() != null) { 
 // 试图在有Looper的线程中再次创建Looper将抛出异常 throw new RuntimeException("Only one Looper may be created per thread");
 } 
 sThreadLocal.set(new Looper()); 
   }
 }

这个方法确保一个线程只用一个Looper对象。

2. Looper.loop() 保持循环接收信息的方法

任务执行的示意图:
h2

调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MQ中取出队头的消息(也 叫任务)执行。

3.除了prepare()和loop()方法,Looper类还提供了一些有用的方法,

(1)Looper.myLooper()得到当前线程looper对象:
public static final Looper myLooper()
(2)getThread()得到looper对象所属线程:
public Thread getThread()
(3)quit()方法结束looper循环:
public void quit()

4.Looper总结

       每个线程有且最多只能有一个Looper对象,它是一个ThreadLocal Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行 Looper使一个线程变成Looper线程。

(二)异步处理大师 Handler

       什么是Handler?
       Handler扮演了往MQ上添加消息和处理消息的角色(只处理由自己发出的消息),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务 (handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法 将关联当前线程的looper,不过这也是可以set的。
       下面是Handler消息处理的示意图:
h3

可以看到,一个线程可以有多个Handler,但是只能有一个Looper!

有了Handler对象之后,我们就可以使用下面一些方法来发送消息

1.post(Runnable)
2.postAtTime(Runnable,long)
3.postDelayed(Runnable,long)
4.sendEmptyMessage(int)
5.sendMessage(Message)
6.sendMessageAtTime(Message,long)
7.sendMessageDelayed(Message,long)
       这些方法就可以向 MQ上发送消息了。光看这些API你可能会觉得handler能发两种消息,一种是Runnable对 象,一种是message对象,这是直观的理解,但其实post发出的Runnable对象最后都被封装成message对象了。使用post(Runnable)方法后,子线程被系统调用启动线程,不需要我们去start。

(三)Message类

       Message是线程之间传递信息的载体,包含了对消息的描述和任意的数据对象。Message被 存放在MessageQueue中,一个MessageQueue中可以包含多个Message对象,Message中 包含了两个额外的 int字段和一个object字段,这样在大部分情况下,使用者就不需要再做内 存分配工作了。
       虽然Message的构造函数是public的,但是最好是使用Message.obtain()或Handler.obtainMessage()函数来获取Message对象,因为 Message的实现中包含了回收再利用的机制,可以提供效率。

1. arg1–int 用来存放整型数据

2. arg2–int 用来存放整型数据

3. obj–Object 用来存放发送给接收器的Object类型的任意对象

4. replayTo–Messenger 用来指定此Message发送到何处的可选Messager对象

5. what–int 用于指定用户自定义的消息代码,这样接收者可以了解这个消息的信息

注意:使用Message类的属性可以携带int类型数据,如果要携带其它类型的数据,可以先将要携带的数据保存到Bundle对象中。然后通过Message类对象的setDate()方法将其添加到Message中。
如果一个Message只需要携带简单的int型信息,应优先使 用Message.argl和Message.arg2属性来传递信息,这比用Bundle更省内存。 尽可能使用Message.what来标识信息,以便用不同方式处理Message。

下面通过两个示例来进一步介绍Handler的使用方法。

二.使用Handler的post方法来调动线程的示例

本示例并没有很复杂的资源下载代码,使用的是倒入程序的图片,主要是通过post(Runnable run)方法实现ImageView的图片资源不断替换。

(一)布局文件代码

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.lwz.handlerRunnable.MainActivity">

    <ImageView
        android:id="@+id/main_iv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        android:src="@mipmap/a1" />

</RelativeLayout>

(二)java代码

package com.lwz.handlerRunnable;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;


/***
 * 实现图片轮播的效果
 */
public class MainActivity extends AppCompatActivity {

    //定义布局内的组件
    ImageView imageView;
    //当前的显示的图片的游标值
    int current = 0;
    //图片资源
    int[] images = {
            R.mipmap.a1, R.mipmap.a2, R.mipmap.a3, R.mipmap.a4, R.mipmap.a5,
            R.mipmap.a6, R.mipmap.a7, R.mipmap.a8, R.mipmap.a9, R.mipmap.a10
    };


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        imageView = (ImageView) findViewById(R.id.main_iv);
        //一秒后开始线程
        handler.postDelayed(runnable, 1000);
    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //接收到子线程中的数据what,并更换图片资源值
            imageView.setImageResource(images[msg.what % images.length]);
            //使用handler对象开始线程方法
            handler.post(runnable);
        }
    };

    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(1000);//休眠一秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            current++;
            //给Handler对象发送数据
            handler.sendEmptyMessage(current);
        }
    };

}

       上面程序使用的是取巧的方法让线程一直在执行。实际上线程只运行一次,但是结束后又开启了一条线程!
循环运行的思路:
1.让主程序的handler对象开启子线程
2.在子线程中发送handler消息
3.主线程的handler对象接收到方法后,改变UI界面
4.接下来一直循环上面的3个步骤。从而实现ImageView图片资源的一直变化的显示结果。
这里要知道的是第一次启动子线程是在onCreate方法里面,以后开始子线程都是在handlerMessage方法里面开启线程。
上面程序只是介绍post()方法的使用,但是一般不会用来一直循环线程,会造成关闭程序出现各种问题出现!

三.Handler的一个应用示例

程序设计:
用户只要在输入框输入关键字,会马上有相关的文本提示供选择。
分析:
如果只是使用文件变化的监听器,文本一直输入,提示框会不断的变化。这样设计如果在网络中会比较消耗资源。
本程序中使用的方法是,文本变化后隔0.5钟后再搜索相关内容,并显示提示文本。如果是在0.5秒内,文本一直在变化,这里要取消上次搜索的任务。

(一)布局文件设计

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.administrator.lesson16_delaysearch.MainActivity">


    <EditText
        android:id="@+id/et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入要搜索的java关键字"
        android:padding="10dp" />

    <ListView
        android:id="@+id/lv"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

(二)java代码设计

package com.example.administrator.lesson16_delaysearch;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements TextWatcher {
    public static final String[] search =
            {
                    "abstract   表明类或者成员方法具有抽象属性",
                    "assert 用来进行程序调试",
                    "boolean    基本数据类型之一,布尔类型",
                    "break  提前跳出一个kua",
                    "byte   基本数据类型之一,字节类型",
                    "case   用在switch语句之中,表示其中的一个分支",
                    "catch  用在异常处理中,用来捕捉异常",
                    "char   基本数据类型之一,字符类型",
                    "class  类",
                    "const  保留关键字,没有具体含义",
                    "continue   回到一个块的开始处",
                    "default    默认,例如,用在switch语句中,表明一个默认的分支",
                    "do 用在do-while循环结构中",
                    "double 基本数据类型之一,双精度浮点数类型",
                    "else   用在条件语句中,表明当条件不成立时的分支",
                    "enum   枚举",
                    "extends    表明一个类型是另一个类型的子类型,这里常见的类型有类和接口",
                    "final  用来说明最终属性,表明一个类不能派生出子类,或者成员方法不能被覆盖,或者成员域的值不能被改变",
                    "finally    用于处理异常情况,用来声明一个基本肯定会被执行到的语句块",
                    "float  基本数据类型之一,单精度浮点数类型",
                    "for    一种循环结构的引导词",
                    "goto   保留关键字,没有具体含义",
                    "if 条件语句的引导词",
                    "implements 表明一个类实现了给定的接口",
                    "import 表明要访问指定的类或包",
                    "instanceof 用来测试一个对象是否是指定类型的实例对象",
                    "int    基本数据类型之一,整数类型",
                    "interface  接口",
                    "long   基本数据类型之一,长整数类型",
                    "native 用来声明一个方法是由与计算机相关的语言(如C/C++/FORTRAN语言)实现的",
                    "new    用来创建新实例对象",
                    "null   用来标识一个不确定的对象",
                    "package    包",
                    "private    一种访问控制方式:私用模式",
                    "protected  一种访问控制方式:保护模式",
                    "public 一种访问控制方式:共用模式",
                    "return 从成员方法中返回数据",
                    "short  基本数据类型之一,短整数类型",
                    "static 表明具有静态属性",
                    "strictfp   用来声明FP_strict(单精度或双精度浮点数)表达式遵循IEEE754算术规范",
                    "super  表明当前对象的父类型的引用或者父类型的构造方法",
                    "switch 分支语句结构的引导词",
                    "synchronized   表明一段代码需要同步执行",
                    "this   指向当前实例对象的引用",
                    "throw  抛出一个异常",
                    "throws 声明在当前定义的成员方法中所有需要抛出的异常",
                    "transient  声明不用序列化的成员域",
                    "try    尝试一个可能抛出异常的程序块",
                    "void   声明当前成员方法没有返回值",
                    "volatile表明两个或者多个变量必须同步地发生变化",
                    "while 用在循环结构中",
            };


    ListView lv;
    EditText et;

    List<String> mList = new ArrayList<>();
    ArrayAdapter<String> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lv = (ListView) findViewById(R.id.lv);
        et = (EditText) findViewById(R.id.et);
        adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mList);
        lv.setAdapter(adapter);
        et.addTextChangedListener(this);
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        //只要文本改变,去匹配
        // 延迟搜索
        // 输入完毕后,1秒之后,在去匹配


        //只要输入,就会执行该方法
        //我们需要取消前一次搜索,重新开始延迟搜索;
        handler.removeCallbacks(run);
        handler.postDelayed(run, 1000);
    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };

    private Runnable run = new Runnable() {
        @Override
        public void run() {
            mList.clear();
            String s = et.getText().toString();
            if (s.length() > 0) {
                //匹配字符串
                for (String s1 : search) {
                    if (s1.contains(s)) {//class
                        mList.add(s1.toString());
                    }
                }
            }
            adapter.notifyDataSetChanged();
        }
    };

    @Override
    public void afterTextChanged(Editable s) {

    }
}

程序运行后的显示结果:

h4

输入ab这两字符时,显示界面:

h5

上面就是Handler的简单应用。

这是我关于Handler比较多个人观点讲解的一篇文章。
http://blog.csdn.net/wenzhi20102321/article/details/52837834

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

峥嵘life

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值