第12章 线程与消息处理
(教学录像:50分钟)
在程序开发时,对于一些比较耗时的操作,通常会为其开辟一个单独的线程来执行,以尽可能减少用户的等待时间。在Android中,默认情况下,所有的操作都在主线程中进行,主线程负责管理与UI相关的事件,而在用户自己创建的子线程中,不能对UI组件进行操作。因此,Android提供了消息处理传递机制来解决这一问题。本章将对Android中如何实现多线程以及如何通过线程和消息处理机制操作UI界面进行详细介绍。
通过阅读本章,您可以:
★ 掌握如何创建及开启线程
★ 掌握如何让线程休眠
★ 掌握如何中断线程
★ 了解循环者Looper
★ 掌握消息处理类Handler的应用
★ 掌握消息类Message的应用
12.1 实现多线程
教学录像:光盘\TM\lx\12\实现多线程.exe
在现实生活中,很多事情都是同时进行的,例如,我们可以一边看书,一边喝咖啡;而计算机则可以一边播放音乐,一边打印文档。对于这种可以同时进行的任务,可以用线程来表示,每个线程完成一个任务,并与其他线程同时执行,这种机制被称为多线程。下面就来介绍如何创建线程、开启线程、让线程休眠和中断线程。
12.1.1 创建线程
在Android中,提供了两种创建线程的方法:一种是通过Thread类的构造方法创建线程对象,并重写run()方法实现;另一种是通过实现Runnable接口实现,下面分别进行介绍。
- 通过Thread类的构造方法创建线程
在Android中,可以使用Thread类提供的以下构造方法来创建线程。
- Thread(Runnable runnable)
该构造方法的参数runnable可以通过创建一个Runnable类的对象并重写其run()方法来实现,例如,要创建一个名称为thread的线程,可以使用下面的代码:
- Thread thread=new Thread(new Runnable(){
- //重写run()方法
- @Override
- public void run() {
- //要执行的操作
- }
- });
说明:在run()方法中,可以编写要执行的操作的代码,当线程被开启时,run()方法将被执行。
- 通过实现Runnable接口创建线程
在Android中,还可以通过实现Runnable接口来创建线程。实现Runnable接口的语法格式如下:
- public class ClassName extends Object implements Runnable
当一个类实现Runnable接口后,还需要实现其run()方法,在run()方法中,可以编写要执行的操作的代码。
例如,要创建一个实现了Runnable接口的Activity,可以使用下面的代码:
- public class MainActivity extends Activity implements Runnable {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- @Override
- public void run() {
- //要执行的操作
- }
- }
12.1.2 开启线程
创建线程对象后,还需要开启线程,线程才能执行。Thread类提供了start()方法用于开启线程,其语法格式如下:
- start()
例如,存在一个名称为thread的线程,如果想开启该线程,可以使用下面的代码:
- thread.start(); //开启线程
12.1.3 线程的休眠
线程的休眠就是让线程暂停一段时间后再次执行。同Java一样,在Android中,也可以使用Thread类的sleep()方法让线程休眠指定的时间。sleep()方法的语法格式如下:
- sleep(long time)
其中参数time用于指定休眠的时间,单位为毫秒。
例如,想要线程休眠1秒钟,可以使用下面的代码:
- Thread.sleep(1000);
12.1.4 中断线程
当需要中断指定的线程时,可以使用Thread类提供的interrupt()方法来实现。使用interrupt()方法可以向指定的线程发送一个中断请求,并将该线程标记为中断状态。interrupt()方法的语法格式如下:
- interrupt()
例如,存在一个名称为thread的线程,如果想中断该线程,可以使用下面的代码:
- … //省略部分代码
- thread.interrupt();
- … //省略部分代码
- public void run() {
- while(!Thread.currentThread().isInterrupted()){
- … //省略部分代码
- }
- }
另外,由于当线程执行wait()、join()或sleep()方法时,线程的中断状态将被清除并抛出InterruptedException,所以,如果想在线程中执行了wait()、join()或sleep()方法时中断线程,就需要使用一个boolean型的标记变量来记录线程的中断状态,并通过该标记变量来控制循环的执行与停止。例如,通过名称为isInterrupt的boolean型变量来标记线程的中断,关键代码如下:
- private boolean isInterrupt=false; //定义标记变量
- … //省略部分代码
- … //在需要中断线程时,将isInterrupt的值设置为true
- public void run() {
while(!isInterrupt){- … //省略部分代码
- }
- }
12.1.5 范例1:通过实现Runnable接口来创建线程
例12.1 在Eclipse中创建Android项目,名称为12.1,通过实现Runnable接口来创建线程、开启线程和中断线程。(实例位置:光盘\TM\sl\12\12.1)
(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的TextView组件删除,然后在默认添加的线性布局管理器中添加两个按钮,一个用于开启线程,另一个用于中断线程,具体代码请参见光盘。
(2)打开默认添加的MainActivity,让该类实现Runnable接口,修改后的创建类的代码如下:
- public class MainActivity extends Activity implements Runnable{}
(3)实现Runnable接口中的run()方法,在该方法中,判断当前线程是否被中断,如果没有被中断,则将循环变量值加1,并在日志中输出循环变量的值,具体代码如下:
- @Override
- public void run() {
- while (!Thread.currentThread().isInterrupted()) {
- i++;
- Log.i("循环变量:", String.valueOf(i));
- }
- }
(4)在该MainActivity中,创建两个成员变量,具体代码如下:
- private Thread thread; //声明线程对象
- int i; //循环变量
(5)在onCreate()方法中,首先获取布局管理器中添加的“开始”按钮,然后为该按钮添加单击事件监听器,在重写的onCreate()方法中,根据当前Activity创建一个线程,并开启该线程,具体代码如下:
- Button startButton = (Button) findViewById(R.id.button1); //获取“开始”按钮
- startButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- i = 0;
- thread = new Thread(MainActivity.this); //创建一个线程
- thread.start(); //开启线程
- }
- });
(6)获取布局管理器中添加的“停止”按钮,并为其添加单击事件监听器,在重写的onCreate()方法中,如果thread对象不为空,则中断线程,并向日志中输出提示信息,具体代码如下:
- Button stopButton = (Button) findViewById(R.id.button2); //获取“停止”按钮
- stopButton.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- if (thread != null) {
- thread.interrupt(); //中断线程
- thread = null;
- }
- Log.i("提示:", "中断线程");
- }
- });
(7)重写MainActivity的onDestroy()方法,在该方法中中断线程,具体代码如下:
- @Override
- protected void onDestroy() {
- if (thread != null) {
- thread.interrupt(); //中断线程
- thread = null;
- }
- super.onDestroy();
- }
运行本实例,在屏幕上将显示一个“开始”按钮和一个“停止”按钮,单击“开始”按钮,将在日志面板中输出循环变量的值;单击“停止”按钮,将中断线程。日志面板的显示结果如图12.1所示。
图12.1 在日志面板中输出的内容
12.1.6 范例2:开启一个新线程播放背景音乐
例12.2 在Eclipse中创建Android项目,名称为12.2,开启一个新线程播放背景音乐,在音乐文件播放完毕后,暂停5秒钟后重新开始播放。(实例位置:光盘\TM\sl\12\12.2)
(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的TextView组件删除,然后在默认添加的线性布局管理器中添加一个“开始”按钮,用于开启线程并播放背景音乐,具体代码请参见光盘。
(2)在该MainActivity中,创建两个成员变量,具体代码如下:
- private Thread thread; //声明一个线程对象
- private static MediaPlayer mp = null; //声明一个MediaPlayer对象
(3)在onCreate()方法中,获取布局管理器中添加的“开始”按钮,并为该按钮添加单击事件监听器,在重写的onCreate()方法中,首先设置该按钮不可用,然后创建一个用于播放背景音乐的线程,并开启该线程,在重写的run()方法中,调用playBGSound()方法播放背景音乐,具体代码如下:
- Button button = (Button) findViewById(R.id.button1); //获取布局管理器中添加的“开始”按钮
- button.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- ((Button) v).setEnabled(false); //设置按钮不可用
- //创建一个用于播放背景音乐的线程
- thread = new Thread(new Runnable() {
- @Override
- public void run() {
- playBGSound(); //播放背景音乐
- }
- });
- thread.start(); //开启线程
- }
- });
(4)编写playBGSound()方法,首先判断MediaPlayer对象是否为空,如果不为空,则释放该对象,然后创建一个用于播放背景音乐的MediaPlayer对象,并开始播放,再为该MediaPlayer对象添加播放完成事件监听器,在重写的onCompletion()方法中,让线程休眠5秒钟,并调用playBGSound()方法重新播放音乐,具体代码如下:
- private void playBGSound() {
- if (mp != null) {
- mp.release(); //释放资源
- }
- mp = MediaPlayer.create(MainActivity.this, R.raw.jasmine);
- mp.start(); //开始播放
- //为MediaPlayer添加播放完成事件监听器
- mp.setOnCompletionListener(new OnCompletionListener() {
- @Override
- public void onCompletion(MediaPlayer mp) {
- try {
- Thread.sleep(5000); //线程休眠5秒钟
- playBGSound(); //重新播放音乐
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- });
- }
(5)重写MainActivity的onDestroy()方法,停止播放背景音乐并释放资源,具体代码如下:
- @Override
- protected void onDestroy() {
- if (mp != null) {
- mp.stop(); //停止播放
- mp.release(); //释放资源
- mp = null;
- }
- if (thread != null) {
- thread = null;
- }
- super.onDestroy();
- }
运行本实例,在屏幕上将显示一个“开始”按钮,单击该按钮,该按钮将变为不可用状态,并且开始播放背景音乐,如图12.2所示。
图12.2 程序运行效果
12.2 Handler消息传递机制
教学录像:光盘\TM\lx\12\Handler消息传递机制.exe
在12.1节中,已经介绍了在Android中如何创建、开启、休眠和中断线程。不过,此时并没有在新创建的子线程中对UI界面上的内容进行操作,如果应用前面介绍的方法对UI界面进行操作,将抛出异常。例如,在子线程的run()方法中循环修改文本框的显示文本,将抛出如图12.3所示的异常信息。
图12.3 抛出的异常信息
为此,Android中引入了Handler消息传递机制,来实现在新创建的线程中操作UI界面。下面将对Handler消息传递机制进行介绍。
12.2.1 循环者(Looper)简介
在介绍Looper之前,需要先来了解一下MessageQueue的概念。在Android中,一个线程对应一个Looper对象,而一个Looper对象又对应一个MessageQueue(消息队列)。MessageQueue用于存放Message(消息),在MessageQueue中,存放的消息按照FIFO(先进先出)原则执行,由于MessageQueue被封装到Looper里面,所以这里不对MessageQueue进行过多介绍。
Looper对象用来为一个线程开启一个消息循环,从而操作MessageQueue。默认情况下,Android中新创建的线程是没有开启消息循环的,但是主线程除外。系统自动为主线程创建Looper对象,开启消息循环。所以,当在主线程中应用下面的代码创建Handler对象时不会出错,而如果在新创建的非主线程中应用下面的代码创建Handler对象,将产生如图12.4所示的异常信息。
- Handler handler2 = new Handler();
如果想要在非主线程中创建Handler对象,首先需要使用Looper类的prepare()方法来初始化一个Looper对象,然后创建该Handler对象,再使用Looper类的loop()方法启动Looper,从消息队列中获取和处理消息。
图12.4 在非主线程中创建Handler对象产生的异常信息
例12.3 在Eclipse中创建Android项目,名称为12.3,创建一个继承Thread类的LooperThread,并在重写的run()方法中创建一个Handler对象,发送并处理消息。(实例位置:光盘\TM\sl\12\12.3)
(1)创建一个继承了Thread类的LooperThread,并在重写的run()方法中创建一个Handler对象,发送并处理消息,关键代码如下:
- public class LooperThread extends Thread {
- public Handler handler1; //声明一个Handler对象
- @Override
- public void run() {
- super.run();
- Looper.prepare(); //初始化Looper对象
- //实例化一个Handler对象
- handler1 = new Handler() {
- public void handleMessage(Message msg) {
- Log.i("Looper",String.valueOf(msg.what));
- }
- };
- Message m=handler1.obtainMessage(); //获取一个消息
- m.what=0x11; //设置Message的what属性的值
- handler1.sendMessage(m); //发送消息
- Looper.loop(); //启动Looper
- }
- }
(2)在MainActivity的onCreate()方法中,创建一个LooperThread线程,并开启该线程,关键代码如下:
- LooperThread thread=new LooperThread(); //创建一个线程
- thread.start(); //开启线程
运行本实例,在日志面板(LogCat)中输出如图12.5所示的内容。
图12.5 在日志面板(LogCat)中输出的内容
Looper类提供的常用方法如表12.1所示。
表12.1 Looper类提供的常用方法
方 法 | 描 述 |
prepare() | 用于初始化Looper |
loop() | 启动Looper线程,线程会从消息队列里获取和处理消息 |
myLooper() | 可以获取当前线程的Looper对象 |
getThread() | 用于获取Looper对象所属的线程 |
quit() | 用于结束Looper循环 |
注意:写在Looper.loop()之后的代码不会被执行,该函数内部是一个循环,当调用Handler. getLooper().quit()方法后,loop()方法才会中止,其后面的代码才能运行。
12.2.2 消息处理类(Handler)简介
消息处理类(Handler)允许发送和处理Message或Runnable对象到其所在线程的MessageQueue中。Handler主要有以下两个作用。
(1)将Message或Runnable应用post()或sendMessage()方法发送到MessageQueue中,在发送时可以指定延迟时间、发送时间及要携带的Bundle数据。当MessageQueue循环到该Message时,调用相应的Handler对象的handlerMessage()方法对其进行处理。
(2)在子线程中与主线程进行通信,也就是在工作线程中与UI线程进行通信。
说明:在一个线程中,只能有一个Looper和MessageQueue,但是可以有多个Handler,而且这些Handler可以共享同一个Looper和MessageQueue。
Handler类提供的发送和处理消息的常用方法如表12.2所示。
表12.2 Handler类提供的常用方法
方 法 | 描 述 |
handleMessage(Message msg) | 处理消息的方法。通常重写该方法来处理消息,在发送消息时,该方法会自动回调 |
post(Runnable r) | 立即发送Runnable对象,该Runnable对象最后将被封装成Message对象 |
postAtTime(Runnable r, long uptimeMillis) | 定时发送Runnable对象,该Runnable对象最后将被封装成Message对象 |
postDelayed(Runnable r, long delayMillis) | 延迟发送Runnable对象,该Runnable对象最后将被封装成Message对象 |
sendEmptyMessage(int what) | 发送空消息 |
sendMessage(Message msg) | 立即发送消息 |
sendMessageAtTime(Message msg, long uptimeMillis) | 定时发送消息 |
sendMessageDelayed(Message msg, long delayMillis) | 延迟发送消息 |
12.2.3 消息类(Message)简介
消息类(Message)被存放在MessageQueue中,一个MessageQueue中可以包含多个Message对象。每个Message对象可以通过Message.obtain()或Handler.obtainMessage()方法获得。一个Message对象具有如表12.3所示的5个属性。
表12.3 Message对象的属性
属 性 | 类 型 | 描 述 |
arg1 | int | 用来存放整型数据 |
arg2 | int | 用来存放整型数据 |
obj | Object | 用来存放发送给接收器的Object类型的任意对象 |
replyTo | Messenger | 用来指定此Message发送到何处的可选Messager对象 |
what | int | 用于指定用户自定义的消息代码,这样接收者可以了解这个消息的信息 |
说明:使用Message类的属性可以携带int型数据,如果要携带其他类型的数据,可以先将要携带的数据保存到Bundle对象中,然后通过Message类的setDate()方法将其添加到Message中。
总之,Message类的使用方法比较简单,在使用时,需注意以下3点:
[√]尽管Message有public的默认构造方法,但是通常情况下,需要使用Message.obtain()或Handler.obtainMessage()方法来从消息池中获得空消息对象,以节省资源。
[√]如果一个Message只需要携带简单的int型信息,应优先使用Message.arg1和Message.arg2属性来传递信息,这比用Bundle更节省内存。
[√]尽可能使用Message.what来标识信息,以便用不同方式处理Message。
12.2.4 范例1:开启新线程获取网络图片并显示到ImageView中
例12.4 在Eclipse中创建Android项目,名称为12.4,开启新线程获取网络图片并显示到ImageView中。(实例位置:光盘\TM\sl\12\12.4)
(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的TextView组件删除,然后在默认添加的线性布局管理器中添加一个ImageView组件,并且设置该组件默认显示的图片,关键代码如下:
- <ImageView
- android:id="@+id/imageView1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:padding="10dp"
- android:src="@drawable/hint"/>
(2)在该MainActivity中,声明一个代表ImageView组件的对象,具体代码如下:
- private ImageView iv; //声明ImageView组件的对象
(3)编写getPicture()方法,用于根据给定的网址从网络上获取图片,并根据获取到的图片创建一个Bitmap对象。getPicture()方法的具体代码如下:
- /**
- * 功能:根据网址获取图片对应的Bitmap对象
- * @param path
- * @return
- */
- public Bitmap getPicture(String path){
- Bitmap bm=null;
- try {
- URL url=new URL(path); //创建URL对象
- URLConnection conn=url.openConnection(); //获取URL对象对应的连接
- conn.connect(); //打开连接
- InputStream is=conn.getInputStream(); //获取输入流对象
- bm=BitmapFactory.decodeStream(is); //根据输入流对象创建Bitmap对象
- } catch (MalformedURLException e1) {
- e1.printStackTrace(); //输出异常信息
- } catch (IOException e) {
- e.printStackTrace(); //输出异常信息
- }
- return bm;
- }
(4)在onCreate()方法中,获取布局管理器中添加的ImageView组件,并创建和开启一个新线程,在创建线程时,需要重写它的run()方法,在重写的run()方法中调用getPicture()方法从网络上获取图片,然后让线程休眠2秒钟,再通过View组件的post()方法发送一个Runnable对象,修改ImageView中显示的图片,具体代码如下:
- iv = (ImageView) findViewById(R.id.imageView1); //获取布局管理器中添加的ImageView
- //创建一个新线程,用于从网络上获取图片
- new Thread(new Runnable() {
- public void run() {
- //从网络上获取图片
- final Bitmap bitmap=getPicture("http://192.168.1.66:8081/test/images/android.png");
- try {
- Thread.sleep(2000); //线程休眠2秒钟
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- //发送一个Runnable对象
- iv.post(new Runnable() {
- public void run() {
- iv.setImageBitmap(bitmap); //在ImageView中显示从网络上获取到的图片
- }
- });
- }
- }).start(); //开启线程
(5)由于在本实例中需要访问网络资源,所以还需要在AndroidManifest.xml文件中指定允许访问网格资源的权限,具体代码如下:
- <uses-permission android:name="android.permission.INTERNET"/>
运行本实例,首先显示如图12.6所示的默认图片,几秒钟后,将显示如图12.7所示的从网络中获取的图片。
![]() | ![]() |
图12.6 显示默认的图片 | 图12.7 显示网络图片 |
12.2.5 范例2:开启新线程实现电子广告牌
例12.5 在Eclipse中创建Android项目,名称为12.5,开启新线程实现电子广告牌。(实例位置:光盘\TM\sl\12\12.5)
(1)修改新建项目的res\layout目录下的布局文件main.xml,在默认添加的TextView组件上方添加一个ImageView组件,用于显示广告图片,并设置垂直线性布局管理器内的组件水平居中显示,具体代码请参见光盘。
(2)打开默认添加的MainActivity,让该类实现Runnable接口,修改后的创建类的代码如下:
- public class MainActivity extends Activity implements Runnable{}
(3)实现Runnable接口中的run()方法,在该方法中,判断当前线程是否被中断,如果没有被中断,则首先产生一个随机数,然后获取一个Message,并将要显示的广告图片的索引值和对应标题保存到该Message中,再发送消息,最后让线程休眠2秒钟,具体代码如下:
- @Override
- public void run() {
- int index = 0;
- while (!Thread.currentThread().isInterrupted()) {
- index = new Random().nextInt(path.length); //产生一个随机数
- Message m = handler.obtainMessage(); //获取一个Message
- m.arg1 = index; //保存要显示广告图片的索引值
- Bundle bundle = new Bundle(); //获取Bundle对象
- m.what = 0x101; //设置消息标识
- bundle.putString("title", title[index]); //保存标题
- m.setData(bundle); //将Bundle对象保存到Message中
- handler.sendMessage(m); //发送消息
- try {
- Thread.sleep(2000); //线程休眠2秒钟
- } catch (InterruptedException e) {
- e.printStackTrace(); //输出异常信息
- }
- }
- }
(4)在该MainActivity中,创建程序中所需的成员变量,具体代码如下:
- private ImageView iv; //声明一个显示广告图片的ImageView对象
- private Handler handler; //声明一个Handler对象
- private int[] path = new int[] { R.drawable.img01, R.drawable.img02,
- R.drawable.img03, R.drawable.img04, R.drawable.img05,
- R.drawable.img06 }; //保存广告图片的数组
- private String[] title = new String[] { "编程词典系列产品", "高效开发", "快乐分享", "用户人群",
- "快速学习", "全方位查询" }; //保存显示标题的数组
(5)在onCreate()方法中,首先获取布局管理器中添加的ImageView组件,然后创建一个新线程,并开启该线程,再实例化一个Handler对象,在重写的handleMessage()方法中,更新UI界面中的ImageView和TextView组件,具体代码如下:
- iv = (ImageView) findViewById(R.id.imageView1); //获取显示广告图片的ImageView
- Thread t = new Thread(this); //创建新线程
- t.start(); //开启线程
- //实例化一个Handler对象
- handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- //更新UI
- TextView tv = (TextView) findViewById(R.id.textView1); //获取TextView组件
- if (msg.what == 0x101) {
- tv.setText(msg.getData().getString("title")); //设置标题
- iv.setImageResource(path[msg.arg1]); //设置要显示的图片
- }
- super.handleMessage(msg);
- }
- };
运行本实例,在屏幕上将每隔两秒钟随机显示一张广告图片,如图12.8所示。
图12.8 电子广告牌
12.3 经典范例
12.3.1 多彩的霓虹灯
例12.6 在Eclipse中创建Android项目,名称为12.6,实现多彩霓虹灯。(实例位置:光盘\TM\sl\ 12\12.6)
(1)修改新建项目的res\layout目录下的布局文件main.xml,将默认添加的TextView组件删除,并为默认添加的线性布局管理器设置ID属性,具体代码请参见光盘。
(2)在res/values目录下,创建一个保存颜色资源的colors.xml文件,在该文件中,定义7个颜色资源,名称依次为color1、color2、…、color7,颜色值分别对应赤、橙、黄、绿、青、蓝、紫。colors.xml文件的关键代码如下:
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <color name="color1">#ffff0000</color>
- <color name="color2">#ffff6600</color>
- <color name="color3">#ffffff00</color>
- <color name="color4">#ff00ff00</color>
- <color name="color5">#ff00ffff</color>
- <color name="color6">#ff0000ff</color>
- <color name="color7">#ff6600ff</color>
- </resources>
(3)在该MainActivity中,声明程序中所需的成员变量,具体代码如下:
- private Handler handler; //创建Handler对象
- private static LinearLayout linearLayout; //整体布局
- public static TextView[] tv = new TextView[14]; //TextView数组
- int[] bgColor=new int[]{R.color.color1,R.color.color2,R.color.color3,
- R.color.color4,R.color.color5,R.color.color6,R.color.color7}; //使用颜色资源
- private int index=0; //当前颜色值
(4)在MainActivity的onCreate()方法中,首先获取线性布局管理器,然后获取屏幕的高度,接下来再通过一个for循环创建14个文本框组件,并添加到线性布局管理器中,具体代码如下:
- linearLayout=(LinearLayout)findViewById(R.id.ll); //获取线性布局管理器
- int height=this.getResources().getDisplayMetrics().heightPixels; //获取屏幕的高度
- for(int i=0;i<tv.length;i++){
- tv[i]=new TextView(this); //创建一个文本框对象
- tv[i].setWidth(this.getResources().getDisplayMetrics().widthPixels); //设置文本框的宽度
- tv[i].setHeight(height/tv.length); //设置文本框的高度
- linearLayout.addView(tv[i]); //将TextView组件添加到线性布局管理器中
- }
(5)创建并开启一个新线程,在重写的run()方法中实现一个循环,在该循环中,首先获取一个Message对象,并为其设置一个消息标识,然后发送消息,最后让线程休眠1秒钟,具体代码如下:
- Thread t = new Thread(new Runnable(){
- @Override
- public void run() {
- while (!Thread.currentThread().isInterrupted()) {
- Message m = handler.obtainMessage(); //获取一个Message
- m.what=0x101; //设置消息标识
- handler.sendMessage(m); //发送消息
- try {
- Thread.sleep(new Random().nextInt(1000)); //休眠1秒钟
- } catch (InterruptedException e) {
- e.printStackTrace(); //输出异常信息
- }
- }
- }
- });
- t.start(); //开启线程
(6)创建一个Handler对象,在重写的handleMessage()方法中,为每个文本框设置背景颜色,该背景颜色从颜色数组中随机获取,具体代码如下:
- handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- int temp=0; //临时变量
- if (msg.what == 0x101) {
- for(int i=0;i<tv.length;i++){
- temp=new Random().nextInt(bgColor.length); //产生一个随机数
- //去掉重复的并且相邻的颜色
- if(index==temp){
- temp++;
- if(temp==bgColor.length){
- temp=0;
- }
- }
- index=temp;
- //为文本框设置背景
- tv[i].setBackgroundColor(getResources().getColor(bgColor[index]));
- }
- }
- super.handleMessage(msg);
- }
- };
(7)在AndroidManifest.xml文件的<activity>标记中,设置android:theme属性,实现全屏显示,关键代码如下:
- android:theme="@android:style/Theme.Black.NoTitleBar"
运行本实例,将全屏显示一个多彩的霓虹灯,它可以不断地变换颜色,如图12.9所示。
图12.9 多彩的霓虹灯
12.3.2 简易打地鼠游戏
例12.7 在Eclipse中创建Android项目,名称为12.7,实现简易打地鼠游戏。(实例位置:光盘\TM\sl\12\12.7)
(1)修改新建项目的res\layout目录下的布局文件main.xml,首先将默认添加的布局管理器和TextView组件删除,然后添加一个帧布局管理器,最后在该布局管理器中添加一个用于显示地鼠的ImageView组件,并设置其显示一张地鼠图片,关键代码如下:
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/fl"
- android:background="@drawable/background"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <ImageView
- android:id="@+id/imageView1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:src="@drawable/mouse"/>
- </FrameLayout>
(2)在该MainActivity中,声明程序中所需的成员变量,具体代码如下:
- private int i = 0; //记录其打到了几只地鼠
- private ImageView mouse; //声明一个ImageView对象
- private Handler handler; //声明一个Handler对象
- public int[][] position = new int[][] { { 231, 325 }, { 424, 349 },
- { 521, 256 }, { 543, 296 }, { 719, 245 }, { 832, 292 },
- { 772, 358 } }; //创建一个表示地鼠位置的数组
(3)创建并开启一个新线程,在重写的run()方法中,创建一个记录地鼠位置的索引值的变量,并实现一个循环,在该循环中,首先生成一个随机数,并获取一个Message对象,然后将生成的随机数作为地鼠位置的索引值保存到Message对象中,再为该Message设置一个消息标识并发送消息,最后让线程休眠一段时间(该时间随机产生),具体代码如下:
- Thread t = new Thread(new Runnable() {
- @Override
- public void run() {
- int index = 0; //创建一个记录地鼠位置的索引值
- while (!Thread.currentThread().isInterrupted()) {
- index = new Random().nextInt(position.length); //产生一个随机数
- Message m = handler.obtainMessage(); //获取一个Message
- m.arg1 = index; //保存地鼠标位置的索引值
- m.what = 0x101; //设置消息标识
- handler.sendMessage(m); //发送消息
- try {
- Thread.sleep(new Random().nextInt(500) + 500); //休眠一段时间
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- });
- t.start(); //开启线程
(4)创建一个Handler对象,在重写的handleMessage()方法中,首先定义一个记录地鼠位置索引值的变量,然后使用if语句根据消息标识判断是否为指定的消息,如果是,则获取消息中保存的地鼠位置的索引值,并设置地鼠在指定位置显示,具体代码如下:
- handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- int index = 0;
- if (msg.what == 0x101) {
- index = msg.arg1; //获取位置索引值
- mouse.setX(position[index][0]); //设置X轴位置
- mouse.setY(position[index][1]); //设置Y轴位置
- mouse.setVisibility(View.VISIBLE); //设置地鼠显示
- }
- super.handleMessage(msg);
- }
- };
(5)获取布局管理器中添加的ImageView组件,并为该组件添加触摸监听器,在重写的onTouch()方法中,首先设置地鼠不显示,然后将i的值加1,再通过消息提示框显示打到了几只地鼠,具体代码如下:
- mouse = (ImageView) findViewById(R.id.imageView1); //获取ImageView对象
- mouse.setOnTouchListener(new OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- v.setVisibility(View.INVISIBLE); //设置地鼠不显示
- i++;
- Toast.makeText(MainActivity.this, "打到[ " + i + " ]只地鼠!",
- Toast.LENGTH_SHORT).show(); //显示消息提示框
- return false;
- }
- });
运行本实例,在屏幕上将随机显示地鼠,触摸地鼠后,该地鼠将不显示,同时在屏幕上通过消息提示框显示打到了几只地鼠,如图12.10所示。
图12.10 简易打地鼠游戏
12.4 小 结
本章主要介绍了在Android中如何实现多线程。由于在Android中,不能在子线程(也称为工作线程)中更新主线程(也称为UI线程)中的UI组件,因此Android引入了消息传递机制,通过使用Looper、Handler和Message就可以轻松实现多线程中更新UI界面的功能,这与Java中的多线程不同,希望读者能很好的理解,并能灵活应用。另外,多线程是游戏开发中非常重要的一项技术。
12.5 实践与练习
编写Android项目,实现在屏幕上来回移动的气球。(答案位置:光盘\TM\sl\12\12.8)
编写Android项目,实现颜色不断变化的文字。(答案位置:光盘\TM\sl\12\12.9)