Notification使用詳解之三:通過服務更新進度通知&在Activity中監聽服務進度
上次我們講到如何實現一個可更新的進度通知,實現的方式是啟動一個執行緒模擬一個下載任務,然後根據任務進度向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();
- }
- }
服務類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 {
- // 模擬下載進度
- Thread.sleep(500);
- rate = rate + 5;
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Message msg = handler.obtainMessage();
- msg.what = 1;
- msg.arg1 = rate;
- handler.sendMessage(msg);
- this.progress = rate;
- }
- if (cancelled) {
- Message msg = handler.obtainMessage();
- msg.what = 0;
- handler.sendMessage(msg);
- }
- }
- /**
- * DownloadBinder中定義了一些實用的方法
- *
- * @author user
- *
- */
- public class DownloadBinder extends Binder {
- /**
- * 開始下載
- */
- public void start() {
- //將進度歸零
- progress = 0;
- //建立通知
- setUpNotification();
- new Thread() {
- public void run() {
- //下載
- startDownload();
- };
- }.start();
- }
- /**
- * 獲取進度
- *
- * @return
- */
- public int getProgress() {
- return progress;
- }
- /**
- * 取消下載
- */
- public void cancel() {
- cancelled = true;
- }
- /**
- * 是否已被取消
- *
- * @return
- */
- public boolean isCancelled() {
- return cancelled;
- }
- }
- }
我們看到,在服務中有個DownloadBinder類,它繼承自Binder,定義了一系列方法,獲取服務狀態以及操作當前服務,剛才我們在MainActivity中獲取的就是這個類的例項。最後,不要忘了在AndroidManifest.xml中配置該服務。關於進度通知的佈局檔案/res/layout/download_notification_layout.xml,在這裡就不需貼出了,朋友們可以參考一下Notification使用詳解之二中進度通知佈局的具體程式碼。
下面我們來介紹一下FileMgrActivity,它就是點選通知之後跳轉到的介面,佈局檔案/res/filemgr.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">
- <ProgressBar
- android:id="@+id/progress"
- style="?android:attr/progressBarStyleHorizontal"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:max="100"
- android:progress="0"/>
- <Button
- android:id="@+id/cancel"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="cancel"
- android:onClick="cancel"/>
- </LinearLayout>
我們來看一下FileMgrActivity.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.Button;
- import android.widget.ProgressBar;
- public class FileMgrActivity extends Activity {
- private DownloadService.DownloadBinder binder;
- private ProgressBar progressBar;
- private Button cancel;
- private boolean binded;
- private Handler handler = new Handler() {
- public void handleMessage(android.os.Message msg) {
- int progress = msg.arg1;
- progressBar.setProgress(progress);
- if (progress == 100) {
- cancel.setEnabled(false);
- }
- };
- };
- private ServiceConnection conn = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- binder = (DownloadService.DownloadBinder) service;
- //監聽進度資訊
- listenProgress();
- }
- @Override
- public void onServiceDisconnected(ComponentName name) {
- }
- };
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.filemgr);
- progressBar = (ProgressBar) findViewById(R.id.progress);
- cancel = (Button) findViewById(R.id.cancel);
- if ("yes".equals(getIntent().getStringExtra("completed"))) {
- //如果已完成,則不需再繫結service
- progressBar.setProgress(100);
- cancel.setEnabled(false);
- } else {
- //繫結service
- Intent intent = new Intent(this, DownloadService.class);
- bindService(intent, conn, Context.BIND_AUTO_CREATE);
- binded = true;
- }
- }
- @Override
- protected void onDestroy() {
- super.onDestroy();
- //如果是繫結狀態,則取消繫結
- if (binded) {
- unbindService(conn);
- }
- }
- public void cancel(View view) {
- //取消下載
- binder.cancel();
- }
- /**
- * 監聽進度資訊
- */
- 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);
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
- }.start();
- }
- }
我們發現,它和MainActivity實現方式很相似,恩,他們都是通過和服務繫結後獲取到的Binder物件來跟服務通訊的,都是主動和服務打招呼來獲取資訊和控制服務的。
這兩個Activity和一個Service似乎像是複雜的男女關係,兩個男人同時喜歡一個女人,都通過自己的手段試圖從那個女人獲取愛情,兩個男人都很主動,那個女人顯得很被動。
以上就是今天的全部內容,也許朋友們會有疑問,能不能讓Service主動告知Activity當前的進度資訊呢?答案是可以。下一次,我就會和大家分享一下,如何變Service為主動方,讓一個女人腳踏兩隻船的方式。
相關文章
- Notification使用詳解之四:由後臺服務向Activity傳送進度資訊
- Notification使用詳解之二:可更新進度的通知
- 使用RSocket進行服務通訊的反應性服務簡介 - Rafał Kowalski
- Redis中監聽key過期通知Redis
- 在 Flutter 應用程式中通過定製服務進行本地化Flutter
- iOS ANCS 通知服務iOS
- Rsync服務詳解
- SMB服務詳解
- Windows服務詳解Windows
- 百度GOPROXY代理服務Go
- 技術傳遞溫度,HMS Core手語服務走進暖心課堂
- 產業服務是什麼意思?詳解產業服務產業
- 走進AngularJs(六) 服務AngularJS
- Android多種進度條使用詳解Android
- SpringCloud系列使用Eureka進行服務治理SpringGCCloud
- Keepalived服務詳解
- [DHCP服務]——DHCP詳解
- Linux 服務詳解Linux
- 微服務的戰爭:按什麼維度拆分服務微服務
- 開通DashScoped靈積模型服務,贈送免費額度。模型
- 服務網格:微服務進入2.0時代微服務
- 使用RSocket進行服務通訊的反應性服務 - 負載平衡和可恢復性 | Rafał Kowalski負載
- RAC中監聽通知的坑!
- 微服務的服務間通訊與服務治理微服務
- win10 進入管理服務方法 windows10系統服務怎麼進入Win10Windows
- Android 活動(activity)和服務(service)進行通訊Android
- 無法使用 Apple 推送通知服務(APNs)APP
- Android Notification 通知詳解Android
- jQuery實進度條效果詳解jQuery
- 服務拆分與架構演進架構
- 圖片服務架構演進架構
- 住宅IP:高純淨度的代理服務
- 百度AI服務go語言sdkAIGo
- Project - IT服務管理成熟度評估Project
- angular五種服務詳解Angular
- Node.js:上傳檔案,服務端如何獲取檔案上傳進度Node.js服務端
- 通過 Laravel 訊息通知使用 EasySms 簡訊服務,讓你的程式碼更簡潔Laravel
- Angular父子元件通過服務傳參Angular元件