前言
积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得;
Handler消息传递机制
出于性能优化考虑,Android的UI操作并不是线程安全的,这意义着如果有多个线程并发操作UI组件,则可能导致线程安全问题。为了解决这个问题,Android制定了一条简单的规则:只允许UI线程修改Activity里的UI组件。在实际开发中,需要让新启动的线程周期性地改变界面组件的属性值,这就需要借助于Handler的消息传递机制来实现了。
Handler类简介
Handler类的主要作用有两个。
- 在新启动的线程中发送消息。
- 在主线程中获取、处理消息。
Handler类的功能看似简单,似乎只要在新启动的线程中发送消息,在主线程中获取、处理消息。但这个过程涉及两个问题:新启动的线程何时发送消息呢?主线程何时去获取并处理消息呢?
为了让主线程能在适当的时机处理新启动的线程所发送的消息,显然只能通过回调的方式来实现——需要重写Handler类中处理消息的方法。
Handler类包含如下方法用于发送、处理消息。
- void handleMessage(Message msg):处理消息的方法。通常被重写。
- final boolean hasMessages(int what):检查消息队列是否包含what属性为指定值的消息。
- final boolean hasMessages(int what, Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
- sendEmptyMessage(int what):发送空消息。
- final boolean sendEmptyMessageDelayed(int what,long delayMillis):指定多少秒后发送空消息。
- final boolean sendMessage(Message msg):立即发送消息。
- final boolean sendMessageDelayed(Message msg,long delayMillis)
本实例将通过一个新线程来周期性地修改ImageView所显示的图片,自动播放图片还可以使用ViewFlipper和AdapterViewFlipper组件来实现。
代码示例
MainActivity.java
public class MainActivity extends Activity {
// 定义周期性显示的图片ID
int[] imageIds = new int[]
{
R.drawable.baxianhua,
R.drawable.dengta,
R.drawable.juhua,
R.drawable.kaola,
R.drawable.qie,
R.drawable.shamo,
R.drawable.shuimo,
R.drawable.yujinx
};
int currentImageId = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.handler);
final ImageView show = (ImageView) findViewById(R.id.image);
final Handler myHandler = new Handler()
{
@Override
public void handleMessage(Message msg) {
if(msg.what == 0x123)
{
//动态地修改所显示的图片
show.setImageResource(imageIds[currentImageId++ % imageIds.length]);
}
}
};
//定义一个计时器,让该计时器周期性地执行指定任务
new Timer().schedule(new TimerTask() {
@Override
public void run() {
//发送空消息
myHandler.sendEmptyMessage(0x123);
}
}, 0, 1200);//1.2秒更改一次
}
}
效果
Screenshot_20171026-112831.pngHandler、Loop、MessageQueue的工作原理
为了更好地理解Handler的工作原理,下面介绍一下与Handler一起工作的几个组件。
-
Message:Handler接收和处理的消息对象。
-
Looper:每个线程只能拥有一个Looper。它的loop()方法负责读取MessageQueue中的消息,读到信息之后就把消息交给发送该消息的Handler进行处理。
-
MessageQueue:消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时,会在它的构造器中创建MessageQueue对象。
Handler的作用有两个——发送消息和处理消息,程序使用Handler发送消息,由Handler发送的消息必须被送到指定的MessageQueue。也就是说当前线程必须要有一个MessageQueue,不过MessageQueue是由Looper负责管理的。如果希望Handler正常工作,必须在当前线程中有一个Looper对象。为了保证当前线程中有Looper对象,可以分如下两种情况处理。
-
主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息了。
-
程序员自己启动的子线程,必须自己创建一个Looper对象,并启动它。创建Looper对象调用它的prepare()方法,调用loop()方法来启动它。
在线程中使用Handler的步骤如下。
-
调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。
-
有了Looper之后,创建Handler子类的实例,重写handleMessage()方法,该方法负责处理来自于其他线程的消息。
-
调用Looper的loop()方法启动Looper。
接下来看一段程序,该程序的功能是允许用户输入一个数值上限,当用户单击“计算”按钮时,该应用会将该上限数值发送到新启动的线程中,让该线程来计算该范围内的所有质数。
代码示例
public class MainActivity extends Activity {
static final String UPPER_NUM = "upper";
EditText et;
CalThread calThread;
// 定义一个线程类
class CalThread extends Thread {
public Handler handler;
public void run() {
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
int upper = msg.getData().getInt(UPPER_NUM);
List<Integer> nums = new ArrayList<Integer>();
//计算从2开始、到upper的所有质数
outer:
for (int i = 2; i <= upper; i++) {
for(int j = 2;j <= Math.sqrt(i); j++) {
if(i % j == 0)
{
continue outer;
}
}
nums.add(i);
}
//使用Toast显示统计出来的所有质数
Toast.makeText(MainActivity.this, nums.toString(), Toast.LENGTH_LONG).show();
}
}
};
Looper.loop();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.handlerdemo1);
et = (EditText) findViewById(R.id.et);
calThread = new CalThread();
calThread.start();
}
public void computer(View v)
{
//创建消息
Message msg = new Message();
msg.what = 0x123;
Bundle bundle = new Bundle();
bundle.putInt(UPPER_NUM, Integer.parseInt(et.getText().toString()));
msg.setData(bundle);
//向新线程中的Handler发送消息
calThread.handler.sendMessage(msg);
}
}
网友评论