1引言
Android一词本义指机器人,Google于2007年11月发布了以Android命名的开源移动设备综合平台,包括其基于Linux的操作系统、中间件和关键的手机应用。并且组建了开放手机联盟,其成员囊括了全球著名的各大手机生产
商和移动运营商。2008年8月,Google又发布了网上应用商店Android Market。任何一个开发者只需要借助Android发
布的SDK开发手机应用,即可把开发的应用在Android Market上销售。目前Android Market上已经有一万多的应用程序,大大丰富了Android手机用户的功能。一个完整的产业链已经形成。因此开源Android吸引了原来越多的开发人员加入进来。本文将跟读者一起学习Android的线程模型。
2 Android进程
在了解Android线程之间得先了解一下Android的进程。当一个程序第一次启动的时候,Android会启动一个LINUX进程和一个主线程。默认的情况下,所有该程序的组件都将在该进程和线程中运行。同时,Android会为每个应用程序分配一个单独的LINUX用户。Android会劲量保留一个正在运行进程,只在内存资源出现不足时,Android会参试停止一些进程从而释放足够的资源给其他新的进程使用,
也能保证用户正在访问的当前进程有足够的资源去及时的响应用户的事件。Android会 根据进程中运行的组件类别以及组件的状态来判断该进程的重要性,Android会 首先停止那些不重要的进程。按照重要性从高到低一共有五个级别:
l前台进程
前台进程是用户当前正在使用的进程。只有一些前台进程可以在任何时候都存在。他们是最后一个被结束的,当内存低到根本连他们都不能运行的时候。一般来说, 在这种情况下,设备会进行内存调度,中止一些前台进程来保持对用户交互的响应。
l可见进程
可见进程不包含前台的组件但是会在屏幕上显示一个可见的进程是的重要程度很高,除非前台进程需要获取它的资源,不然不会被中止。
l服务进程
运行着一个通过startService() 方法启动的service,这个service不属于上面提到的2种更高重要性的。service所在的进程虽然对用户不是直接可见的,但是他们执行了用户非常关注的任务(比如播放mp3,从网络下载数据)。只要前台进程和可见进程有足够的内存,系统不会
回收他们。
l后台进程
运行着一个对用户不可见的activity(调用过on
程需要内存的时候回收。通常,系统中会有很多不可见进程在运行,他们被保存在LRU (least recently used) 列表中,以便内存不足的时候被第一时间回收。如果一个activity正 确的执行了它的生命周期,关闭这个进程对于用户体验没有太大的影响。
l空进程
未运行任何程序组件。运行这些进程的唯一原因是作为一个缓存,缩短下次程序需要重新使用的启动时间。系统经常中止这些进程,这样可以调节程序缓存和系统缓 存的平衡。
Android
对进程的重要性评级的时候,选取它最高的级别。另外,当被另外的一个进程依赖的时候,某个进程的级别可能会增高。一个为其他进程服务的进程永远不会比被服 务的进程重要级低。因为服务进程比后台activity进程重 要级高,因此一个要进行耗时工作的activity最好启动一 个service来做这个工作,而不是开启一个子进程――特别
是这个操作需要的时间比activity存在的时间还要长的时 候。例如,在后台播放音乐,向网上上传摄像头拍到的图片,使用service可 以使进程最少获取到“服务进程”级别的重要级,而不用考虑activity目 前是什么状态。broadcast
receivers做费时的工作的时候,也应该启用一个服务而不是开一个线程。
2单线程模型
当一个程序第一次启动时,Android会同时启动一个对应的 主线程(Main Thread),主线程主要负责处理与UI相关的事件,如:用户的按键事件,用户接触屏幕的事件以及屏幕绘图事
件,并把相关的事件分发到对应的组件进行处理。所以主线程通常又被叫做UI线 程。在开发Android应用时必须遵守单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行。
2.1案例演示
如果在没有理解这样的单线程模型的情况下,设计的程序可能会使程序性能低下,因为所有的动作都在同一个线 程中触发。例如当主线程正在做一些比较耗时的操作的时候,如正从网络上下载一个大图片,或者访问数据库,由于主线程被这些耗时的操作阻塞住,无法及时的响
应用户的事件,从用户的角度看会觉得程序已经死掉。如果程序长时间不响应,用户还可能得重启系统。为了避免这样的情况,Android设 置了一个5秒 的超时时间,一旦用户的事件由于主线程阻塞而超过5秒 钟没有响应,Android会
弹出一个应用程序没有响应的对话框。下面将通过一个案例来演示这种情况:
本程序将设计和实现查看指定城市的当天天气情况的功能,
1. 首先,需要选择一个天气查询的
服务接口,目前可供选择的接口很多,诸如YAHOO的 天气API和Google提 供的天气API。 本文将选择GOOGLE
的 天气查询API。 该接口提供了多种查询方式,可以通过指定具体城市的经纬度进行查询,也可以通过城市名称进行查询。
2. 用户在输入框内输入需要查询的
城市名称,然后点击查询按钮
3. 当用户点击查询按钮后,使用已
经内置在Android SDK中的HttpClient API来调用GOOGLE的 天气查询API, 然后解析返回的指定城市的天气信息,并把该天气信息显示在Title上
主要代码如下:
publicclass WeatherReportextends
Activityimplements On
privatestaticfinal
StringGOOGLE_API_URL ="http://www.google.com/ig/api?weather=";
privatestaticfinal
StringNETWORK_ERROR ="网络异常";
private EditTexteditText;
@Override
publicvoid
on
super.on
setContentView(R.layout.main);
editText = (EditText) findViewById(R.id.weather_city_edit);
Button button = (Button) findViewById(R.id.goQuery);
button.setOnClickListener(this);
}
@Override
publicvoid
on
//获得用户输入的城市名称
String city =
editText.getText().toString();
//调用Google天气API查询指定城市的当日天气
情况
String weather = getWetherByCity(city);
//把天气信息显示在title上
setTitle(weather);
}
public String getWetherByCity(String
city) {
HttpClient httpClient =
new DefaultHttpClient();
HttpContext localContext =
new BasicHttpContext();
HttpGet httpGet =
new HttpGet(GOOGLE_API_URL +
city);
try {
HttpResponse response = httpClient.execute(httpGet, localContext);
if (response.getStatusLine().getStatusCode()
!= HttpStatus.SC_OK) {
httpGet.abort();
}
else {
HttpEntity httpEntity = response.getEntity();
return
parseWeather(httpEntity.getContent());
}
}
catch (Exception e) {
Log.e("WeatherReport","Failed
to get weather", e);
}
finally {
httpClient.getConnectionManager().shutdown();
}
returnNETWORK_ERROR;
}
}
当用户输入城市名称,然后单击按钮进行查询后,程序会调用Google API的接口获得指定城市的当日天气情况。由于需要访问网络,所以当网络出现异常或者服务繁忙的时候都会使访问网络的动作很耗时。本文为了
要演示超时的现象,只需要制造一种网络异常的状况,最简单的方式就是断开网络连接,然后启动该程序,同时触发一个用户事件,比如按一下MENU键, 由于主线程因为网络异常而被长时间阻塞,所以用户的按键事件在5秒 钟内得不到响应,Android会 提示一个程序无法响应的异常
2.1子线程更新UI
显然如果你的程序需要执行耗时的操作的话,如果像上例一样由主线程来负责执行 该操作是错误的。所以我们需要在on
法中创建一个新的子线程来负责调用GOOGLE API来获得天气数据。刚接触Android的 开发者最容易想到的方式就是如下:
publicvoid
on
//创建一个子线程执行耗时的从网络上获取天气信息的操作
new Thread() {
@Override
publicvoid
run() {
//获得用户输入的城市名称
String city =
editText.getText().toString();
//调用Google天气API查询指定城市的当日天气
情况
String weather = getWetherByCity(city);
//把天气信息显示在title上
setTitle(weather);
}
}.start();
}
但是很不幸,你会发 现Android会 提示程序由于异常而终止。为什么在其他平台上看起来很简单的代码在Android上运行的时候依然会出错呢?如果你观察LogCat中打印的日志信息就会发现这样的错误日志:
android.view.ViewRoot$CalledFromWrongThreadException: On
从错误信息不难看出Android禁 止其他子线程来更新由UI thread创建的试图。本例中显示天气信息的title实际是就是一个由UI
thread所创建的TextView,所以参试在一个子线程中去更改TextView的时候就出错了。这显示违背了单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线
程中执行
2.2 Message Queue
在单线程模型下,为 了解决类似的问题,Android设 计了一个Message Queue(消息队列), 线程间可以通过该Message
Queue并结合Handler和Looper组 件进行信息交换。下面将对它们进行分别介绍:
l Message Queue
Message Queue是一个消息队列,用来存放通过Handler发 布的消息。消息队列通常附属于某一个创建它的线程,可以通过Looper.myQueue()得 到当前线程的消息队列。Android在
第一启动程序时会默认会为UI thread创建一个关联的消息队列,用来管理程序的一些上层组件,activities,broadcast receivers等等。你可以在自己的子线程中创建Handler与UI
thread通讯。
l Handler
通过Handler你 可以发布或者处理一个消息或者是一个Runnable的 实例。没个Handler都 会与唯一的一个线程以及该线程的消息队列管理。当你创建一个新的Handler时候,默认情况下,它将关联到创建它的这个线程和该线程的消息队列。也就是说,如果你通过Handler发
布消息的话,消息将只会发送到与它关联的这个消息队列,当然也只能处理该消息队列中的消息。
主要的方法有:
1) public final boolean sendMessage(Message
msg)
把消息放入该Handler所 关联的消息队列,放置在所有当前时间前未被处理的消息后。
2) public void handleMessage(Message msg)
关联该消息队列的线 程将通过调用Handler的handleMessage方 法来接收和处理消息,通常需要子类化Handler来 实现handleMessage。
l Looper
Looper扮演着一个Handler和 消息队列之间通讯桥梁的角色。程序组件首先通过Handler把 消息传递给Looper,Looper把
消息放入队列。Looper也 把消息队列里的消息广播给所有的Handler,Handler接 受到消息后调用handleMessage进 行处理。
1) 可以通过Looper类
的静态方法Looper.myLooper得 到当前线程的Looper实 例,如果当前线程未关联一个Looper实 例,该方法将返回空。
2) 可以通过静态方法Looper.getMainLooper方法得到主线程的Looper实
例
线程,消息队列,Handler,Looper之 间的关系可以通过一个图来展示:
在了解了消息队列及 其相关组件的设计思想后,我们将把天气预报的案例通过消息队列来重新实现:
在了解了消息队列及其相关组件的设计思想后,我们将把天气预报的案例通过消息队列来重新实现:
privateEditText
editText;
privateHandler
messageHandler;
@Override
publicvoid
on
super.on
setContentView(R.layout.main);
editText
= (EditText) findViewById(R.id.weather_city_edit);
Button button = (Button) findViewById(R.id.goQuery);
button.setOnClickListener(this);
//得到当前线程
的Looper实例,由于 当前线程是UI线程也可以
通过Looper.getMainLooper()得到
Looper looper = Looper.myLooper();
//此处甚至可以
不需要设置Looper,因为Handler默认就使用当
前线程的Looper
messageHandler
= newMessageHandler(looper);
}
@Override
publicvoid
on
//创建一个子线
程去做耗时的网络连接工作
newThread()
{
@Override
publicvoid
run() {
//活动用户输入
的城市名称
String city =
editText.getText().toString();
//调用Google天气API查询指定城
市的当日天气情况
String weather = getWetherByCity(city);
//创建一个Message对象,并把得
到的天气信息赋值给Message对象
Message message = Message.obtain();
message.obj
= weather;
//通过Handler发布携带有天
气情况的消息
messageHandler.sendMessage(message);
}
}.start();
}