二、Android消息处理-应用参考篇
1.概述
本文收集了常见的消息处理方式。
2.要点
A。子线程更新UI的常错
B。Handler应用实例
C。AsyncTask应用实例
3. 正文
3.1 子线程更新UI(一个初学者常犯的错误)
如果你的程序需要执行耗时的操作的话,需要在onClick方 法中创建一个新的子线程来负责调用GOOGLE API来获得天气数据。刚接触Android的开发者最容易想到的方式就是如下:
public void onClick(View v) {
//创建一个子线程执行耗时的从网络上获取天气信息的操作
new Thread() {
@Override
public void run() {
//获得用户输入的城市名称
String city = editText.getText().toString();
//调用Google 天气API查询指定城市的当日天气情况
String weather =getWetherByCity(city);
//把天气信息显示在title上
setTitle(weather);
}
}.start();
}
但是很不幸,你会发 现Android会 提示程序由于异常而终止。为什么在其他平台上看起来很简单的代码在Android上运行的时候依然会出错呢?如果你观察LogCat中打印的日志信息就会发现这样的错误日志:
android.view.ViewRoot$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.
从错误信息不难看出Android禁 止其他子线程来更新由UI thread创建的试图。本例中显示天气信息的title实际是就是一个由UI thread所创建的TextView,所以参试在一个子线程中去更改TextView的时候就出错了。这显示违背了单线程模型的原则:Android UI操作并不是线程安全的并且这些操作必须在UI线 程中执行。
如何更改?最简方式:使用Handler,向主线程发送Message,并在主线程中处理UI更新。也可参考下例。
3.2 主线程和其他子线程如何交互
在网上有很多文章讲述主线程和其他子线程如何交互,传送信息,最终谁来执行处理信息之类的,个人理解是最简单的方法——判断Handler对象里面的Looper对象是属于哪条线程的,则由该线程来执行!
1. 当Handler对象的构造函数的参数为空,则为当前所在线程的Looper;
2. Looper.getMainLooper()得到的是主线程的Looper对象,Looper.myLooper()得到的是当前线程的Looper对象。
现在来看一个例子,模拟从网络获取数据,加载到ListView的过程:
1. public class ListProgressDemo extends ListActivity {
2.
3. @Override
4. public void onCreate(Bundle savedInstanceState) {
5. super.onCreate(savedInstanceState);
6. setContentView(R.layout.listprogress);
7.
8. ((Button) findViewById(R.id.load_Handler)).setOnClickListener(new View.OnClickListener(){
9. @Override
10. public void onClick(View view) {
11. data = null;
12. data = new ArrayList<String>();
13. adapter = null;
14. showDialog(PROGRESS_DIALOG);
15. new ProgressThread(handler, data).start();
16. }
17. });
18. }
19.
20. @Override
21. protected Dialog onCreateDialog(int id) {
22. switch(id) {
23. case PROGRESS_DIALOG:
24. return ProgressDialog.show(this, "", "Loading. Please wait...", true);
25. default: return null;
26. }
27. }
28.
29. private class ProgressThread extends Thread {
30. private Handler handler;
31. private ArrayList<String> data;
32. public ProgressThread(Handler handler, ArrayList<String> data) {
33. this.handler = handler;
34. this.data = data;
35. }
36.
37. @Override
38. public void run() {
39. for (int i=0; i<8; i++) {
40. data.add("ListItem"); //后台数据处理
41. try {
42. Thread.sleep(100);
43. }catch(InterruptedException e) {
44. Message msg = handler.obtainMessage();
45. Bundle b = new Bundle();
46. b.putInt("state", STATE_ERROR);
47. msg.setData(b);
48. handler.sendMessage(msg);
49. }
50. }
51. Message msg = handler.obtainMessage();
52. Bundle b = new Bundle();
53. b.putInt("state", STATE_FINISH);
54. msg.setData(b);
55. handler.sendMessage(msg);
56. }
57. }
58.
59. // 此处甚至可以不需要设置Looper,因为Handler默认就使用当前线程的Looper
60. private final Handler handler = new Handler(Looper.getMainLooper()) {
61. public void handleMessage(Message msg) { // 处理Message,更新ListView
62. int state = msg.getData().getInt("state");
63. switch(state){
64. case STATE_FINISH:
65. dismissDialog(PROGRESS_DIALOG);
66. Toast.makeText(getApplicationContext(), "加载完成!", Toast.LENGTH_LONG) .show();
67. adapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1,
68. data );
69. setListAdapter(adapter);
70. break;
71.
72. case STATE_ERROR:
73. dismissDialog(PROGRESS_DIALOG);
74. Toast.makeText(getApplicationContext(), "处理过程发生错误!", Toast.LENGTH_LONG).show();
75. adapter = new ArrayAdapter<String>(getApplicationContext(),
76. android.R.layout.simple_list_item_1, data );
77. setListAdapter(adapter);
78. break;
79. default:
80. }
81. }
82. };
83.
84. private ArrayAdapter<String> adapter;
85. private ArrayList<String> data;
86. private static final int PROGRESS_DIALOG = 1;
87. private static final int STATE_FINISH = 1;
88. private static final int STATE_ERROR = -1;
89. }
这个例子,我自己写完后觉得还是有点乱,要稍微整理才能看明白线程间交互的过程以及数据的前后变化。随后了解到AsyncTask类,相应修改后就很容易明白了!
3.3 使用AsyncTask异步加载
1. ((Button) findViewById(R.id.load_AsyncTask)).setOnClickListener(new View.OnClickListener(){
2.
3. @Override
4. public void onClick(View view) {
5. data = null;
6. data = new ArrayList<String>();
7.
8. adapter = null;
9.
10. //显示ProgressDialog放到AsyncTask.onPreExecute()里
11. //showDialog(PROGRESS_DIALOG);
12. new ProgressTask().execute(data);
13. }
14. });
15.
16. private class ProgressTask extends AsyncTask<ArrayList<String>, Void, Integer> {
17.
18. /* 该方法将在执行实际的后台操作前被UI thread调用。可以在该方法中做一些准备工作,如在界面上显示一个进度条。*/
19. @Override
20. protected void onPreExecute() {
21. // 先显示ProgressDialog
22. showDialog(PROGRESS_DIALOG);
23. }
24.
25. /* 执行那些很耗时的后台计算工作。可以调用publishProgress方法来更新实时的任务进度。 */
26. @Override
27. protected Integer doInBackground(ArrayList<String>... datas) {
28. ArrayList<String> data = datas[0];
29. for (int i=0; i<8; i++) {
30. data.add("ListItem");
31. }
32. return STATE_FINISH;
33. }
34.
35. /* 在doInBackground 执行完成后,onPostExecute 方法将被UI thread调用,
36. * 后台的计算结果将通过该方法传递到UI thread.
37. */
38. @Override
39. protected void onPostExecute(Integer result) {
40. int state = result.intValue();
41. switch(state){
42. case STATE_FINISH:
43. dismissDialog(PROGRESS_DIALOG);
44. Toast.makeText(getApplicationContext(), "加载完成!", Toast.LENGTH_LONG).show();
45.
46. adapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, data );
47. setListAdapter(adapter);
48. break;
49.
50. case STATE_ERROR:
51. dismissDialog(PROGRESS_DIALOG);
52. Toast.makeText(getApplicationContext(),"处理过程发生错误!", Toast.LENGTH_LONG).show();
53. adapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, data );
54. setListAdapter(adapter);
55. break;
56.
57. default:
58.
59. }
60. }
Android另外提供了一个工具类:AsyncTask。它使得UI thread的使用变得异常简单。它使创建需要与用户界面交互的长时间运行的任务变得更简单,不需要借助线程和Handler即可实现。
1) 子类化AsyncTask
2) 实现AsyncTask中定义的下面一个或几个方法
onPreExecute() 开始执行前的准备工作;
doInBackground(Params...)开始执行后台处理,可以调用publishProgress方法来更新实时的任务进度;
onProgressUpdate(Progress...) 在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况,例如通过一个进度条进行展示。
onPostExecute(Result) 执行完成后的操作,传送结果给UI 线程。
这4个方法都不能手动调用。而且除了doInBackground(Params...)方法,其余3个方法都是被UI线程所调用的,所以要求:
1) AsyncTask的实例必须在UI thread中创建;
2) AsyncTask.execute方法必须在UI thread中调用;
同时要注意:该task只能被执行一次,否则多次调用时将会出现异常。而且是不能手动停止的,这一点要注意,看是否符合你的需求!待测
在使用过程中,发现AsyncTask的构造函数的参数设置需要看明白:AsyncTask<Params, Progress, Result>
Params对应doInBackground(Params...)的参数类型。而new AsyncTask().execute(Params... params),就是传进来的Params数据,你可以execute(data)来传送一个数据,或者execute(data1, data2, data3)这样多个数据。
Progress对应onProgressUpdate(Progress...)的参数类型;
Result对应onPostExecute(Result)的参数类型。
当以上的参数类型都不需要指明某个时,则使用Void,注意不是void。不明白的可以参考上面的例子,或者API Doc里面的例子。
[以上转自:http://android.blog.51cto.com/268543/343823]
4. 结语
Handler部分,还可以以Bundle形式,携带很多数据,此例待扩;
AnyscTask部分,为什么不能多次调用?第三个方法也有待尝试使用。