1. 程式人生 > >Android學習筆記4-通知

Android學習筆記4-通知

使用者通知

有幾種情況需要你通知使用者某些事件發生了,例如下面幾個:

@ 當檔案儲存成功時,需要發一個簡訊提醒一下使用者。

@ 一些後臺執行的程式需要使用者注意時,要建立一個通知來使用者,並且讓使用者更加方便的回饋後臺程式。

@ 當程式需要使用者等待時,需要給使用者一個進度條或者進度環來提示使用者。

每一種提示任務都可以用不同的技術來實現:

@ Toast 提示:在背景上顯示一個簡短的提示資訊。

@ Status Bar Notification:一個持續的後臺提醒,來響應使用者的請求。

@ Dailog提醒:和activity相關的提醒

這個文件總結了每種提示的使用技巧並且包含了大量的文件連結。

 


 

Toast 提示

一個Toast提醒會在視窗之上彈出一個訊息,它只會使用合適的空間來顯示資訊並且使用者當前的activity是可見的可互動的。提示會自動的淡入和漸漸消失,並且不會接收使用者操作。因為一個toast繼承自後來Service,他可以在程式不可見時出現。

Toast提示非常適合簡單的文字資訊比如檔案儲存成功,當你能確保使用者正在關注螢幕時。toast不能接收使用者的操作,如果你想接收使用者的操作,可以考慮使用Status Bar 提示。

 


 

狀態列提示

狀態列提示會新增一個圖示到系統的狀態列,並且附帶一個提示資訊視窗,當用戶選中資訊時,android會發出一個由提示資訊定義好的intent,通常會啟動一個activity。你可以自定義這個提示,比如新增聲音、震動、或者閃動螢幕。

當你的程式作為一個後臺服務工作時並且想要給使用者提示資訊時,狀態列提示是非常合適的一種提示方法,它給了使用者提示並且讓當前的activity並不是去焦點。

 


 

對話方塊提示

對話方塊是經常出現在當前activity之上的一個小視窗。底層的activity失去焦點,然後上層的dialog接受使用者的操作。dialog經常用來提醒、和在原activity上的一些簡短的對話。

當你想顯示一個進度條或者簡短的提示資訊並且需要從使用者哪裡得到確認是使用它。你也可以使用dialog來作為程式介面的一部分,不僅僅使用者通知。


響應通知

圍繞通知如何跟應用程式的UI流進行互動是使用者體驗的核心部分。你必須正確的實現以便在應用程式中提供一直的使用者體驗。

由日曆應用提供的兩種典型的通知的例子,一個是能夠對即將發生的事件發出一個通知,另一個是Email應用能夠在收到新的訊息時發出通知。它們代表了兩種推薦的處理通知的方式:既可以啟動一個跟主應用程式分離的Activity,也可以啟動一個完整的用於顯示對應通知的新的應用程式例項。

下面場景描述了在這兩種通知流中Activity堆疊是如何工作的,首先看如何處理日曆通知:

1.  使用者在Calendar應用中建立一個新的事件,並確認需要把電子郵件的訊息部分複製到一個事件中;

2.  使用者在主視窗選擇Email應用程式;

3.  Email應用中時,它們會收到來之日曆的一個要開會的通知;

4.  使用者選擇這個通知時,會把應用帶到一個專用的顯示即將開始的會議細節的日曆應用程式的Activity

5.  使用者看完通知的細節後,按回退按鈕可以返回Email應用程式中接受到通知的地方。

處理Email應用的通知:

1.  使用者在Email應用中撰寫一個訊息,並且需要檢查一下日曆的日期;

2.  使用者選擇了HomeàCalendar;

3.  使用者在日曆應用中時,會收到一個來之Email應用程式的關於新訊息的通知;

4.  使用者選擇這個通知時,就會被帶到現實這個訊息細節的Email應用程式中。這個介面取代了之前編寫郵件的介面,但是那個訊息依然被保留在草稿中;

5.  使用者一旦按下回退按鈕,就會返回到訊息列表,並且再次按鈕回退按鈕時,才會返回到從日曆應用中離開時的介面。

Email應用程式的通知方式中,由通知啟動的UI介面以通知所處的狀態來顯示主應用程式。例如,當Email應用程式因它的一個通知而顯示在前臺時,它既可以顯示郵件列表,也可以依賴是否有新有郵件來顯示一個特定的會話。要達到這個目的,我們要用一個代表通知狀態的新的Activity堆疊來代替應用程式的當前狀態。

下列程式碼演示瞭如何顯示這種型別的通知,最值得關注的makeMessageIntentStack()方法,它給這種狀態構造了一個代表應用程式的新的Activity堆疊的Intent物件陣列。如果你使用Fragment,需要初始化Fragment和應用程式的狀態,以便按回退按鈕時,會把UI介面切換會它的父狀態。這種通知的核心是Intent.makeRestartActivityTask()方法,它會用適當的標記來構造堆疊的根Activity,如Intent.FLAG_ACTIVITY_CLEAR_TASK

/**
 * This method creates an array of Intent objects representing the
 * activity stack for the incoming message details state that the
 * application should be in when launching it from a notification.
 */

staticIntent[] makeMessageIntentStack(Context context,CharSequencefrom,
        
CharSequence msg){
    
// A typical convention for notifications is to launch the user deeply
    
// into an application representing the data in the notification; to
    
// accomplish this, we can build an array of intents to insert the back
    
// stack stack history above the item being displayed.
    
Intent[] intents =newIntent[4];

    
// First: root activity of ApiDemos.
    
// This is a convenient way to make the proper Intent to launch and
    
// reset an application's task.
    intents
[0]=Intent.makeRestartActivityTask(newComponentName(context,
            com
.example.android.apis.ApiDemos.class));

    
// "App"
    intents
[1]=newIntent(context, com.example.android.apis.ApiDemos.class);
    intents
[1].putExtra("com.example.android.apis.Path","App");
    
// "App/Notification"
    intents
[2]=newIntent(context, com.example.android.apis.ApiDemos.class);
    intents
[2].putExtra("com.example.android.apis.Path","App/Notification");

    
// Now the activity to display to the user.  Also fill in the data it
    
// should display.
    intents
[3]=newIntent(context,IncomingMessageView.class);
    intents
[3].putExtra(IncomingMessageView.KEY_FROM,from);
    intents
[3].putExtra(IncomingMessageView.KEY_MESSAGE, msg);

    
return intents;
}

/**
 * The notification is the icon and associated expanded entry in the
 * status bar.
 */

void showAppNotification(){
    
// look up the notification manager service
    
NotificationManager nm =(NotificationManager)getSystemService(NOTIFICATION_SERVICE);

    
// The details of our fake message
    
CharSequencefrom="Joe";
    
CharSequence message;
    
switch((newRandom().nextInt())%3){
        
case0: message ="r u hungry?  i am starved";break;
        
case1: message ="im nearby u";break;
        
default: message ="kthx. meet u for dinner. cul8r";break;
    
}

    
// The PendingIntent to launch our activity if the user selects this
    
// notification.  Note the use of FLAG_CANCEL_CURRENT so that, if there
    
// is already an active matching pending intent, cancel it and replace
    
// it with the new array of Intents.
    
PendingIntent contentIntent =PendingIntent.getActivities(this,0,
            makeMessageIntentStack
(this,from, message),PendingIntent.FLAG_CANCEL_CURRENT);

    
// The ticker text, this uses a formatted string so our message could be localized
    
String tickerText = getString(R.string.imcoming_message_ticker_text, message);

    
// construct the Notification object.
    
Notification notif =newNotification(R.drawable.stat_sample, tickerText,
            
System.currentTimeMillis());

    
// Set the info for the views that show in the notification panel.
    notif
.setLatestEventInfo(this,from, message, contentIntent);

    
// We'll have this notification do the default sound, vibration, and led.
    
// Note that if you want any of these behaviors, you should always have
    
// a preference for the user to turn them off.
    notif
.defaults =Notification.DEFAULT_ALL;

    
// Note that we use R.layout.incoming_message_panel as the ID for
    
// the notification.  It could be any integer you want, but we use
    
// the convention of using a resource id for a string related to
    
// the notification.  It will always be a unique number within your
    
// application.
    nm
.notify(R.string.imcoming_message_ticker_text, notif);
}

在日曆應用的通知方式中,由通知啟動的UI介面是一個專用的不是普通的應用程式展現流程中的Activity。例如,使用者收到一個日曆通知時,會選擇這個通知來啟動一個特殊的Activity,這個Activity顯示了一個即將發生的日曆事件的列表。這個視窗只對通知有效,而不是普通的使用者介面。

針對傳送這種型別通知的程式碼時非常直接的,跟上例中的程式碼一樣,只是PendingIntent物件是針對通知專用的那個Activity

/**
 * The notification is the icon and associated expanded entry in the
 * status bar.
 */

void showInterstitialNotification(){
    
// look up the notification manager service
    
NotificationManager nm =(NotificationManager)getSystemService(NOTIFICATION_SERVICE);

    
// The details of our fake message
    
CharSequencefrom="Dianne";
    
CharSequence message;
    
switch((newRandom().nextInt())%3){
        
case0: message ="i am ready for some dinner";break;
        
case1: message ="how about thai down the block?";break;
        
default: message ="meet u soon. dont b late!";break;
    
}

    
// The PendingIntent to launch our activity if the user selects this
    
// notification.  Note the use of FLAG_CANCEL_CURRENT so that, if there
    
// is already an active matching pending intent, cancel it and replace
    
// it with the new Intent.
    
Intent intent =newIntent(this,IncomingMessageInterstitial.class);
    intent
.putExtra(IncomingMessageView.KEY_FROM,from);
    intent
.putExtra(IncomingMessageView.KEY_MESSAGE, message);
    intent
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |Intent.FLAG_ACTIVITY_CLEAR_TASK);
    
PendingIntent contentIntent =PendingIntent.getActivity(this,0,
            intent
,PendingIntent.FLAG_CANCEL_CURRENT);

    
// The ticker text, this uses a formatted string so our message could be localized
    
String tickerText = getString(R.string.imcoming_message_ticker_text, message);

    
// construct the Notification object.
    
Notification notif =newNotification(R.drawable.stat_sample, tickerText,
            
System.currentTimeMillis());

    
// Set the info for the views that show in the notification panel.
    notif
.setLatestEventInfo(this,from, message, contentIntent);

    
// We'll have this notification do the default sound, vibration, and led.
    
// Note that if you want any of these behaviors, you should always have
    
// a preference for the user to turn them off.
    notif
.defaults =Notification.DEFAULT_ALL;

    
// Note that we use R.layout.incoming_message_panel as the ID for
    
// the notification.  It could be any integer you want, but we use
    
// the convention of using a resource id for a string related to
    
// the notification.  It will always be a unique number within your
    
// application.
    nm
.notify(R.string.imcoming_message_ticker_text, notif);
}

僅有以上程式碼是不夠的,通常,Android會把應用程式的所有的Activity都看做是應用程式UI流的一部分,因此只是簡單的啟動這種通知Activity會導致它跟普通的應用程式回退堆疊混雜在一起。要保證它的正確的行為,在清單檔案中必須給這個Activity宣告屬性:android:launchMode=”singleTask”android:taskAffinity=””android:excludeFromRecents=”true”。下例是一個完整的Activity的宣告。

<activityandroid:name=".app.IncomingMessageInterstitial"
        
android:label="You have messages"
        
android:theme="@style/ThemeHoloDialog"
        
android:launchMode="singleTask"
        
android:taskAffinity=""
        
android:excludeFromRecents="true">
</activity>

從這個初始的Activity中啟動其他的Activity時必須要小心,因為,它不是應用程式的頂層Activity,也不是最近顯示的那個Activity,並且在任何要顯示帶有新資料的通知的時點都要重新啟動。最好的方法是從通知Activity中啟動的其他任何Activity都在它們自己的任務中。使用這種方法時必須確保新的任務與當前正在退出的應用程式的任務的狀態進行正確的互動。這種方式本質上跟之前介紹的Email應用的通知方式一樣的。下例中的程式碼使用之前例子中的makeMessageIntentStack()方法,然後處理點選事件,完成應用程式之間的切換:

/**
 * Perform a switch to the app.  A new activity stack is started, replacing
 * whatever is currently running, and this activity is finished.
 */

void switchToApp(){
    
// We will launch the app showing what the user picked.  In this simple
    
// example, it is just what the notification gave us.
    
CharSequencefrom= getIntent().getCharSequenceExtra(IncomingMessageView.KEY_FROM);
    
CharSequence msg = getIntent().getCharSequenceExtra(IncomingMessageView.KEY_MESSAGE);
    
// Build the new activity stack, launch it, and finish this UI.
    
Intent[] stack =IncomingMessage.makeMessageIntentStack(this,from, msg);
    startActivities
(stack);
    finish
();
}

管理通知

NotificationManager是管理所有通知的一個系統服務。必須用getSystemService()方法獲取這個物件的引用,如:

String ns =Context.NOTIFICATION_SERVICE;
NotificationManager mNotificationManager =(NotificationManager) getSystemService(ns);

當你想要給狀態列傳送通知時,就要使用NotificatonManager物件的notify(int, Notification)方法,第一個引數是通知的唯一ID,第二個引數是Notification物件。這ID唯一標識了從你的應用程式中發出通知,如果要更新通知或者(應用管理了不同型別的通知)通過通知中定義的Intent物件返回應用程式時,就需要這個ID

把“FLAG_AUTO_CANCEL”標記新增給Notification物件,使用者從通知視窗選擇了通知後,就會清除狀態列通知。也可以用cancle(int)方法來手動的清除,或者用cancelAll()方法清除所有的通知。

建立通知

Notification物件定義了顯示在狀態列和通知視窗中的通知訊息的細節,以及一些提醒設定,如聲音,閃爍等。

以下是狀態列通知的所有需求:

1.  一個狀態列圖示;

2.  除非你定義了一個定製的通知佈局,否則就需要一個標題和訊息內容;

3.  在通知被選擇時,要觸發一個PendingIntent物件。

以下是狀態列通知的可選設定:

1.  針對狀態列的一個提醒文字訊息;

2.  一個提示音;

3.  一個震動設定;

4.  一個LED的閃爍設定。

啟動一個新的通知的工具包括Notification(int, CharSequence, long)構造器和setLatestEventInfo(Context, CharSequence, CharSequence,PendingIntent)方法。它們能夠定義通知設定的所有需求。下列程式碼片段演示了基本的通知安裝步驟:

int icon = R.drawable.notification_icon;        // icon from resources
CharSequence tickerText ="Hello";              // ticker-text
longwhen=System.currentTimeMillis();         // notification time
Context context = getApplicationContext();      // application Context
CharSequence contentTitle ="My notification";  // message title
CharSequence contentText ="Hello World!";      // message text

Intent notificationIntent =newIntent(this,MyClass.class);
PendingIntent contentIntent =PendingIntent.getActivity(this,0, notificationIntent,0);

// the next two lines initialize the Notification, using the configurations above
Notification notification =newNotification(icon, tickerText,when);
notification
.setLatestEventInfo(context, contentTitle, contentText, contentIntent);

更新通知

當應用程式中持續發生事件時,可以更新狀態列中的資訊。例如,當在閱讀之前的訊息之前又收到了新的通知訊息時,Messaging應用程式會更新既存的通知,來顯示收到新訊息總數。這種更新既存的通知的實踐比新增一個新的通知要好,因為它避免了混亂的通知視窗。

因為每個通知是用一個唯一標識的整數IDNotificationManager服務來管理的,你能夠通過呼叫setLatesEventInfo()來修改通知,然後再呼叫notify()方法。

你能夠修改通知物件成員欄位的每個屬性(除了Context物件和通知的標題和文字以外)。你始終應該在呼叫setLatestEventInfo()方法更新通知時來修改文字訊息(內容標題和內容文字),然後呼叫notify()方法來更新通知。當然,如果你建立了一個定製的通知佈局,那麼更新這些標題和文字值就不會有影響。

給通知新增聲音

你能夠用預設的通知聲音或應用程式指定的聲音來提醒使用者。

要使用預設的提示音,就要給Notification物件的defaults欄位新增“DEFAULT_SOUND”設定,如:

notification.defaults |=Notification.DEFAULT_SOUND;

如果要使用不同的提示音,就要給Notificationsound欄位設定一個聲音資源的位置,下例中使用了一個已知的儲存在裝置的SD卡上的音訊檔案:

notification.sound =Uri.parse("file:///sdcard/notification/ringer.mp3");

在下面的例子中,音訊檔案是從內部的MediaStoreContentProvider物件中選擇音訊檔案:

notification.sound =Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI,"6");

在這個例子中,媒體檔案的準確ID6)是已知的,如果你不知道準確的ID,就必須用ContentResolverd物件查詢MediaStore中的所有有效的媒體。

如果你想要聲音持續的重複的播放,直到使用者響應了這個通知,或通知被取消,就要給Notification物件的flags欄位新增FLAG_INSISTENT設定。

注意:如果預設的欄位包含了DEFAULT_SOUND設定,那麼預設的聲音會覆蓋指定給sound欄位任何聲音。

給通知新增震動

你能夠用預設的震動模式或應用指定的震動模式來提醒使用者。

要使用預設的模式,就要把DEFAULT_VIBRATE值新增給defaults欄位:

notification.defaults |=Notification.DEFAULT_VIBRATE;

要定義自己的震動模式,就要把一個long型別陣列值傳遞給vibrate欄位:

long[] vibrate ={0,100,200,300};
notification
.vibrate = vibrate;

這個陣列定義了震動關停時長的交替模式(以毫秒為單位)。第一個值指定開始之前要等多長時間,第二個值指定的是第一次震動的時長,第三個引數下次停止的時長,依次類推。這種模式可以設定你希望的時長,但是不能重複設定。

注意:如果defaults欄位包含了DEFAULT_VIBRATE設定,預設的震動會覆蓋任何由vibragte欄位指定的震動。

給通知新增閃爍

要通過LED燈的閃爍來提醒使用者,你能夠實現預設的閃爍模式(如果這種模式有效),或者定義自己的顏色和閃爍模式。

要使用預設的亮度設定,就要給defaults欄位新增DEFAULT_LIGHTS設定:

notification.defaults |=Notification.DEFAULT_LIGHTS;

要定義自己的顏色和模式,就要給ledARGB欄位定義顏色值,給ledOffMS欄位定義燈關閉的時長(毫秒為單位),給ledOnMS欄位定義燈開啟的時長(毫秒為單位),還要給flags欄位新增FLAG_SHOW_LIGHTS標記:

notification.ledARGB =0xff00ff00;
notification
.ledOnMS =300;
notification
.ledOffMS =1000;
notification
.flags |=Notification.FLAG_SHOW_LIGHTS;

在這個例子中,綠燈代表閃爍300毫秒,並且關閉一秒。不是每張顏色都會被LEDs裝置支援的,並且也不是每種裝置都支援同一種顏色,因此最好要先預估一下硬體的能力。綠色是最常用的通知顏色。

更多的功能

能夠使用Notificatiion物件欄位和標記給通知新增更多的功能。下面列出了一些有用的功能:

FLAG_AUTO_CANCEL標記:

   flags欄位新增這個標記,從通知視窗選擇對應通知後會自動的取消通知。

FLAG_INSISTENT標記:

   flags欄位新增這個標記,重複播放音訊直到使用者響應這個通知。

FLAG_ONGOING_EVENT標記:

   flags欄位新增這個標記,把通知分組到通知視窗中的“Ongoing”標題下。這樣就表明傳送通知的應用程式正在執行---它的程序依然在後臺執行,即使是應用程式不可見(如音樂播放或電話呼叫)。

FLAG_NO_CLEAR標記:

   flags欄位新增這個標記,表明通知不應該通過“清除通知按鈕”來清除。如果你的通知還在繼續,使用這個標記就非常有用。

number欄位:

   這個值表明了當前通知所代表的事件的數量。對應數字被疊加在狀態列圖示上。如果要使用這個欄位,那麼在通知首次被建立時,必須用1開始。(如果從0開始計數,那麼在把改變到任意比0大的數時,這個數字將不會被顯示。)

iconLevel欄位:

  這個值指明瞭當前的用於通知圖示的LevelListDrawable的級別。通過改變這個值能夠在狀態列中使用動畫圖示。這個值跟LevelListDrawable中可描畫的定義相關聯。相關資訊可參照LevelListDrawable類定


建立定製化的通知佈局

預設情況下,在通知視窗顯示的通知包括標題和訊息文字。這兩項內容使用通過setLatestEventInfo()方法的contentTitlecontentText引數來定義的。但是,你也能夠使用RemoteViews類給通知定義一個定製化的佈局。如圖3所示就是一個定製的通知佈局的例子。它看上去與預設的通知類似,但是實際上它是用一個定製的XML佈局來建立的。

3.帶有定製化佈局的通知。

要給通知建立自己的佈局,就要例項化一個RemoteViews物件,用它來填充一個定製的佈局檔案,然後把RemoteViews物件傳遞給通知的contentView屬性欄位。

下面用一個例子來更好的理解如何建立定製化的通知:

1.  給通知建立XML佈局,如以下在custom_notification.xml檔案中定義的通知佈局:

<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    
android:id="@+id/layout"
    
android:layout_width="fill_parent"
    
android:layout_height="fill_parent"
    
android:padding="10dp">
    
<ImageViewandroid:id="@+id/image"
        
android:layout_width="wrap_content"
        
android:layout_height="fill_parent"
        
android:layout_alignParentLeft="true"
        
android:layout_marginRight="10dp"/>
    
<TextViewandroid:id="@+id/title"
        
android:layout_width="wrap_content"
        
android:layout_height="wrap_content"
        
android:layout_toRightOf="@id/image"
        
style="@style/NotificationTitle"/>
    
<TextViewandroid:id="@+id/text"
        
android:layout_width="wrap_content"
        
android:layout_height="wrap_content"
        
android:layout_toRightOf="@id/image"
        
android:layout_below="@id/title"
        
style="@style/NotificationText"/>
</RelativeLayout>

我們注意到,兩個TextView元素都包含了style屬性,給定製的通知中的文字使用樣式資源是至關重要的,因為通知的背景色在不同裝置和平臺版本中會有所差異。從Android2.3API級別9)開始,系統給預設的通知佈局所使用的文字定義了樣式,這樣你就應該在Android2.3或更高的版本上執行時使用樣式,以便確保文字針對背景是可見的。

例如,要在比Android2.3低的版本上使用標準的文字色,應該使用下列樣式(res/values/styles.xml):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
<stylename="NotificationText">
      
<itemname="android:textColor">?android:attr/textColorPrimary</item>
    
</style>
    
<stylename="NotificationTitle">
      
<itemname="android:textColor">?android:attr/textColorPrimary</item>
      
<itemname="android:textStyle">bold</item>
    
</style>
    
<!-- If you want a slightly different color for some text,
         consider using ?android:attr/textColorSecondary -->

</resources>

如果要在Android2.3以上的版本上給通知應用系統預設的顏色,就要使用以下樣式(res/values-v9/styles.xml):

<?xml version="1.0" encoding="utf-8"?>
<resources>
    
<stylename="NotificationText"parent="android:TextAppearance.StatusBar.EventContent"/>
    
<stylename="NotificationTitle"parent="android:TextAppearance.StatusBar.EventContent.Title"/>
</resources>

現在,在Android2.3API級別9)或更高的版本上執行時,在自己定製的View物件中的文字會使用與系統給預設的通知相同的顏色。這是重要的,因為Android的後續版本實際上把通知的背景色改變成深色的。繼承系統的樣式,確保文字會高亮顯示,而且,如果背景是其他的不期望的顏色,那麼文字也要做適當的改變。

2.  現在,在應用程式的程式碼中,使用RemoveViews類的方法來定義圖片和文字,然後把RemoteView物件傳遞給通知的contentView屬性欄位,如下例所示:

RemoteViews contentView =newRemoteViews(getPackageName(),R.layout.custom_notification_layout);
contentView
.setImageViewResource(R.id.image, R.drawable.notification_image);
contentView
.setTextViewText(R.id<