Android中Activity、Service和線程之間的通信

原文地址:http://blog.csdn.net/wy819/article/details/51454769

Activity、Service和線程應該是Android編程中最常見的幾種類了,幾乎大多數應用程序都會涉及到這幾個類的編程,自然而然的,也就會涉及到三者之間的相互通信,本文就試圖簡單地介紹一下這三者通信的方式。
想寫這篇文章的起因是,筆者跟幾個同學在做一個Android上的應用,起初代碼寫得很凌亂,因爲我在Activity中直接創建了線程,去執行某些任務。但是我們知道線程可能需要運行的時間比較長,而Android在內存不足的時候,會將一些Activity銷燬,這樣線程就會失去了管理的對象,從而使程序發生意想不到的結果。此外,在Activity中創建線程,線程跟Activity的通信也比較麻煩,一般藉助Handler類來進行通信( http://blog.sina.com.cn/s/blog_3fe961ae0100mvc5.html)。
與Activity相比,Service一般「默默」地運行在後臺,生命週期比較長,所以它更合適去爲主程序提供某些服務,創建線程並管理線程。因此,筆者將原程序改成三層架構,從高到低分別爲:Activity層--Service層--Thread層。Activity將需要的服務「告訴」Service層,Service層創建Thread去完成服務。Thread將任務的進度、狀態、錯誤信息反饋給Service,Service將這些消息反饋給相關的Activity,並且還可以利用Notification更新通知欄消息。大體的結構就是這樣。

1 Activity和Service之間的通信
1.1 利用Handler通信: http://blog.sina.com.cn/s/blog_3fe961ae0100mvc5.html   :
Android在子線程中更新Activity中UI的方法:
Android 平臺下,進行多線程編程時,經常需要在主線程之外的一個單獨的線程中進行某些處理,然後更新用戶界面顯示。但是,在主線線程之外的線程中直接更新頁面顯示的問題是:系統會報這個異常:ERROR/AndroidRuntime(1222):android.view.ViewRoot$CalledFromWrongThreadException: Only theoriginal thread that created a view hierarchy can touch itsviews.

或許編程人員會在線程中調用Toast.makeText()方法,試圖在UI中顯示某些提示信息,這樣也會報如下的錯誤:

Can't create handler insidethread that has not called Looper.prepare()

解決方法:子線程中無法直接去更新Activity中的UI,一般的作法是子線程向Activity傳遞消息,然後Activity根據這些消息自己來更新UI。Android中有一個類叫android.os.Handler,就是用來做這件事的。

1. 在需要被線程更新UI的Activity 中聲明一個android.os.Handler 類的變量,privateHandler handler;

2. onCreate函數中加入handler的初始化:

@Override

public void onCreate(Bundle savedInstanceState) {

//其他代碼……

//……

//……

handler=new Handler(){

public void handleMessage(Message msg){

    

String message=(String)msg.obj;//obj不一定是String類,可以是別的類,看用戶具體的應用

  //根據message中的信息對主線程的UI進行改動

  //……                                                     }

}

 };


另外Activity中需要提供handler的get函數,這樣線程才能得到handler,進而傳遞消息。

 

public HandlergetHandler(){

returnthis.handler;

}

3.子線程類中需要持有表示上下文的Context類對象,實際應用中這個引用就是指向要更新UI的Activity對象,一般聲明爲:

private Contextctx;

然後在子線程類構造函數或其它函數中初始化ctx,這一步是爲了能夠得到Activity對象中的Handler對象。(或者用別的方法也行,只要子線程能得到Activity中的這個handler對象就可以。)

4. 最後一步,在子線程運行到某個地方,需要向Activity傳遞消息的時候,創建一個android.os.Message 類的對象,將要傳送的對象加入message ,通過Handler發佈傳送給主線程,代碼示例如下:

String str_temp="要傳給主線程的消息"

Message message Message.obtain();

message.obj=str_temp;

//通過Handler發佈傳送消息,handler

handler.sendMessage(message);

記住,這裏的handler跟Activity中的handler是同一個對象噢,這樣纔是把消息送到那個Activity中了。

另外,這種方法不但可以讓子線程更新UI,還可以有別的用途。現在我們假設子線程可能拋出某些錯誤,這個應該是很正常的,那麼如何讓錯誤信息能夠讓用戶知道呢?很簡單,在catch語句塊中,將catch到的錯誤對象,放入message.obj中,傳遞給Activity,Activity中用Toast.makeText()方法將錯誤信息顯示出來就可以了。




1.2 Activity調用startService (Intentservice)方法,將消息添加到Intent對象中,這樣Service對象可以在調用onStartCommand (Intentintent, int flags, intstartId)的時候可以得到這些消息。這種方法很簡單,但如果有大量的信息要傳遞的話,就很麻煩了。因爲Service端還要判斷一下消息是什麼,才能作進一步的動作。

1.3 Activity調用bindService (Intentservice, ServiceConnection conn, intflags)方法,得到Service對象的一個引用,這樣Activity可以直接調用到Service中的方法。具體代碼:
http://blog.csdn.net/liuhe688/article/details/6623924

通過服務更新進度通知&在Activity中監聽服務進度

上次我們講到如何實現一個可更新的進度通知,實現的方式是啓動一個線程模擬一個下載任務,然後根據任務進度向UI線程消息隊列發送進度消息,UI線程根據進度消息更新通知的UI界面。可是在實際應用中,我們一般會將上傳、下載等比較耗時的後臺任務以服務的形式運行,更新進度通知也是交由後臺服務來完成的。 不過有的時候,除了在通知裏面顯示進度信息,我們也要在Activity中顯示當前進度,很多下載系統都有這樣的功能,例如Android自帶瀏覽器的下載系統、QQ瀏覽器的下載系統等等。那麼如何實現這一功能呢?實現方式有很多,我們今天先來介紹其中的一種:在Activity中主動監聽服務的進度。

具體的思路是:讓Activity與後臺服務綁定,通過中間對象Binder的實例操作後臺服務,獲取進度信息和服務的狀態以及在必要的時候停止服務。

關於服務的生命週期,如果有些朋友們不太熟悉的話,可以去查閱相關資料;如果以後有時間,我可能也會總結一些與服務相關的知識。

爲了讓大家對這個過程更清晰一些,在上代碼之前,我們先來看看幾個截圖:

 

整個過程如上圖所示:在我們點擊開始按鈕後,下載任務開始運行,同事更新通知上的進度,當前Activity也從後臺服務獲取進度信息,顯示到按鈕下方;當我們點擊通知後,跳轉到下載管理界面,在這裏我們也從後臺服務獲取進度,還可以做取消任務等操作。

瞭解了整個過程的情況後,我們就來分析一下具體的代碼實現。

首先是/res/main.xml佈局文件:

[html]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     android:orientation="vertical"  
  4.     android:layout_width="fill_parent"  
  5.     android:layout_height="fill_parent">  
  6.     <Button  
  7.         android:layout_width="fill_parent"  
  8.         android:layout_height="wrap_content"  
  9.         android:text="start"  
  10.         android:onClick="start"/>  
  11.     <TextView  
  12.         android:id="@+id/text"  
  13.         android:layout_width="fill_parent"  
  14.         android:layout_height="wrap_content"  
  15.         android:gravity="center"/>  
  16. </LinearLayout>  

其中Button是用來啓動服務的,TextView是用來顯示進度信息的。

然後再在看一下MainActivity.Java的代碼:

[java]  view plain  copy
  1. package com.scott.notification;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.ComponentName;  
  5. import android.content.Context;  
  6. import android.content.Intent;  
  7. import android.content.ServiceConnection;  
  8. import android.os.Bundle;  
  9. import android.os.Handler;  
  10. import android.os.IBinder;  
  11. import android.os.Message;  
  12. import android.view.View;  
  13. import android.widget.TextView;  
  14.   
  15. public class MainActivity extends Activity {  
  16.   
  17.     private DownloadService.DownloadBinder binder;  
  18.     private TextView text;  
  19.       
  20.     private boolean binded;  
  21.   
  22.     private Handler handler = new Handler() {  
  23.         public void handleMessage(android.os.Message msg) {  
  24.             int progress = msg.arg1;  
  25.             text.setText("downloading..." + progress + "%");  
  26.         };  
  27.     };  
  28.   
  29.     private ServiceConnection conn = new ServiceConnection() {  
  30.   
  31.         @Override  
  32.         public void onServiceConnected(ComponentName name, IBinder service) {  
  33.             binder = (DownloadService.DownloadBinder) service;  
  34.             binded = true;  
  35.             // 開始下載  
  36.             binder.start();  
  37.             // 監聽進度信息  
  38.             listenProgress();  
  39.         }  
  40.   
  41.         @Override  
  42.         public void onServiceDisconnected(ComponentName name) {  
  43.         }  
  44.     };  
  45.   
  46.     @Override  
  47.     public void onCreate(Bundle savedInstanceState) {  
  48.         super.onCreate(savedInstanceState);  
  49.         setContentView(R.layout.main);  
  50.         text = (TextView) findViewById(R.id.text);  
  51.     }  
  52.   
  53.     @Override  
  54.     protected void onDestroy() {  
  55.         super.onDestroy();  
  56.         if (binded) {  
  57.             unbindService(conn);              
  58.         }  
  59.     }  
  60.   
  61.     public void start(View view) {  
  62.         if (binded) {  
  63.             binder.start();  
  64.             listenProgress();  
  65.             return;  
  66.         }  
  67.         Intent intent = new Intent(this, DownloadService.class);  
  68.         startService(intent);   //如果先調用startService,則在多個服務綁定對象調用unbindService後服務仍不會被銷燬  
  69.         bindService(intent, conn, Context.BIND_AUTO_CREATE);  
  70.     }  
  71.   
  72.     /** 
  73.      * 監聽進度 
  74.      */  
  75.     private void listenProgress() {  
  76.         new Thread() {  
  77.             public void run() {  
  78.                 while (!binder.isCancelled() && binder.getProgress() <= 100) {  
  79.                     int progress = binder.getProgress();  
  80.                     Message msg = handler.obtainMessage();  
  81.                     msg.arg1 = progress;  
  82.                     handler.sendMessage(msg);  
  83.                     if (progress == 100) {  
  84.                         break;  
  85.                     }  
  86.                     try {  
  87.                         Thread.sleep(200);  
  88.                     } catch (InterruptedException e) {  
  89.                         e.printStackTrace();  
  90.                     }  
  91.                 }  
  92.             };  
  93.         }.start();  
  94.     }  
  95. }  
我們可以看到,當點擊開始按鈕後,以bindService的方式綁定服務,用獲取到的DownloadService.DownloadBinder實例啓動服務,並在Activity中啓動一個線程監聽服務的進度信息,及時的顯示到按鈕下方。

服務類DownloadService.java代碼如下:

[java]  view plain  copy
  1. package com.scott.notification;  
  2.   
  3. import android.app.Notification;  
  4. import android.app.NotificationManager;  
  5. import android.app.PendingIntent;  
  6. import android.app.Service;  
  7. import android.content.Context;  
  8. import android.content.Intent;  
  9. import android.os.Binder;  
  10. import android.os.Handler;  
  11. import android.os.IBinder;  
  12. import android.os.Message;  
  13. import android.widget.RemoteViews;  
  14.   
  15. public class DownloadService extends Service {  
  16.   
  17.     private static final int NOTIFY_ID = 0;  
  18.     private boolean cancelled;  
  19.     private int progress;  
  20.   
  21.     private Context mContext = this;  
  22.   
  23.     private NotificationManager mNotificationManager;  
  24.     private Notification mNotification;  
  25.   
  26.     private DownloadBinder binder = new DownloadBinder();  
  27.   
  28.     private Handler handler = new Handler() {  
  29.         public void handleMessage(android.os.Message msg) {  
  30.             switch (msg.what) {  
  31.             case 1:  
  32.                 int rate = msg.arg1;  
  33.                 if (rate < 100) {  
  34.                     // 更新進度  
  35.                     RemoteViews contentView = mNotification.contentView;  
  36.                     contentView.setTextViewText(R.id.rate, rate + "%");  
  37.                     contentView.setProgressBar(R.id.progress, 100, rate, false);  
  38.                 } else {  
  39.                     // 下載完畢後變換通知形式  
  40.                     mNotification.flags = Notification.FLAG_AUTO_CANCEL;  
  41.                     mNotification.contentView = null;  
  42.                     Intent intent = new Intent(mContext, FileMgrActivity.class);  
  43.                     // 告知已完成  
  44.                     intent.putExtra("completed""yes");  
  45.                     //更新參數,注意flags要使用FLAG_UPDATE_CURRENT  
  46.                     PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);  
  47.                     mNotification.setLatestEventInfo(mContext, "下載完成""文件已下載完畢", contentIntent);  
  48.                     stopSelf();//停掉服務自身  
  49.                 }  
  50.   
  51.                 // 最後別忘了通知一下,否則不會更新  
  52.                 mNotificationManager.notify(NOTIFY_ID, mNotification);  
  53.                 break;  
  54.             case 0:  
  55.                 // 取消通知  
  56.                 mNotificationManager.cancel(NOTIFY_ID);  
  57.                 break;  
  58.             }  
  59.         };  
  60.     };  
  61.   
  62.     @Override  
  63.     public void onCreate() {  
  64.         super.onCreate();  
  65.         mNotificationManager = (NotificationManager) getSystemService(android.content.Context.NOTIFICATION_SERVICE);  
  66.     }  
  67.   
  68.     @Override  
  69.     public IBinder onBind(Intent intent) {  
  70.         // 返回自定義的DownloadBinder實例  
  71.         return binder;  
  72.     }  
  73.   
  74.     @Override  
  75.     public void onDestroy() {  
  76.         super.onDestroy();  
  77.         cancelled = true// 取消下載線程  
  78.     }  
  79.       
  80.     /** 
  81.      * 創建通知 
  82.      */  
  83.     private void setUpNotification() {  
  84.         int icon = R.drawable.down;  
  85.         CharSequence tickerText = "開始下載";  
  86.         long when = System.currentTimeMillis();  
  87.         mNotification = new Notification(icon, tickerText, when);  
  88.   
  89.         // 放置在"正在運行"欄目中  
  90.         mNotification.flags = Notification.FLAG_ONGOING_EVENT;  
  91.   
  92.         RemoteViews contentView = new RemoteViews(mContext.getPackageName(), R.layout.download_notification_layout);  
  93.         contentView.setTextViewText(R.id.fileName, "AngryBird.apk");  
  94.         // 指定個性化視圖  
  95.         mNotification.contentView = contentView;  
  96.   
  97.         Intent intent = new Intent(this, FileMgrActivity.class);  
  98.         PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);  
  99.         // 指定內容意圖  
  100.         mNotification.contentIntent = contentIntent;  
  101.         mNotificationManager.notify(NOTIFY_ID, mNotification);  
  102.     }  
  103.   
  104.     /** 
  105.      * 下載模塊 
  106.      */  
  107.     private void startDownload() {  
  108.         cancelled = false;  
  109.         int rate = 0;  
  110.         while (!cancelled && rate < 100) {  
  111.             try {  
  112.                 // 模擬下載進度  
  113.             &n
  114.     private void startDownload() {  
  115.         cancelled = false;  
  116.         int rate = 0;  
  117.         while (!cancelled && rate < 100) {  
  118.             try {  
  119.                 // 模擬下載進度  
  120.                 Thread.sleep(500);  
  121.                 rate = rate + 5;  
  122.             } catch (InterruptedException e) {  
  123.                 e.printStackTrace();  
  124.             }  
  125.             Message msg = handler.obtainMessage();  
  126.  &
相關文章
相關標籤/搜索