编程思想之消息机制
从一个剧情开始
路遥的《平凡的世界》因为翻拍成电视剧,又再次火起来了!我们就从这里开始吧,其小说是以这样一个场景开头的: 在一个半山腰县立高中的大院坝里,在一个校园内的南墙根下,按班级排起了十几个纵队的年轻男女,各班的值日生正忙碌地给众人分发饭菜…… 菜分为甲、乙、丙三等,甲菜以土豆、白菜、粉条为主,还有可人大肉片,乙菜没有肉,丙菜只有清水煮白萝卜。主食也分为三等:白面馍,玉米面馍,高粱面馍,白、黄、黑分别代表了三种差别,学生们戏称欧洲、亚洲、非洲。每个人的饭菜都是昨天登记好并付了饭票的,在这一长长的队伍中自然以光景较好的富家子弟排在最前,光景一般的随后,而那些家庭贫困少吃缺穿的学生只能在其他学生走后才姗姗来迟……
这一活生生的例子虽然看着有些悲凉(排队打饭的情景相信曾经是学生的你一定经历过,可能没这般悲凉而已),却像极了消息机制的原理,也许发明消息机制的灵感就是原来于这样的生活吧!排队的学生就是消息队列,值日生分发饭菜就消息循环并完成消息处理,学生吃饭就类似于事件处理。
什么是消息?
何为消息?消息就是带有某种信息的信号,如你用鼠标点击一个窗口会产生鼠标的消息,键盘输入字符会产生键盘的消息,一个窗口大小的改变也会产生消息。 消息从何而来?根据冯·诺依曼的体系结构计算机有运算器、存储器、控制器和输入设备和输出设备五大部件组成,消息主要来自输入设备,如键盘、鼠标、扫描仪等,也可来自已窗口和操作系统。 消息机制的三大要点:消息队列、消息循环(分发)、消息处理。其结构如下:
消息队列就是存放消息的一种队列,具有先进先出的特点。每产生一个消息都会添加进消息队列中,在Window中消息队列是在操作系统中定义的。消息队列就如同一群排队打饭的少男少女,这群人中光景较好的排在前面,光景较差的排在后面,可以理解成是一种优先级队列!要想更多的了解队列的相关知识,可参见队列。 消息循环就是通过循环(如while)不断地从消息队列中取得队首的消息,并将消息分发出去。类似于上面的例子中分发饭菜值日生。 消息处理就是在接收到消息之后根据不同的消息类型做出不同的处理。上面例子中值日生根据学生不同类型的饭票给他们不同等级的饭菜就是消息处理,学生手中的饭票就是消息所携带的信息。 事件是根据接收到的消息的具体信息做出的特定的处理,放在代码中是事件响应函数。上面的例子中学生拿到饭菜后吃饭就是具体的事件。
消息机制模拟
在这里我们以控制台输入信息模拟窗口、对话框接收鼠标、键盘等消息,以ArrayBlockingQueue对象存放消息队列。在控制台中输入一个数值和一个字符串代表一个消息,输入-1结束输入。模拟代码如下:
package message;
import java.util.Queue;
import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
/**
* 消息
* @author luoweifu
*/
class Message {
//消息类型
public static final int KEY_MSG = 1;
public static final int MOUSE_MSG = 2;
public static final int SYS_MSG = 3;
private Object source; //来源
private int type; //类型
private String info; //信息
public Message(Object source, int type, String info) {
super();
this.source = source;
this.type = type;
this.info = info;
}
public Object getSource() {
return source;
}
public void setSource(Object source) {
this.source = source;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public static int getKeyMsg() {
return KEY_MSG;
}
public static int getMouseMsg() {
return MOUSE_MSG;
}
public static int getSysMsg() {
return SYS_MSG;
}
}
interface MessageProcess {
public void doMessage(Message msg);
}
/**
* 窗口模拟类
*/
class WindowSimulator implements MessageProcess{
private ArrayBlockingQueue msgQueue;
public WindowSimulator(ArrayBlockingQueue msgQueue) {
this.msgQueue = msgQueue;
}
public void GenerateMsg() {
while(true) {
Scanner scanner = new Scanner(System.in);
int msgType = scanner.nextInt();
if(msgType < 0) { //输入负数结束循环
break;
}
String msgInfo = scanner.next();
Message msg = new Message(this, msgType, msgInfo);
try {
msgQueue.put(msg); //新消息加入到队尾
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
/**
* 消息处理
*/
public void doMessage(Message msg) {
switch(msg.getType()) {
case Message.KEY_MSG:
onKeyDown(msg);
break;
case Message.MOUSE_MSG:
onMouseDown(msg);
break;
default:
onSysEvent(msg);
}
}
//键盘事件
public static void onKeyDown(Message msg) {
System.out.println("键盘事件:");
System.out.println("type:" + msg.getType());
System.out.println("info:" + msg.getInfo());
}
//鼠标事件
public static void onMouseDown(Message msg) {
System.out.println("鼠标事件:");
System.out.println("type:" + msg.getType());
System.out.println("info:" + msg.getInfo());
}
//操作系统产生的消息
public static void onSysEvent(Message msg) {
System.out.println("系统事件:");
System.out.println("type:" + msg.getType());
System.out.println("info:" + msg.getInfo());
}
}
/**
* 消息模拟
* @author luoweifu
*/
public class MessageSimulator {
//消息队列
private static ArrayBlockingQueue<Message> messageQueue = new ArrayBlockingQueue<Message>(100);
public static void main(String[] args) {
WindowSimulator generator = new WindowSimulator(messageQueue);
//产生消息
generator.GenerateMsg();
//消息循环
Message msg = null;
while((msg = messageQueue.poll()) != null) {
((MessageProcess) msg.getSource()).doMessage(msg);
}
}
}
这里模拟用例中只有一个消息输入源,且是一种线程阻塞的,只有输入结束后才会进行消息的处理。真实的Windows操作系统中的消息机制会有多个消息输入源,且消息输入的同时也能进行消息的处理。