第12章 廣播接收器、服務與通知 12-1 系統服務的基礎 12-2 狀態列與通知 12-3 廣播接收器 12-4 建立本地服務
12-1 系統服務的基礎 – 說明 服務是一個在背景執行的行程,可以執行和活動一樣的工作,只是沒有使用介面,所以不會與使用者互動。例如:播放背景音樂時,之所以不會打斷我們發簡訊或收發電子郵件,因為它是一個在背景執行的服務,才能讓音樂播放不會中斷。
12-1 系統服務的基礎 – 服務的類型 Android的服務(Services)依據使用範圍的不同,可以分為兩種類型,如下所示: 12-1 系統服務的基礎 – 服務的類型 Android的服務(Services)依據使用範圍的不同,可以分為兩種類型,如下所示: 本地服務(Local Service):使用在Android應用程式的內部,主要是實作一些應用程式的耗時任務,例如:下載檔案、播放背景音樂和查询升级資訊等,因為服務是在背景執行,所以不會影響使用者操作應用程式,可以提昇使用者經驗,不會被一些耗時任務中斷。 遠端服務(Remote Services):使用在Android作業系統中不同應用程式之間的服務,簡單的說,這種服務可以被應用程式重複使用,例如:天氣預報服務、定位服務和感應器服務等,應用程式並不需要自行建立這些服務,而只是使用這些遠端服務。
12-1 系統服務的基礎 – 系統服務(System Services) 如同許多現代電腦的作業系統,Android作業系統本身就內建許多系統服務,這些服務在開機後有些就會自動啟動,而且持續執行中,因為它隨時需要提供使用者所需的服務,支援作業系統的正常運作。 Android內建相當多的系統服務,一些常用的系統服務有:定位服務、感應器服務、WIFI服務、藍牙服務、電信服務和警報服務等。
12-1 系統服務的基礎 – 如何使用系統服務 我們可以直接使用Manager類別來使用這些系統服務,在Activity類別是呼叫繼承自Context類別的getSystemService()方法,可以取得系統服務的Manager物件,然後透過Manager物件來存取系統服務。 一般來說,大部分系統服務是使用訂閱/取消訂閱機制來提供Android應用程式所需的服務,換句話說,我們需要註冊Android應用程式為系統服務的通知對象,在Java語言是建立傾聽者物件來實作服務事件的回撥方法(Callbacks Methods),當事件發生時,服務就會呼叫這些回撥方法。
12-1 系統服務的基礎 – 系統服務與電源管理 請注意!因為有些系統服務需要大量電力的供應,換句話說,它會花費較多行動裝置的電力,例如:GPS定位服務和一些感應器服務,為了節省電力,通常只有在真正需要時才會使用這些服務,以活動生命周期來說,建議在onResume()方法註冊服務的傾聽者物件;onPause()方法取消註冊服務的傾聽者物件。
12-2 狀態列與通知 12-2-1 在狀態列顯示通知訊息 12-2-2 配合通知訊息的更多提醒方式 12-2-3 群組通知、動作按鈕與直接回應通知
12-2-1 在狀態列顯示通知訊息– 說明 狀態列(Status Bar)是行動裝置最上方的一條橫向的長條區域,「通知服務」(Notification Service)是一種系統服務,可以在狀態列顯示可向下捲動顯示的通知訊息,例如:未接來電,如右圖所示:
12-2-1 在狀態列顯示通知訊息– 步驟一:取得通知管理物件 12-2-1 在狀態列顯示通知訊息– 步驟一:取得通知管理物件 在Android應用程式是使用「通知管理」(Notification Manager)的Manager物件在狀態列顯示提醒訊息和通知,這是一種系統服務,如下所示: NotificationManager notiMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 上述程式碼呼叫Context物件的getSystemService()方法來取得NotificationManager物件,參數是NOTIFICATON_SERVICE常數。
12-2-1 在狀態列顯示通知訊息– 步驟二:建立通知物件 12-2-1 在狀態列顯示通知訊息– 步驟二:建立通知物件 在取得NotificationManager物件後,就可以建立Notification通知物件,不過,我們並不是直接建立Notification物件,而是透過NotificationCompat.Builder物件,如下所示: NotificationCompat.Builder noti = new NotificationCompat.Builder(this) .setSmallIcon(R.drawable.ic_launcher) .setContentTitle("新郵件") .setContentText("你有一封新郵件");
12-2-1 在狀態列顯示通知訊息– 步驟三:建立PendingIntent物件(說明) 對於通知的項目清單來說,我們可以點選指定項目(可能有很多個)來觸發指定的Intent意圖物件,使用的是PendingIntent物件,此物件是一個容器物件用來包裝Intent物件,可以讓通知管理的系統服務在稍後觸發再來送出其中的Intent物件。
12-2-1 在狀態列顯示通知訊息– 步驟三:建立PendingIntent物件 Intent intent = new Intent(this, NotificationActivity.class); intent.putExtra("NOTIFICATION_ID", NOTIF_ID); 上述程式碼建立啟動NotificationActivity活動的Intent物件後,就可以建立PendingIntent物件,如下所示: PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
12-2-1 在狀態列顯示通知訊息– 步驟四:指定通知物件使用的PendingIntent物件 noti.setContentIntent(pIntent); 上述程式碼使用NotificationCompat.Builder物件的setContentIntent()方法指定使用的PendingIntent物件,參數是PendingIntent物件pIntent。
12-2-1 在狀態列顯示通知訊息– 步驟五:通知使用者 12-2-1 在狀態列顯示通知訊息– 步驟五:通知使用者 最後,我們可以通知使用者來顯示提醒訊息,如下所示: notiMgr.notify(NOTIF_ID, noti.build()); 上述程式碼使用NotificationManager物件的notify()方法來通知使用者,第1個參數是唯一的通知識別碼,第2個參數是Notification物件,我們是呼叫NotificationCompat.Builder物件的build()方法來建立Notification物件。
12-2-1 在狀態列顯示通知訊息– 步驟六:取消狀態列的提醒訊息 12-2-1 在狀態列顯示通知訊息– 步驟六:取消狀態列的提醒訊息 在通知啟動的活動可以取消狀態列的提醒訊息,如下所示: NotificationManager notiMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notiMgr.cancel(getIntent().getExtras(). getInt("NOTIFICATION_ID")); 上述程式碼是在活動的onCreate()方法取得NotificationManager物件後,呼叫cancel()方法取消狀態列的提醒訊息,參數是Intent物件傳遞的通知識別碼。
12-2-2 配合通知訊息的更多提醒方式 - 使用振動提醒使用者1 12-2-2 配合通知訊息的更多提醒方式 - 使用振動提醒使用者1 Notification類別支援vibrate屬性來指定振動範本,可以讓我們在訊息提醒外,加上振動效果,如下所示: Notification note=new Notification(R.drawable.icon, "新郵件", System.currentTimeMillis()); … note.vibrate= new long[] {100, 250, 100, 500}; vibrate屬性值是一個long陣列,第1個元素是等待時間,第2個元素是振動時間,以此類推,以此例就是:100ms等待,250ms振動,再100ms等待,500ms振動。
12-2-2 配合通知訊息的更多提醒方式 - 使用振動提醒使用者2 12-2-2 配合通知訊息的更多提醒方式 - 使用振動提醒使用者2 行動裝置除了硬體支援外,還需要在AndroidManifest.xml檔案加上使用振動的權限,如下所示: <uses-permission android:name="android.permission.VIBRATE"/>
12-2-2 配合通知訊息的更多提醒方式 - 使用LED燈提醒使用者 Notification類別也支援LED閃爍的提醒,我們只需指定相關屬性值,就可以在行動裝置關閉螢幕時,閃爍LED燈來提醒使用者,如下所示: note.ledARGB = Color.RED; note.flags |= Notification.FLAG_SHOW_LIGHTS; note.ledOnMS = 200; note.ledOffMS = 300;
12-2-3 群組通知、動作按鈕與直接回應通知- 通知訊息的動作按鈕 通知訊息可以新增動作按鈕來執行操作,在第12-2-1節是點選項目啟動第2個活動,我們可以改為按下動作按鈕來啟動,如下圖所示:
12-2-3 群組通知、動作按鈕與直接回應通知- 通知訊息的動作按鈕 首先,我們需要建立動作按鈕,如下所示: NotificationCompat.Action action = new NotificationCompat.Action.Builder( android.R.drawable.sym_action_email, "開啟", pIntent ).build(); noti.addAction(action); // noti.setContentIntent(pIntent);
12-2-3 群組通知、動作按鈕與直接回應通知- 群組通知 如果同時有相關的多個通知訊息,我們可以群組通知訊息,和顯示群組的摘要資訊,如下圖所示:
12-2-3 群組通知、動作按鈕與直接回應通知- 群組通知 首先建立群組通知的摘要訊息,如下所示: private static final String NOTIF_KEY_GROUP="group_key_notify"; ...... NotificationCompat.Builder notiSummary = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.ic_dialog_info) .setContentTitle("群組郵件通知") .setContentText("你有2封新郵件") .setGroup(NOTIF_KEY_GROUP) .setGroupSummary(true);
12-2-3 群組通知、動作按鈕與直接回應通知- 群組通知 然後,就可以一一建立屬於此群組的多個通知,如下所示: NotificationCompat.Builder noti1 = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentTitle("新郵件") .setContentText("你有一封Yahoo新郵件") .setGroup(NOTIF_KEY_GROUP); NotificationCompat.Builder noti2 = .setContentText("你有一封Gmail新郵件")
12-2-3 群組通知、動作按鈕與直接回應通知- 群組通知 程式碼建立2個通知訊息,和呼叫setGroup()方法指定屬於同一個群組,最後,我們可以送出3個通知訊息,如下所示: notiMgr.notify(NOTIF_ID1, noti1.build()); notiMgr.notify(NOTIF_ID2, noti2.build()); notiMgr.notify(NOTIF_GROUP, notiSummary.build());
12-2-3 群組通知、動作按鈕與直接回應通知- 直接回應通知訊息 直接回應通知訊息是Android 7.x版的新功能(只能在Android 7.x版執行),可以讓我們在通知訊息新增RemoteInput物件的輸入欄位,當按下【回應郵件】動作按鈕,就會顯示此輸入欄位,讓使用者直接輸入訊息和回應,如下圖所示:
12-2-3 群組通知、動作按鈕與直接回應通知- 直接回應通知訊息 在欄位輸入Hello!後,點選之後圖示就可以馬上回應,這是一個RemoteInput物件,如下所示: private static final String DIRECT_REPLY = "key_text_reply"; ...... RemoteInput rInput = new RemoteInput.Builder(DIRECT_REPLY) .setLabel("請輸入回應訊息...").build();
12-2-3 群組通知、動作按鈕與直接回應通知- 直接回應通知訊息 然後,我們可以將RemoteInput物件新增至動作按鈕,如下所示: NotificationCompat.Action action = new NotificationCompat.Action.Builder( android.R.drawable.sym_action_email, "回應郵件", pIntent ).addRemoteInput(rInput).build(); noti.addAction(action); // 新增動作按鈕
12-2-3 群組通知、動作按鈕與直接回應通知- 直接回應通知訊息 我們可以在MainActivity.java的onCreate()方法取得使用者輸入的字串,如下所示: Intent intent = this.getIntent(); Bundle rInput = RemoteInput.getResultsFromIntent(intent); 上述程式碼取得啟動此活動的Intent物件後,取出透過意圖傳入RemoteInput物件的輸入資料,這是一個Bundle物件,然後在if條件判斷是否有輸入資料,有,就取出顯示在TextView元件,如下所示: if (rInput != null) { TextView output = (TextView) findViewById(R.id.textView); String input = rInput.getCharSequence(DIRECT_REPLY).toString(); output.setText(input);
12-2-3 群組通知、動作按鈕與直接回應通知- 直接回應通知訊息 然後更新同一個通知訊息,顯示已經收到回應,如下所示: NotificationCompat.Builder rNoti = new NotificationCompat.Builder(this) .setSmallIcon(R.mipmap.ic_launcher) .setContentText("收到回應"); NotificationManager notiMgr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notiMgr.notify(NOTIF_ID3, rNoti.build()); }
12-3 廣播接收器 12-3-1 建立廣播接收器 12-3-2 送出與接收自訂廣播 12-3-3 系統服務與廣播接收器
12-3-1 建立廣播接收器 – 基礎 Android系統本身就會常常發出廣播,例如:接到來電、收到簡訊、啟動相機裝置、時區改變、系統開機、電池剩餘電量過低或使用者選擇偏好語言時,Android系統都會發出廣播。 我們可以將廣播接收器想像是停車場上的多輛轎車,駕駛手上的遙控器就是廣播發送者,因為頻率不同,一輛轎車只能接收指定遙控器頻率的廣播,等到轎車接到廣播,就解開門鎖,駕駛才能打開車門進入車輛,其他駕駛的遙控器就沒有作用。
12-3-1 建立廣播接收器 – 建立廣播接收器接收系統廣播 12-3-1 建立廣播接收器 – 建立廣播接收器接收系統廣播 廣播接收器並沒有任何使用介面,只是繼承android.content.BroadcastReceiver抽象類別的子類別,等到接收到指定廣播而觸發時,即可在實作onReceive()抽象方法回應廣播來執行操作,如下所示: public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 處理接收的廣播 }
12-3-2 送出與接收自訂廣播 – 說明 Android應用程式也可以送出自訂廣播,從一個活動至另一個活動,或完全不同的Android應用程式,例如:使用廣播讓其他應用程式知道它已經完成檔案下載,在Java程式是使用Intent物件的sendBroadcast()方法來送出自訂廣播。 因為廣播接收器類別本身並沒有程式進入點,在行動裝置安裝廣播接收器後,使用者並不能直接執行,我們需要在活動透過Intent意圖物件發出廣播來啟動。
12-3-2 送出與接收自訂廣播 – 說明 基本上,我們可以從活動送出廣播,為了方便說明,本節範例專案是將送出廣播的活動和廣播接收器置於同一個專案,在實務上,通常都是從一個Android應用程式送出廣播,讓另一個廣播接收器的Android應用程式接收廣播,而少在同一個應用程式使用廣播接收器。
12-3-2 送出與接收自訂廣播 – 建立活動送出自訂廣播 12-3-2 送出與接收自訂廣播 – 建立活動送出自訂廣播 在活動送出廣播一樣是使用Intent意圖物件,如下所示: public void btn1_Click(View view) { Intent i = new Intent("android.broadcast.TOAST"); sendBroadcast(i); } btn1_Click()方法是事件處理方法,在建立Intent物件後,使用sendBroadcast()方法送出廣播,Intent物件的建構子參數是廣播接收器註冊的動作名稱android.broadcast.TOAST,換句話說,我們發出的是不明確接收者的廣播。
12-3-2 送出與接收自訂廣播 – 建立廣播接收器接收自訂廣播 12-3-2 送出與接收自訂廣播 – 建立廣播接收器接收自訂廣播 等到接收到指定廣播而觸發時,廣播接收器可以啟動活動、服務、顯示通知或訊息文字,即在實作的onReceive()抽象方法回應廣播來執行所需操作,如下所示: public void onReceive(Context context, Intent intent) { Toast.makeText(context, "收到Toast廣播!", Toast.LENGTH_SHORT).show(); } ToastBroadcastReceiver類別的onReceive()方法在接到廣播後,使用Toast類別顯示收到廣播的訊息文字。
12-3-2 送出與接收自訂廣播 – AndroidManifest.xml <receiver android:name=".ToastBroadcastReceiver" android:enabled="true" android:exported="true" > <intent-filter> <action android:name="android.broadcast.TOAST"/> </intent-filter> </receiver> android:name=".NotificationBroadcastReceiver"
12-3-2 送出與接收自訂廣播 – AndroidManifest.xml <intent-filter> <action android:name="android.broadcast.NOTIFICATION"/> </intent-filter> </receiver> <receiver android:name=".ActivityBroadcastReceiver" android:enabled="true" android:exported="true" > <action android:name="android.broadcast.ACTIVITY"/>
12-3-3 系統服務與廣播接收器 – 說明 在實務上,我們可以透過系統服務發出廣播,然後在廣播接收器使用系統服務來回應廣播,例如:建立倒數計時的Android應用程式,它是使用AlertManager鬧鐘管理員在計時到後發出廣播,然後在廣播接收器使用Vibrator系統服務讓行動裝置振動來回應計時已經到了。
12-3-3 系統服務與廣播接收器 – 使用鬧鐘管理員排程發出廣播(PedingIntent) Android作業系統的鬧鐘服務(Alarm Services)可以排程在指定時間點送出廣播,並且自動啟動目標應用程式,簡單的說,就是提供定時服務的功能。 在本節是使用ALARM_SERVICE系統服務的AlarmManager物件在時間到達時送出廣播給指定的廣播接收器,它是使用PendingIntent物件來包裝攜帶Intent物件,如下所示: Intent i = new Intent(this, AlarmBroadcastReceiver.class); PendingIntent pi = PendingIntent.getBroadcast( this, 1234, i, 0);
12-3-3 系統服務與廣播接收器 – 使用鬧鐘管理員排程發出廣播(取得系統服務) 12-3-3 系統服務與廣播接收器 – 使用鬧鐘管理員排程發出廣播(取得系統服務) 我們使用getSystemService()方法取得系統服務的AlarmManager物件,如下所示: AlarmManager alarmMgr = (AlarmManager) getSystemService(ALARM_SERVICE); 程式碼呼叫Context物件的getSystemService()方法來取得AlarmManager物件,參數是ALARM_SERVICE常數。 在取得AlarmManager物件後,就可以使用set()方法來指定排程的時間,如下所示: alarmMgr.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+ (seconds * 1000), pi);
12-3-3 系統服務與廣播接收器 – 在廣播接收器使用Vibrator系統服務 等到接收到AlarmManage的廣播而觸發時,廣播接收器可以在實作的onReceive()抽象方法回應廣播來執行所需的操作,如下所示: public void onReceive(Context context, Intent intent) { Toast.makeText(context, "倒數計時的時間已經到了!", Toast.LENGTH_LONG).show(); Vibrator vibrator = (Vibrator) context.getSystemService( Context.VIBRATOR_SERVICE); vibrator.vibrate(1500); // 1秒半 }
12-4 建立本地服務 12-4-1 服務的生命周期 12-4-2 建立本地服務 12-4-3 建立與活動通訊的本地服務
12-4-1 服務的生命周期 – 說明 服務(Services)是在背景執行,所以一些耗時或在背景執行不影響使用者操作的任務,都可以使用本地服務來處理,例如:播放音樂和掃描SD卡上的媒體檔案等。
12-4-1 服務的生命周期 – 回撥方法 Android的服務並不能自行啟動,我們需要透過其他元件使用Context.startService()或Context.bindService()方法來啟動服務。服務的生命周期就是Android作業系統依序呼叫的一些回撥方法,如下表所示: 方法 說明 onCreate() 作業系統當服務第1次啟動時呼叫,請注意!我們不可以直接呼叫此方法 onDestroy() 當服務不再需要時,作業系統呼叫此方法來移除服務 onStartCommand() 每一次客戶端明確的啟動服務,就會呼叫一次此方法,舊版名為onStart()方法 onBind() 傳回客戶端可以與服務通訊的通訊通道(Communications Channel) onRebind() 當有新的客戶端連接服務時,就呼叫此方法 onUnbind() 當所有客戶端都斷線時,呼叫此方法
12-4-1 服務的生命周期 – 使用startService()方法啟動服務-1 服務的生命周期就是作業系統依序呼叫上表的回撥方法,當應用程式呼叫startService()方法啟動服務後,系統就依序呼叫onCreate()和onStartCommand()方法,此時的服務便處於執行狀態,如下圖所示:
12-4-1 服務的生命周期 – 使用startService()方法啟動服務-2 當應用程式呼叫stopService()方法停止服務,就是呼叫onDestroy()方法,如下圖所示:
12-4-1 服務的生命周期 – 使用bindService()方法啟動服務 如果服務是呼叫bindService()方法來啟動,作業系統只會呼叫onCreate()方法,而不會呼叫onStartCommand()方法,如右圖所示:
12-4-2 建立本地服務 - 說明 我們需要繼承android.app.Service抽象類別來建立本地服務的類別,如下圖所示:
12-4-2 建立本地服務 – 程式碼1 public class CountService extends Service { … @Override public void onCreate() { super.onCreate(); } public void onDestroy() { super.onDestroy();
12-4-2 建立本地服務 – 程式碼2 @Override public int onStartCommand(Intent intent, int flags, int startId) { … return START_STICKY; } public IBinder onBind(Intent arg0) { return null;
12-4-3 建立與活動通訊的本地服務 – 說明 CountService服務是直接修改上一節的同名服務,因為使用bindService()方法啟動服務不會呼叫onStartCommand()方法,所以將計數的執行緒改在onCreate()方法,新增getCount()方法傳回目前計數。
12-4-3 建立與活動通訊的本地服務 – 在服務宣告Binder內層類別1 因為活動需要和服務建立連接(透過Binder物件建立連接),所以在CountService類別需要宣告Binder內層類別,如下所示: private ServiceBinder sBinder = new ServiceBinder(); .... public class ServiceBinder extends Binder { CountService getService() { return CountService.this; }
12-4-3 建立與活動通訊的本地服務 – 在服務宣告Binder內層類別2 實作的onBind()方法就是傳回此ServiceBinder物件sBinder,如下所示: @Override public IBinder onBind(Intent intent) { return sBinder; }
12-4-3 建立與活動通訊的本地服務 – 在活動建立ServiceConnection介面物件 活動是透過ServiceConnection物件和服務的Binder物件建立連接,如下所示: private CountService s; private ServiceConnection myConnection = new ServiceConnection() { public void onServiceConnected( ComponentName className, IBinder binder) { s = ((CountService.ServiceBinder) binder).getService(); } public void onServiceDisconnected( ComponentName className) { s = null; };
12-4-3 建立與活動通訊的本地服務 – 啟動服務和建立活動與服務之間的連接 12-4-3 建立與活動通訊的本地服務 – 啟動服務和建立活動與服務之間的連接 在這一節的CountService服務是使用bindService()方法啟動服務,位在活動覆寫的onCreate()方法,如下所示: bindService(new Intent(this, CountService.class), myConnection, BIND_AUTO_CREATE); Context類別的bindService()方法共有3個參數,第1個是啟動服務的Intent物件,第2個是ServiceConnection介面物件,最後1個是旗標值,BIND_AUTO_CREATE是自動啟動服務和建立連接。
12-4-3 建立與活動通訊的本地服務 – 中斷活動與服務之間的連接 12-4-3 建立與活動通訊的本地服務 – 中斷活動與服務之間的連接 在onDestroy()方法可以中斷與服務之間的連接,使用的是unbindService()方法,參數是ServiceConnection介面物件,如下所示: unbindService(myConnection);
End