Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Handler消息传送机制总结

$
0
0


一.线程通信相关知识



    这里的消息传送其实就是不同线程的消息通信。
在Android中子线程是不能直接改变UI界面的,这是Android的运行机制里面规定的,比如你在子线程改变主线程界面的文本(tv.setText(“XXX”)),程序会马上蹦掉。
    所以在子线程做完某些事情后,要改变主页面就要通过数据的通信,让主线程接收到信息后自己改变UI界面。
Android中Handler能满足线程间的通信。

(一)线程通信中相关的类

1.Handler先进先出原则。

2.Looper类用来管理特定线程内对象之间的消息交换(MessageExchange)。

3.Message类用来保存数据。

(二)线程通信的过程

1.Looper: 一个线程可以产生一个Looper对象,由它来管理此线程里的MessageQueue(消息队列)。

2.Handler: 你可以构造Handler对象来与Looper沟通,以便push新消息到MessageQueue里;或者接收Looper从Message Queue取出)所送来的消息。

3. Message Queue(消息队列):用来存放线程放入的消息。

4.线程:UIthread 通常就是main thread,而Android启动程序时会替它建立一个MessageQueue。

(三)理解


看完上面的概念,对于初学者来说,脑袋其实还是一片混乱的。对Handler的消息传送机制还是不懂。其实最好的理解方法还是先学会它的简单使用,在进一步研究它的机制。


这里简单描述一下它的原理:
Handler用到了监听事件和回调方法的思想。


整个原理的大概步骤如下:


1.在类中创建Handler对象,用来接收和处理消息
2.然后再创建一个Loop对象,用来管理MessageQueue
3.MessageQueue来接收和保存子线程发过来的消息
4.上面只是做好接收消息的准备,做好相关准备后,才会让子线程发送消息
5.子线程直接调用Handler对象,通过Handler对象的SendMessage方法来对主线程发送数据
6.消息是保存在MessageQueue对象中的
7.Loop控制MessageQueue传递消息给Handler对象,这里就要注意了,虽然概念上说的是Handler能对子线程的数据进行接收和处理。但实际上它是接收MessageQueue里面的数据,然后进行处理的,MessageQueue里面可以接收很多很多的数据,它们以队列的形式排列,当Handler处理完一个数据后,MessageQueue就会再传递下一个数据给Handler。
8.上面是要重点理解的机制过程,MessageQueue对象内存放很多子线程发来的信息,有序的保存下来,并不做处理。而Handler一次只接收MessageQueue对象传来的一个数据,并进行处理。
9.这是最后一步了,Handler对象对传来的信息进行判断,并作相应的行为。



二.Handler的使用

(一)在子线程改变UI界面


主线程创建Handler对象,等待子线程发来信息,然后做相应的处理。
1.布局文件的简单设计

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="start"
            android:text="开始" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:onClick="stop"
            android:text="停止" />
    </LinearLayout>

    <TextView
        android:id="@+id/main_tv_showmessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="message" />


</LinearLayout>


2.java代码的设计

package com.example.handlerchangeui;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends Activity {

    // 定义布局里面的控件
    static TextView tv_message;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化TextView
        tv_message = (TextView) findViewById(R.id.main_tv_showmessage);

    }

    // 创建Handler对象,匿名类的方式实现handleMessage方法,这里是子线程
    Handler handler = new Handler() {

        /**
         * 接收Message信息, 只要Handler对象执行了SendMessage方法, 这个方法就会触发
         */
        @Override
        public void handleMessage(Message msg) {

            // 获取Message的what数值
            int index = msg.what;

            // 获取Message里面的复杂数据
            Bundle date = new Bundle();
            date = msg.getData();
            String name = date.getString("name");
            int age = date.getInt("age");
            String sex = date.getString("sex");
            // 这里是主线程,可以直接对页面进行修改
            String line = name + age + sex + "line" + index + "......";
            tv_message.setText(line);
        }

    };

    // 线程是否继续执行的布尔值
    boolean continueRun = true;

    // 创建子线程的对象,匿名类的方式实现run方法
    Runnable runable = new Runnable() {

        @Override
        public void run() {
            int index = 0;
            while (continueRun) {

                index++;
                // 这里的SendEmptyMessage只能发送的是数字
                // handler.sendEmptyMessage(index);

                // 在子线程中利用Handler对象的SendMessage发送复杂的消息
                // 先创建Message对象
                Message msg = Message.obtain();// =Message.obtain();
                                                // 和new Message();是一个意思
                msg.what = index;
                // Message对象保存的数据是Bundle类型的
                Bundle data = new Bundle();
                data.putString("name", "李文志");
                data.putInt("age", 18);
                data.putString("sex", "男");
                // 把数据保存到Message对象中
                msg.setData(data);
                // 使用Handler对象发送消息
                handler.sendMessage(msg);

                // 让线程休眠一秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    };

    // 开始按钮的监听事件
    public void start(View v) {
        // 启动线程
        continueRun = true;
        new Thread(runable).start();
    }

    // 停止按钮的监听事件
    public void stop(View v) {
        // 关闭线程
        continueRun = false;
    }

}


本示例只是简单的实现了Handler的使用,这里也展示了复杂数据的发送和接收。


运行程序之后,如图所示:

Handler1


点击开始按钮后,页面的TextView改变,如图所示:

Handler2

    这里其实可以设计成一个简单的定时器。
    上面的Handler对象是主线程中创建的,所以它可以直接对UI界面进行修改。
    线程中通信如果只是用来发送数值的信息,可以在子线程中使用handler.sendEmptyMessage(what);这里的what是int类型的数值。这时不需要创建Message对象了。


(二)Handler的特殊例子


    主线程给子线程发送一个数字,然后让子线程算出该数字以内的所有质数。然后以吐司的形式显示出来。
    思路:这里在子线程创建Handler对象,主线程调用子线程的Handler对象来给子线程发送信息,子线程接收信息后做相应处理。

1.布局文件的简单设计

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <EditText
        android:id="@+id/main_et_prime"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2" />

    <Button
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:onClick="getPrime"
        android:text="计算质数" />

</LinearLayout>


2.java代码的设计

package com.example.handlerforprime;

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

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity {

    // 定义布局控件
    EditText et_prime;
    // 定义一个子线程类 的对象
    PrimeThread thread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 初始化控件
        et_prime = (EditText) findViewById(R.id.main_et_prime);
        // 实例化子线程
        thread = new PrimeThread();
        // 启动子线程
        thread.start();
        // 上面是要先创建handler实例才能进行下面的handler发送信息
        // 所有要让子线程先执行

    }

    // 按钮的监听事件
    public void getPrime(View v) {
        // 获取输入框内的数字
        String prime = et_prime.getText().toString();
        // 判断非空
        if (TextUtils.isEmpty(prime)) {
            Toast.makeText(this, "你还没有数据输入数据", Toast.LENGTH_SHORT).show();
        }
        // 防止非法数据
        try {
            // 获取字符串数值
            int primeNum = Integer.parseInt(prime);
            // 使用Handler对象发送数据
            thread.handler.sendEmptyMessage(primeNum);
        } catch (Exception e) {
            Toast.makeText(this, "你输入的数据不合法", Toast.LENGTH_SHORT).show();
        }

    }

    // 定义一个线程类
    class PrimeThread extends Thread {
        // 定义Handler对象
        public Handler handler;

        // 重写run方法
        // 在线程里面创建Handler对象
        @Override
        public void run() {
            super.run();
            // 创建Loop对象,系统会自动创建MessageQueue
            Looper.prepare();
            // 实例化Handler对象
            handler = new Handler() {
                // 重写handlerMessage方法
                // 只要是通过同一个Handler对象发送的数据肯定会执行这个方法
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    // 接收what值的数据数据
                    int prime = msg.what;
                    // 创建一个集合保存质数
                    List<Integer> list = new ArrayList<Integer>();

                    // 计算0到what之间的质数
                    outer: for (int i = 2; i <= prime; i++) {
                        // 如果i对2到i的平方根的值求余都不等于零,那么这个数是质数
                        // 如果期间有一个值为零,就不是质数
                        for (int j = 2; j <= Math.sqrt(i); j++) {
                            // 这里设置i!=2是为了让2保存到集合中去
                            if (i != 2 && i % j == 0) {
                                // 跳到下一个i的值,如果直接使用continue是跳到下一个j的值;
                                continue outer;
                            }

                        }
                        // 把符合条件的i的值添加到集合中
                        list.add(i);
                    }
                    // 显示结合的元素,就是所有的质数的值
                    Toast.makeText(MainActivity.this, list.toString(),
                            Toast.LENGTH_LONG).show();
                }
            };

            // 让Loop一直进行工作,即让handMessage一直在等待消息
            Looper.loop();
        }
    }

}


程序运行结果,如图所示:

Handler3

    通过上面的俩个例子,应该能简单的理解Handler的使用方法。
对比这两个例子,我们发现示例中使用的Handler对象是同一个,一边是创建者,那么它就一直通过handlerMessage方法来在监听等待消息;另一边是调用者,使用handler.sendMessage(msg);或使用handler.sendEmptyMessage(what)来发送信息。最后创建者接收到信息并进行处理。
    还有一个值得我们注意的是,上面的例子中在子线程创建Handler后,要调用Loop的方法Looper.prepare();和Looper.loop();其中第一个方法是让Loop对象创建,第二个方法是让Loop对象一直处于工作状态。正是因为有第二个方法的执行才能让handlerMessage方法内接收到数据。
    但是在主线程主创建Handler就不需要调用Looper.prepare();和Looper.loop();因为系统已经在主线程加载了这里个方法。


其实上面只是Handler一些比较基础的用法和原理。下面在介绍一下复杂的原理知识。


三.Android消息处理的核心类


android的消息处理有三个核心类:Looper,Handler和Message。其实还有一Message Queue(MQ消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,所以它不算是个核心类。

1. 消息类:Message类


     android.os.Message的主要功能是进行消息的封装,同时可以指定消息的操作形式,Message类定义的变量和常用方法如下:

(1)public int what:变量,用于定义此Message属于何种操作

(2)public Object obj:变量,用于定义此Message传递的信息数据,通过它传递信息

(3)public int arg1:变量,传递一些整型数据时使用

(4)public int arg2:变量,传递一些整型数据时使用

(5)public Handler getTarget():普通方法,取得操作此消息的Handler对象。


    在整个消息处理机制中,message又叫task,封装了任务携带的信息和处理该任务的handler。message的用法比较简单,但是有这么几点需要注意:
1)尽管Message有public的默认构造方法,但是你应该通过Message.obtain()来从消息池中获得空消息对象,以节省资源。
2)如果你的message只需要携带简单的int信息,请优先使用Message.arg1和Message.arg2来传递信息,这比用Bundle更省内存
3)擅用message.what来标识信息,以便用不同方式处理message。

2. 消息通道:Looper


在使用Handler处理Message时,需要Looper(通道)来完成。在一个Activity中,系统会自动帮用户启动Looper对象,而在一个用户自定义的类中,则需要用户手工调用Looper类中的方法,然后才可以正常启动Looper对象。Looper的字面意思是“循环者”,它被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在程序开发中(尤其是GUI开发中),我们经常会需要一个线程不断循环,一旦有新任务则执行,执行完继续等待下一个任务,这就是Looper线程。使用Looper类创建Looper线程很简单:

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


    这是在子线程中创建Handler的情况,如果在主线程中创建Handler是不需要调用Looper.prepare(); 和 Looper.loop(); 方法。

关于这两个方法在系统底层做了什么事情:
    Looper.prepare();创建了Loop对象,Loop对象是用来管理MessageQueue对象的,MessageQueue是帮助Handler保存数据的。
    Looper.loop();是在底层保证线程的一直运行状态,只要调用者调用handler.SendMessage(–);方法,创建者就可以接收到数据。

Looper有以下几个要点:
1)每个线程有且只能有一个Looper对象,它是一个ThreadLocal
2)Looper内部有一个消息队列,loop()方法调用后线程开始不断从队列中取出消息执行
3)Looper使一个线程变成Looper线程。
那么,我们如何操作Message Queue上的消息呢?这就是Handler的用处了

3. 消息操作类:Handler类


     Message对象封装了所有的消息,而这些消息的操作需要android.os.Handler类完成。什么是handler?handler起到了处理MQ上的消息的作用(只处理由自己发出的消息,所有Handler都是同一个对象来的),即通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。handler创建时会关联一个looper,默认的构造方法将关联当前线程的looper,不过这也是可以set的。
还有一个要注意的:一个线程可以有多个Handler,但是只能有一个Looper!对应的Handler对象只接收自己对象发送的信息。


创建Handler实例化对象时,可以重写的回调方法:
void handlerMessage(Message msg);

有了handler之后,我们就可以使用Handler发送消息的所有方法:
post(Runnable)
postAtTime(Runnable, long)
postDelayed(Runnable, long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message, long)
sendMessageDelayed(Message, long)

    上面就是Handler核心类的详细介绍,如果想要详细理解Handler来,要去理解核心类的系统后台的相关处理。。这里不做详细解释。


    在实际应用中,子线程肯定使用来做耗时的操作的,比如:下载东西,遍历寻找文件,或计算很复杂的运算等等,在结果出来之后就要在主线程中显示出来。这里就需要在主线程中创建Handler对象,当子线程的工作任务完成后,调用Handler对象的方法来给主线程发送数据,主线程接收到数据后,进行简单处理就显示在UI界面上。

四.最后的总结:

(一)Handler类的主要作用:

1.在子线程中发送数据。

2.在主线程中接收数据,处理数据。


(二)Handler创建和使用的简化过程:

1.在主线程创建Handler对象,重写handlerMessage(msg)方法,用来随时接收数据

2.在子线程调用主线程创建的Handler对象,来给主线程发送信息。

3.主线程接收到子线程发送来的信息,进行处理,可以直接显示在UI界面上。


(三)其他的


    对于基本概念我们都是要记住的:UI线程就是我们说的主线程。
    还有就是Handler能在不同线程之间进行数据传递,并不局限于子线程和主线程,也可以是多个线程的数据传递,但是要注意的是,Handler对象只接收自己对象发送的数据。比如说,多个子线程利用主线程创建的Handler对象给主线程发送数据也是可以的,子线程发送的数据都会保存到MessageQueue里面,然后Handler对象对MessageQueue的里面的数据进行逐个的接收和处理。
    上面的两个示例使用的数据传递,尽量不要像上面一样,一般的Message.what不是直接拿来使用的数据,而是用来判断某种行为的数据值,然后创建者做相应的行为,就像Intent数据传递的resultCode请求码的作用是一样的。
    上面就是个人对Handler机制的理解,有些方面说的并不准确,MessageQueue的理解可能比较片面话和个人化,因为它都是系统的底层运转机制,在实际调用中并没有设计。

    Handler机制相对来说也是一个比较复杂的过程,本文中如有误笔也请大家及时纠正。

作者:wenzhi20102321 发表于2016/10/17 12:50:05 原文链接
阅读:14 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>