一.线程通信相关知识



    这里的消息传送其实就是不同线程的消息通信。
在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的使用,这里也展示了复杂数据的发送和接收。


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

Handler消息传送机制总结_第1张图片


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

Handler消息传送机制总结_第2张图片

    这里其实可以设计成一个简单的定时器。
    上面的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 list = new ArrayList();                    // 计算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();        }    }}


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

Handler消息传送机制总结_第3张图片

    通过上面的俩个例子,应该能简单的理解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机制相对来说也是一个比较复杂的过程,本文中如有误笔也请大家及时纠正。

更多相关文章

  1. Android线程通信机制-Handler(Java层)
  2. [原]Android应用程序线程消息循环模型分析
  3. android系统中的多线程(一): 关于在android中启动线程以及线程间
  4. Android中消息系统模型和Handler Looper
  5. Android开发实践:基于命令模式的异步任务线程

随机推荐

  1. Android探索之旅 | Android(安卓)Studio
  2. 2018年60个实用Android框架排行榜
  3. Android 常用 adb 命令总结
  4. Android RelativeLayout布局详解
  5. Android操作SQLite数据库
  6. Android中Dialog样式的设置
  7. Android FrameWork Service 之 StatusBar
  8. Android(安卓)锁屏功能
  9. Android(安卓)单页面 音乐播放器和电影播
  10. Android 滚动条属性