二、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部分,为什么不能多次调用?第三个方法也有待尝试使用。