Android消息处理-应用参考篇

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

你可能感兴趣的:(Android消息处理-应用参考篇)