Activity、Service和線程應該是Android編程中最常見的幾種類了,幾乎大多數應用程序都會涉及到這幾個類的編程,自然而然的,也就會涉及到三者之間的相互通信,本文就試圖簡單地介紹一下這三者通信的方式。
與Activity相比,Service一般「默默」地運行在後臺,生命週期比較長,所以它更合適去爲主程序提供某些服務,創建線程並管理線程。因此,筆者將原程序改成三層架構,從高到低分別爲:Activity層--Service層--Thread層。Activity將需要的服務「告訴」Service層,Service層創建Thread去完成服務。Thread將任務的進度、狀態、錯誤信息反饋給Service,Service將這些消息反饋給相關的Activity,並且還可以利用Notification更新通知欄消息。大體的結構就是這樣。
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。
上次我們講到如何實現一個可更新的進度通知,實現的方式是啓動一個線程模擬一個下載任務,然後根據任務進度向UI線程消息隊列發送進度消息,UI線程根據進度消息更新通知的UI界面。可是在實際應用中,我們一般會將上傳、下載等比較耗時的後臺任務以服務的形式運行,更新進度通知也是交由後臺服務來完成的。 不過有的時候,除了在通知裏面顯示進度信息,我們也要在Activity中顯示當前進度,很多下載系統都有這樣的功能,例如Android自帶瀏覽器的下載系統、QQ瀏覽器的下載系統等等。那麼如何實現這一功能呢?實現方式有很多,我們今天先來介紹其中的一種:在Activity中主動監聽服務的進度。
具體的思路是:讓Activity與後臺服務綁定,通過中間對象Binder的實例操作後臺服務,獲取進度信息和服務的狀態以及在必要的時候停止服務。
關於服務的生命週期,如果有些朋友們不太熟悉的話,可以去查閱相關資料;如果以後有時間,我可能也會總結一些與服務相關的知識。
爲了讓大家對這個過程更清晰一些,在上代碼之前,我們先來看看幾個截圖:
整個過程如上圖所示:在我們點擊開始按鈕後,下載任務開始運行,同事更新通知上的進度,當前Activity也從後臺服務獲取進度信息,顯示到按鈕下方;當我們點擊通知後,跳轉到下載管理界面,在這裏我們也從後臺服務獲取進度,還可以做取消任務等操作。
瞭解了整個過程的情況後,我們就來分析一下具體的代碼實現。
首先是/res/main.xml佈局文件:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <Button
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="start"
- android:onClick="start"/>
- <TextView
- android:id="@+id/text"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center"/>
- </LinearLayout>
其中Button是用來啓動服務的,TextView是用來顯示進度信息的。
然後再在看一下MainActivity.Java的代碼:
- package com.scott.notification;
-
- import android.app.Activity;
- import android.content.ComponentName;
- import android.content.Context;
- import android.content.Intent;
- import android.content.ServiceConnection;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.view.View;
- import android.widget.TextView;
-
- public class MainActivity extends Activity {
-
- private DownloadService.DownloadBinder binder;
- private TextView text;
-
- private boolean binded;
-
- private Handler handler = new Handler() {
- public void handleMessage(android.os.Message msg) {
- int progress = msg.arg1;
- text.setText("downloading..." + progress + "%");
- };
- };
-
- private ServiceConnection conn = new ServiceConnection() {
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- binder = (DownloadService.DownloadBinder) service;
- binded = true;
- // 開始下載
- binder.start();
- // 監聽進度信息
- listenProgress();
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- }
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- text = (TextView) findViewById(R.id.text);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (binded) {
- unbindService(conn);
- }
- }
-
- public void start(View view) {
- if (binded) {
- binder.start();
- listenProgress();
- return;
- }
- Intent intent = new Intent(this, DownloadService.class);
- startService(intent); //如果先調用startService,則在多個服務綁定對象調用unbindService後服務仍不會被銷燬
- bindService(intent, conn, Context.BIND_AUTO_CREATE);
- }
-
- /**
- * 監聽進度
- */
- private void listenProgress() {
- new Thread() {
- public void run() {
- while (!binder.isCancelled() && binder.getProgress() <= 100) {
- int progress = binder.getProgress();
- Message msg = handler.obtainMessage();
- msg.arg1 = progress;
- handler.sendMessage(msg);
- if (progress == 100) {
- break;
- }
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
- }.start();
- }
- }
我們可以看到,當點擊開始按鈕後,以bindService的方式綁定服務,用獲取到的DownloadService.DownloadBinder實例啓動服務,並在Activity中啓動一個線程監聽服務的進度信息,及時的顯示到按鈕下方。
服務類DownloadService.java代碼如下:
- package com.scott.notification;
-
- import android.app.Notification;
- import android.app.NotificationManager;
- import android.app.PendingIntent;
- import android.app.Service;
- import android.content.Context;
- import android.content.Intent;
- import android.os.Binder;
- import android.os.Handler;
- import android.os.IBinder;
- import android.os.Message;
- import android.widget.RemoteViews;
-
- public class DownloadService extends Service {
-
- private static final int NOTIFY_ID = 0;
- private boolean cancelled;
- private int progress;
-
- private Context mContext = this;
-
- private NotificationManager mNotificationManager;
- private Notification mNotification;
-
- private DownloadBinder binder = new DownloadBinder();
-
- private Handler handler = new Handler() {
- public void handleMessage(android.os.Message msg) {
- switch (msg.what) {
- case 1:
- int rate = msg.arg1;
- if (rate < 100) {
- // 更新進度
- RemoteViews contentView = mNotification.contentView;
- contentView.setTextViewText(R.id.rate, rate + "%");
- contentView.setProgressBar(R.id.progress, 100, rate, false);
- } else {
- // 下載完畢後變換通知形式
- mNotification.flags = Notification.FLAG_AUTO_CANCEL;
- mNotification.contentView = null;
- Intent intent = new Intent(mContext, FileMgrActivity.class);
- // 告知已完成
- intent.putExtra("completed", "yes");
- //更新參數,注意flags要使用FLAG_UPDATE_CURRENT
- PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- mNotification.setLatestEventInfo(mContext, "下載完成", "文件已下載完畢", contentIntent);
- stopSelf();//停掉服務自身
- }
-
- // 最後別忘了通知一下,否則不會更新
- mNotificationManager.notify(NOTIFY_ID, mNotification);
- break;
- case 0:
- // 取消通知
- mNotificationManager.cancel(NOTIFY_ID);
- break;
- }
- };
- };
-
- @Override
- public void onCreate() {
- super.onCreate();
- mNotificationManager = (NotificationManager) getSystemService(android.content.Context.NOTIFICATION_SERVICE);
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- // 返回自定義的DownloadBinder實例
- return binder;
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- cancelled = true; // 取消下載線程
- }
-
- /**
- * 創建通知
- */
- private void setUpNotification() {
- int icon = R.drawable.down;
- CharSequence tickerText = "開始下載";
- long when = System.currentTimeMillis();
- mNotification = new Notification(icon, tickerText, when);
-
- // 放置在"正在運行"欄目中
- mNotification.flags = Notification.FLAG_ONGOING_EVENT;
-
- RemoteViews contentView = new RemoteViews(mContext.getPackageName(), R.layout.download_notification_layout);
- contentView.setTextViewText(R.id.fileName, "AngryBird.apk");
- // 指定個性化視圖
- mNotification.contentView = contentView;
-
- Intent intent = new Intent(this, FileMgrActivity.class);
- PendingIntent contentIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
- // 指定內容意圖
- mNotification.contentIntent = contentIntent;
- mNotificationManager.notify(NOTIFY_ID, mNotification);
- }
-
- /**
- * 下載模塊
- */
- private void startDownload() {
- cancelled = false;
- int rate = 0;
- while (!cancelled && rate < 100) {
- try {
- // 模擬下載進度
- &n
- private void startDownload() {
- cancelled = false;
- int rate = 0;
- while (!cancelled && rate < 100) {
- try {
- // 模擬下載進度
- Thread.sleep(500);
- rate = rate + 5;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Message msg = handler.obtainMessage();
- &