Android基础笔记整理:组件通信—9.5 消息机制

 

目录

Looper、Message和Handler

图9-31 Looper、Message和Handler关系图

提示:对于Looper、Message和Handler的另一种解释

9.5.1 消息类:Message

表9-14 Message类定义的变量及常用方法

9.5.2 消息操作类:Handler

代码——03090500_Handler基本的实现方式

【例9-45】定义布局管理器——main.xml

【例9-46】定义Activity程序,通过Timer类完成定时更新

9.5.3 消息通道:Looper

表9-16 Looper类的常用方法

代码——03090501_线程间通讯(使用Loop)

【例9-47】定义布局文件——main.xml

【例9-48】定义Activity程序,完成处理

代码——03090502_线程间通讯(不使用Loop)

说明——提问:以上代码不使用Looper也可以完成?

【例9-49 】修改MyHandler类的定义

【例9-50】修改按钮单击事件操作

回答:所修改的代码为简便写法。

代码——03090503_自定义子线程类中启动Looper

【例9-51】定义布局管理器——main.xml

【例9-52】定义Activity操作类(程序代码采用分段列出形式进行解释)

说明——提问:为什么子线程接收到的信息只能通过后台输出?

回答:子线程不能更新主线程的UI组件。


LooperMessageHandler

Android操作系统中存在着消息队列的操作,用消息队列可以完成主线程和子线程之间的消息传递,要想完成这些线程的消息操作,则需要使用LooperMessageHandler类,这3个类的关系,如图9-31所示。

9-31 LooperMessageHandler关系图

从图9-31中可以发现,Looper本身提供的就是一个消息队列的集合,而每个消息都可以通 过Handler增加和取出,而操作Handler的对象就是主线程( UI Thread)和子线程。

 

提示:对于LooperMessageHandler的另一种解释

如果把Looper比喻成一个正在排队买票的队伍,那么每一排队的人就是一个Message,而一个维护队伍的管理员就相当于是一个Handler,管理员负责通知队外的人进到队列之中等待,也负责通知队列中的人离开队伍。

3个操作类都在android.os包中定义,下面依次讲解其具体作用,并且通过实际的程序进行说明。

 

9.5.1 消息类:Message

android.os.Message的主要功能是进行消息的封装,同时可以指定消息的操作形式,Message 类定义的变量及常用方法如表9-14所示。

9-14 Message类定义的变量及常用方法

Message类中,使用最多的是whatobj两个变量,往往会通过what变量指明一个Message

所携带的是何种信息,而通过obj传递信息。

 

9.5.2 消息操作类:Handler

Message对象封装了所有的消息,而这些消息的操作需要android.os.Handler类完成,Handler 类所定义的常用方法,如表9-15所示。

可以发现,表9-14中所列出的方法都是用于操作Message的,可以向队列中添加Message,也可以从队列中删除指定的Message。

 

代码——03090500_Handler基本的实现方式

  (1)在主线程中创建Handler,通过回调的方式获取、处理消息

  (2)在新启动的线程中发送消息

下面通过一个具体的程序来观察HandlerMessage的操作。

本程序将完成一个数据的自动增长操作,每过一秒之后,由子线程向主线程(UI线程)发送更新的消息,

主线程根据指定的消息类型进行操作。

 

【例9-45】定义布局管理器——main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<TextView
		android:id="@+id/info"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content" />
</LinearLayout>

 

【例9-46】定义Activity程序,通过Timer类完成定时更新

package org.lxh.demo;

import java.util.Timer;
import java.util.TimerTask;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;

/**
 * 定义Activity程序,通过Timer类完成定时更新
 * @author luminal
 */
public class MyMessageDemo extends Activity {
	
	private static int count = 0;// 定义全局变量
	public static final int SET = 1;// 设置一个what标记
	private TextView info = null;// 文本显示组件
	
	private Handler myHandler = new Handler() {// 定义Handler对象
		@Override
		public void handleMessage(android.os.Message msg) {// 覆写此方法
			
			switch (msg.what) {// 判断操作类型
			case SET:// 为设置文本操作
				MyMessageDemo.this.info.setText(Thread.currentThread()+"---MLDN ---- " + count++);
			}
			
		}
	};
	
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		super.setContentView(R.layout.main);
		this.info = (TextView) super.findViewById(R.id.info);
		
		Timer timer = new Timer();// 定义调度器
		timer.schedule(new MyTask(), 0, 1000);// 立即开始,1秒一增长
	}
	
	//看源码,TimerTask其实已经实现了Runnable接口
	private class MyTask extends TimerTask {// 定义定时调度的具体实现类
		@Override
		public void run() {// 启动子线程 
			
			//MyMessageDemo.this.info.setText(Thread.currentThread()+"---MLDN ---- " + count++);
			//报异常:Only the original thread that created a view hierarchy can touch its views.
			//直译:只有创建视图层次结构的原始线程可以访问它的视图。
			//也就是子线程不能更新主线程(UI线程)的UI组件。后面会详细介绍下
			
			Message msg = new Message();// 定义Message
			msg.what = SET ;// 操作为设置显示文字
			// 注意:不是发送消息到子线程,而是子线程发送消息到主线程。
			MyMessageDemo.this.myHandler.sendMessage(msg);
		}
	}
	
	
}

在本程序中采用定时器实现文本的显示更新,在MyTask类中的run()方法里,通过定义的Handler对象发送要传递的消息,此处发送的只是一个what,主要目的是告诉Handler消息的处理类型, 而Handler对象接收消息之后,将修改全局变量count 的内容,并将更新后的文本设置到文本显示组件中,程序的运行效果如:图9-32所示

 

注意:不是发送消息到子线程,而是子线程发送消息到主线程。

这个是整理笔记发现的一个注释问题吧!所以上图把当前线程名,给打印出来了。

 

9.5.3 消息通道:Looper

在使用Handler处理Message,都需要依靠一个Looper通道完成,当用户取得一个Handler对象时,实际上都是通过Looper成的。在一个Activity类中,会自动帮助用户启动Looper对象,而若是在一个用户自定义的类中,则需要用户手工调用Looper类中的若干方法,之后才可以正常启动Looper对象。Looper类的常用方法如表9-16所示。

9-16 Looper类的常用方法

因为Looper的使用较为复杂,所以下面先通过一个简单的程序,观察LooperHandlerMessage之间的关系。

 

代码——03090501_线程间通讯(使用Loop)

【例9-47】定义布局文件——main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<TextView
		android:id="@+id/info"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content" />
	<Button
		android:id="@+id/but"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="启动" />
</LinearLayout>

在此布局文件中,只定义了一个文本显示组件和一个按钮,当用户单击按钮之后会触发单击事件,将Message发送到Handler中处理,并在文本显示组件中显示接收到的信息。

 

【例9-48定义Activity程序,完成处理

package org.lxh.demo;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

/**
 * 线程间通讯(Loop)
 * @author luminal
 */
public class MyLoopDemo extends Activity {
	private TextView info;// 定义文本显示组件
	private Button but;// 定义按钮组件
	private static final int SET = 1;// what操作码
	
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		super.setContentView(R.layout.main);// 调用布局文件
		this.info = (TextView) super.findViewById(R.id.info);// 取得组件
		this.but = (Button) super.findViewById(R.id.but);// 取得组件
		this.but.setOnClickListener(new OnClickListenerImpl());// 设置单击事件
	}
	
	
	private class OnClickListenerImpl implements OnClickListener {
		@Override
		public void onClick(View view) {
			switch (view.getId()) {// 判断操作的组件ID
			case R.id.but:// 表示按钮操作
				
				Looper looper = Looper.myLooper();// 取得当前的线程
				MyHandler myHandler = new MyHandler(looper);// 构造一个Handler
				myHandler.removeMessages(0);// 清空所有的消息队列
				String data = "魔乐科技软件学院(MLDN)";// 设置要发送的数据
				
				//从全局消息池返回一条新消息,四个参数用于定义携带的数据。详见官网
				//Message obtainMessage (int what, int arg1, int arg2, Object obj)
				Message msg = myHandler.obtainMessage(SET, 1, 1, data);
				myHandler.sendMessage(msg);// 发送消息
				break;
			}
		}
	}
	
	
	
	private class MyHandler extends Handler {
		public MyHandler(Looper looper) {// 接收Looper
			super(looper);// 调用父类构造
		}
		@Override
		public void handleMessage(Message msg) {// 处理消息
			switch (msg.what) {// 判断操作形式
			case 1:
				MyLoopDemo.this.info.setText(msg.obj.toString());// 设置文本内容
			}
		}
	}
	
	
}

在本程序的按钮单击事件中,首先通过Looper.myLooper()方法取得当前的线程对象,随后将此Looper对象与Handler对象连接起来,并进行消息的发送,程序的运行效果如图9-33所示。

 

 

代码——03090502_线程间通讯(不使用Loop)

说明——提问:以上代码不使用Looper也可以完成?

9.5.2节的程序中直接使用HandlerMessage也可以完成消息的发送,

而且通过本节的代码,也无法发现Looper的作用,如果现在将代码修改如下:

【例9-49 修改MyHandler的定义

 

【例9-50】修改按钮单击事件操作

 

程序运行之后,也可以发送消息,那为什么还要使用Looper呢?这样是不是太麻烦了。

 

回答:所修改的代码为简便写法。

因为现在是由Activity直接发送消息的,所以在Handler子类中会自动帮助用户创建好要操作的Looper对象,而在例9-49中所编写的代码只是将Looper类的对象的操作流程编写清楚,但从实际来讲效果都是一样的。如果要想更清楚地理解Looper的处理,则可以继续研究下面的程序。

 

代码——03090503_自定义子线程类中启动Looper

1、如何在自定义的子线程类中启动Looper

2、主线程和子线程,都创建Handler对象,进行交互

之前的程序都是由系统为用户提供的Looper对象完成操作,如果用户使用的是一个自定义的线程类,就要由用户自己进行Looper对象的维护操作了。下面通过程序演示如何在自定义的线程类中启动Looper

下面的程序将定义两个类:个是主线程的操作类MyThreadDemo一个是子线程操作的内部类ChildThread,所有主线程接收到的信息都将在文本框中进行显示,而所有子线程接收到的信息将釆用系统输出方式显示。

【例9-51】定义布局管理器——main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical" 
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	<TextView 
		android:layout_width="fill_parent"
		android:layout_height="wrap_content" 
		android:id="@+id/msg"
		android:text="等待子线程发送消息。"/>
	<Button
		android:layout_width="fill_parent"
		android:layout_height="wrap_content" 
		android:id="@+id/but"
		android:text="交互"/>
</LinearLayout>

本布局管理器中定义了文本显示组件,用于显示主线程接收到的信息,当单击按钮时,主线程将向子线程中发送消息。

 

【例9-52】定义Activity操作类(程序代码采用分段列出形式进行解释)

package org.lxh.demo;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

/**
 * 1、如何在自定义的线程类中启动Looper
 * 2、主线程和子线程,都创建Handler对象,进行交互
 * @author luminal
 */
public class MyThreadDemo extends Activity {
	public static final int SETMAIN = 1;// 设置一个what标记
	public static final int SETCHILD = 2;// 设置一个what标记
	private Handler mainHandler, childHandler;// 定义Handler对象
	private TextView msg;// 文本显示组件
	private Button but;// 按钮组件

在本程序中最重要的定义就是两个Handler对象:一个表示主线程中操作的Handler (mainHandler)

另外一个表示子线程操作的 Handler (childHandler)

 

	class ChildThread implements Runnable {// 子线程类
		@Override
		public void run() {
			
			Looper.prepare();// 初始化Looper
			MyThreadDemo.this.childHandler = new Handler() {
				public void handleMessage(Message msg) {
					switch (msg.what) {// 判断what操作
					case SETCHILD:// 接收主线程发送给子线程的信息
						System.out.println("*** Main to Child Message : "
								+ msg.obj);// 打印消息
						
						
						Message toMain = MyThreadDemo.this.mainHandler
								.obtainMessage();// 创建Message
						toMain.obj = "这是子线程发给主线程的信息,"
								+ super.getLooper().getThread()
									.getName()+"\n\n";// 设置显示文字
						toMain.what = SETMAIN;// 设置主线程操作的状态码
						// 子线程向主线程发送消息
						MyThreadDemo.this.mainHandler.sendMessage(toMain);
						
						break;
					}
				}
			};
			Looper.loop();// 启动该线程的消息队列
			
		}
	}

本程序是一个自定义的线程类,此类实现了Runnable接口,并且在run()方法中,采用匿名内部类的形式实例化了childHandler类的对象。在handleMessage()方法中进行消息处理时,首先判断消息的类型(如果为SETCHILD,则表示由主线程发消息给子线程)之后将此消息进行输出,随后又使用mainHandler向主线程发送一个消息。

在本段程序中,最重要的部分就是Looper对象操作的prepare()loop()方法,如果没有这两个方法,将无法通过子线程创建Looper,也就无法由子线程发送消息给主线程。

 

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		super.setContentView(R.layout.main);// 调用布局文件
		this.msg = (TextView) super.findViewById(R.id.msg);// 取得组件
		this.but = (Button) super.findViewById(R.id.but);// 取得按钮
		
		
		this.mainHandler = new Handler() {// 主线程的Handler对象
			public void handleMessage(Message msg) {// 消息处理
				switch (msg.what) {// 判断Message类型
				case SETMAIN:// 设置主线程的操作类
					MyThreadDemo.this.msg.setText("主线程接收数据:"
							+ msg.obj.toString());// 设置文本内容
					break;
				}
			}
		};
		
		
		new Thread(new ChildThread(), "Child Thread").start();// 启动子线程
		this.but.setOnClickListener(new OnClickListenerImpl());// 单击事件操作
		
	}
	
	
	
	private class OnClickListenerImpl implements OnClickListener {
		@Override
		public void onClick(View view) {
			if (MyThreadDemo.this.childHandler != null) {// 已实例化子线程Handler
				
				Message childMsg = MyThreadDemo.this.childHandler
						.obtainMessage();// 创建一个消息
				
				childMsg.obj = MyThreadDemo.this.mainHandler.getLooper()
						.getThread().getName()
						+ " --> Hello MLDN ";// 设置消息内容
				childMsg.what = SETCHILD;// 操作码

				// 主线程向子线程发送消息
				MyThreadDemo.this.childHandler.sendMessage(childMsg);
				
			}
		}
	}

在本程序中,首先为mainHandler对象进行实例化,采用匿名内部类的形式并覆写了类中的 handleMessage()方法,

这样当主线程接收到消息时,将在文本显示组件中进行显示,而后启动自定义的线程类。

而按钮单击事件中的代码,主要是主线程向子线程发送的消息操作。

	@Override
	protected void onDestroy() {
		super.onDestroy();
		MyThreadDemo.this.childHandler.getLooper().quit();// 结束队列
	}

程序是当销毁Activity程序时,同时结束操作的队列。

由于整个程序操作较为复杂,下面通过图9-34进行详细说明。

注:为了看起来输出文字更舒服,输出文字内容的代码,我做了一点点修改,对于程序没有任何影响

通过图9-34可以发现,当主线程要发消息给子线程时,首先需要设置一个 what 的内容(此时为SETCHILD),之后使用子线程的Handler对象(childHandler)发送消息;

当子线程操作时,首先将what的内容设置为SETMAIN,而后采用主线程的Handler对象(mainHandler)发送消息。当子线程接收到主线程发送来的消息后,将通过System.out进行输出操作,输出信息如下:

主线程收到子线程发送来的消息后,将在文本显示组件中显示文字,接收的效果如图 9-35 所示。

 

说明——提问:为什么子线程接收到的信息只能通过后台输出?

在上面的程序中,当子线程接收到主线程发来的信息之后,为什么不直接在文本显示组件msg)中显示,

如下面代码操作一样:

MyThreadDemo.this.msg.setText("主线程接收数据:"+ msg.obj.toString());// 设置文本内容

   这样着起来不是更方便吗?

回答:子线程不能更新主线程的UI组件

主线程操作的是UI线程,可以直接进行UI组件的更新操作,文本组件就是一个UI组件,

如果现在非要执行以上操作代码,则将会出现如下错误信息:

android.view.ViewRootSCalledFromWrongThreadException: 

Only the original thread that created a view hierarchy can touch its views.

所以非主线程(子线程)是不能刷新主线程界面的,正因为如此,才使用在子线程中直接输出的形式完成主线程接收数据的显示。

而如果用户非要解决以上问题,可以让子线程和主线程使用同一个Handler对象完成, 这一点可以在随后的进度条组件(ProgressBar中看到如何使用。

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

被开发耽误的大厨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值