1问题的引出
如果需要用手机从网络上下载一个图片到自己的手机上,在onCreate方法中可以如下图1所示的代码。通过代码会获取并显示所需要的位图。但其中隐藏着一个非常危险的问题——代码本身就是运行在主线程上。Android系统只能运行一个主线程,而且只有主线程能够与屏幕之间进行任意权限级别的交互。这意味着,当代码等待网络回传图片数据的时候,任何内容都无法显示到屏幕上。这段获取图片的代码会限制在手机上任意地方执行任意操作。所以如果主线程被绑架,那么它在被释放之前,系统就不会响应按单击事件,不能接电话,屏幕上不能显示任何内容。这对于手机用户来说是灾难性的。
2什么是子线程
为了有效解决手机被绑架的问题,提高用户体验,提出了子线程。在Android的布局界面中,可以实现按钮的单击、TextView内容的修改,所有这些与界面控件相关的操作,实际上都是由主UI线程在负责运行。但有时候,程序会执行一些耗时的操作,比如复杂的计算、访问网络,下载数据,查询数据库、甚至包括让线程休眠等,这些操作如果放到主UI线程中执行,会造成主UI线程无法及时响应用户在界面上的操作,造成界面假死的状态。一般的解决方案是将耗时的操作交给另外一个子线程来执行,从而保证主UI线程的畅通。有时候子线程在完成了一部分的耗时操作以后,希望能够在主界面上有所体现,此时子线程是不能够直接操作界面控件的,它必须通过消息的方式告知主UI线程进行控件更新。
3子线程与主UI线程的通信原理
Android应用程序运行时,一个主线程被创建(也称作UI线程),此线程主要负责处理UI相关的事件,由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作,如果在非UI线程直接对UI进行了操作,则会报错,另外,对于运算量较大的操作和IO操作,我们需要新开线程来处理这些工作,以免阻塞UI线程,子线程与主线程之间通信原理如下图2所示。
从上图2可以看出,要完成子线程与主UI线程之间的通信,需要配合多个类来完成。其中Thread类负责线程工作,要创建该类需要实现Runnable接口的run()方法,run()方法中一般是耗时操作的代码。第二个类是Message类,用来描述消息,在Message对象中可以存储一些信息。第三个类是Handler类,用来发送和接受消息,要创建该类需要实现handleMessage(Messagemsg)方法,该方法会在Handler对象接收到消息时被调用。那么这三个类之间协同合作,才能有序地完成子线程与主UI线程之间的通信。
3.1Thread类
要创建子线程对象,就需要使用Thread类。要创建该类需要实现Runnable接口的抽象方法run(),在run()方法中需要添加子线程所要运行任务的代码。其次,Sleep(longtime)方法是让线程休眠,其中time为休眠的时间,单位时毫秒。最后一定要用start()方法运行线程。之后该子线程就可以开始工作了。
子线程在特定情况下需要通过Handler发送Message给主UI线程,委托主UI线程进行一些与界面相关的处理,所以,发送消息的Handler类也是其中的一个很重要的类。
3.2Message类
Message类型的消息中包含了一个int类型的成员对象what,利用what可以区分不同的消息类型。返回值有两种情况,如果消息成功放置到消息队列则返回true,否则返回false。
3.3Handler类
发送消息:所使用的类为Handler类,方法为publicbooleansendMessage(Messagemsg),其中,msg是消息对象。Message类型的消息中包含了一个int类型的成员对象what,利用what可以区分不同的消息类型。子线程将消息发送给主线程,主线程收到消息之后,会根据传过来的消息信号的不同做不同的动作。如果消息号为STOP,则子线程结束消息的传送,如果消息号为CONTINUE,则继续传送消息。当子线程的状态发生变化,则在子线程中发出Message,通知更新UI。
接收处理消息:使用的是Handler类,方法为publicvoidhan
dleMessage(Messagemsg),通过判断msg.what可以区分不同的消息类型。根据不同的消息类型,主UI线程会产生不同的动作来响应子线程。
4子线程与主线程之间通信的实例
该程序的界面如图3所示,界面中存在一个水平进度条和一个大环形进度条,以及一个TextView来显示“界面加载中。。。”。运行中:水平滚动条每1秒前进一格,当进度条的进度达到100%时,TextView显示“页面加载完毕”。
分析:要完成这样的任务,需要让程序循环性地休眠1s后,更新水平进度条。但是如果让主程序休眠,会导致界面假死状态,造成用户使用感受的下降,如何解决?就用到了上面所提到的线程。实现过程代码如下图4所示。
5需要改善的几个问题
(1)设置线程的中断标记,有效地控制线程的进度。在Eclipse中创建Android项目,通过实现Runnable接口来创建线程、开启线程、让线程休眠指定的时间,除此之外,还可以中断线程。当需要中断指定线程时,可以使用Thread类提供的interrupt()方法来实现。使用interrupt()方法可以向指定的线程发送一个中断请求,此时可以使用一个boolean型的标记变量来记录该线程的中断状态,并通过该标记变量来控制循环的执行与停止。
(2)子线程开启消息循环。在Andorid中一个线程对应一个Looper对象,而一个Looper对象对应一个MessageQueue消息队列,消息队列里面用于存放消息。Looper对象用来为一个线程开启一个消息循环,用来操作消息队列。默认情况下,系统自动为主线程开启消息循环,新建子线程中则需要手动开启,否则会抛出异常。
6结语
Android的应用程序的多线程编程为我们充分利用系统资源提供了便利,同时也为设计复杂UI和耗时操作提供了途径,提升了手机用户的使用体验。在子线程与UI主线程通信过程中,用到了try{}catch{Exceptione}{}语句,这个语法是代码异常处理的,如果没有try的话,出现异常会导致程序崩溃。而try则可以保证程序的正常运行下去,并且输出为什么出错,对程序的维护有很大的帮助。
另外在主线程上应该避免下列操作:与网络相关的操作;需要对文件系统进行读写操作的任务;任何种类的繁重事务处理(如图片或视频修改);在等待某个事务完成时会阻碍线程执行的任务等等。因此,作为一般规律,如果不涉及对用户界面的设置或修改,就不要放在主线程上。
参考文献
[1]余永佳,赵佩华,等.Android应用开发基础[M].北京:机械工业出版社,2014.
[2]盖索林.Android开发入门指南(第二版)[M].北京:人民邮电出版社,2013.
来源:数字技术与应用 2016年9期
作者:孙翠改