Android在4.3的版本中(即API 18)加入了NotificationListenerService,根據SDK的描述(AndroidDeveloper)能夠知道,當系統收到新的通知或者通知被刪除時,會觸發NotificationListenerService的回調方法。同時在Android 4.4 中新增了Notification.extras 字段,也就是說可使用NotificationListenerService獲取系統通知具體信息,這在之前是須要用反射來實現的。html
轉載請務必註明出處:http://blog.csdn.net/yihongyuelan
java
對於系統通知,三方APP使用NotificationListenerService主要目的是爲了獲取系統通知相關信息,主要包括:通知的新增和刪除,獲取當前通知數量,通知內容相關信息等。這些信息能夠經過NotificationListenerService類提供的方法以及StatusBarNotification類對象來獲取。android
NotificationListenerService主要方法(成員變量):git
cancelAllNotifications() :刪除系統中全部可被清除的通知;
cancelNotification(String pkg, String tag, int id) :刪除具體某一個通知;
getActiveNotifications() :返回當前系統全部通知到StatusBarNotification[];
onNotificationPosted(StatusBarNotification sbn) :當系統收到新的通知後出發回調;
onNotificationRemoved(StatusBarNotification sbn) :當系統通知被刪掉後出發回調;
github
以上是NotificationListenerService的主要方法,經過這些方法就能夠在應用中操做系統通知,在NotificationListenerService中除了對通知的操做以外,還能夠獲取到通知的StatusBarNotification對象,經過該對象能夠獲取通知更詳細的數據。數據庫
StatusBarNotification主要方法(成員變量):數組
getId():返回通知對應的id;
getNotification():返回通知對象;
getPackageName():返回通知對應的包名;
getPostTime():返回通知發起的時間;
getTag():返回通知的Tag,若是沒有設置返回null;
getUserId():返回UserId,用於多用戶場景;
isClearable():返回該通知是否可被清楚,FLAG_ONGOING_EVENT、FLAG_NO_CLEAR;
isOngoing():檢查該通知的flag是否爲FLAG_ONGOING_EVENT;
多線程
正確使用NotificationListenerService須要注意三點:app
(1). 新建一個類並繼承自NotificationListenerService,override其中重要的兩個方法;ide
[java] view plaincopy
public class NotificationMonitor extends NotificationListenerService {
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
Log.i("SevenNLS","Notification posted");
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
Log.i("SevenNLS","Notification removed");
}
}
(2). 在AndroidManifest.xml中註冊Service並聲明相關權限;
[html] view plaincopy
<service android:name=".NotificationMonitor"
android:label="@string/service_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
(3). 開啓NotificationMonitor的監聽功能;
完成以上兩步以後,將程序編譯並安裝到手機上,但此時該程序是沒法監聽到新增通知和刪除通知的,還須要在"Settings > Security > Notification access"中,勾選NotificationMonitor。此時若是系統收到新的通知或者通知被刪除就會打印出相應的log了。
這裏須要注意,若是手機上沒有安裝使用NotificationListenerService類的APP,Notification access是不會顯示出來的。能夠在源碼/packages/apps/Settings/src/com/android/settings/SecuritySettings.java中看到,若是沒有使用NotificationListenerService的APK,直接就不顯示這一項了。
[java] view plaincopy
mNotificationAccess = findPreference(KEY_NOTIFICATION_ACCESS);
if (mNotificationAccess != null) {
final int total = NotificationAccessSettings.getListenersCount(mPM);
if (total == 0) {
if (deviceAdminCategory != null) {
deviceAdminCategory.removePreference(mNotificationAccess);
}
} else {
final int n = getNumEnabledNotificationListeners();
if (n == 0) {
mNotificationAccess.setSummary(getResources().getString(
R.string.manage_notification_access_summary_zero));
} else {
mNotificationAccess.setSummary(String.format(getResources().getQuantityString(
R.plurals.manage_notification_access_summary_nonzero,
n, n)));
}
}
}
經過前面的講解(實際上就是對AndroidDeveloper的解釋),已經能夠正常使用NotificationListenerService了,但對於實際應用中,須要考慮的事情還比較多。好比:
1. 如何檢測應用已開啓Notification access監聽功能?
若是檢測到應用沒有激活Notification access監聽功能,須要提示用戶開啓;
2. 能不能主動跳轉到Notification access監聽頁面?
若是可以根據第1步的判斷自動跳轉到對應的頁面,那能夠省掉不少操做;
3. 如何與NotificationListenerService交互?
涉及到與Service的交互,但又與普通的Service不一樣,這裏後文解釋;
4. NotificationListenerService使用過程當中有哪些注意事項?
在使用NotificationListenerService過程當中本身遇到了一些坑,後文會經過分析給出相應的解決方案;
圖 1 程序運行截圖
NotificationListenerDemo主要用於獲取系統當前通知信息,並可手動建立"可清除通知",逐條刪除"可清除通知",一次性刪除"可清除通知",以及顯示系統當前活動的通知信息。實際上該示例回答了前面使用詳解中提出的各項疑問,在實際使用過程當中相信大部分人都會遇到,所以這裏逐條展開與你們分享。
圖 2 主界面
在程序啓動時,執行Notification access的檢測,查看是否訪問Notification的權限。若是用戶沒有Enable Notification access,則彈出提示對話框,點擊OK跳轉到Notification access設置頁面。
圖 3 首次啓動 isEnable
使用NotificationListenerService的應用若是開啓了Notification access,系統會將包名等相關信息寫入SettingsProver數據庫中,所以能夠從數據庫中獲取相關信息並過濾,從而判斷應用是否開啓了Notification access,代碼以下:
[java] view plaincopy
private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
private boolean isEnabled() {
String pkgName = getPackageName();
final String flat = Settings.Secure.getString(getContentResolver(),
ENABLED_NOTIFICATION_LISTENERS);
if (!TextUtils.isEmpty(flat)) {
final String[] names = flat.split(":");
for (int i = 0; i < names.length; i++) {
final ComponentName cn = ComponentName.unflattenFromString(names[i]);
if (cn != null) {
if (TextUtils.equals(pkgName, cn.getPackageName())) {
return true;
}
}
}
}
return false;
}
在返回值flat中若是包含了應用的包名,便可肯定應用已開啓Notification access,反之則表示沒有開啓。
經過查看能夠知道,Notification access界面接收action爲"android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"的intent啓動,所以使用startActivity能夠很容易的跳轉到該頁面,從而避免用戶在Settings中查找。代碼以下:
[java] view plaincopy
private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
private void openNotificationAccess() {
startActivity(new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS));
}
由於NotificationListenerService中包含了四個重要的方法,分別是:onNotificationPosted、onNotificationRemoved、cancelNotification、cancelAllNotifications。經過這些方法咱們才能實現諸如通知信息的獲取以及刪除等功能,雖然這些方法是public的,那是否是意味着咱們只要拿到NotificationListenerService的對象就能夠直接調用這些方法了呢?那如何拿到Service的對象呢?在以前的博文中,曾有提到與Service的交互( 具體可參考拙做《Android中程序與Service交互的方式——交互方式》),能夠看到與Service的交互有不少種方法,但若是要拿到Service的對象,歸根到底仍是須要Binder。
也就是說得使用bindService的辦法,將onServiceConnected回調中的IBinder對象轉型成NotificationListenerService的對象。測試代碼以下:
[java] view plaincopy
//在MainActivity.java的onCreate方法中使用bindService幫頂NotificationMonitor服務
bindService(new Intent(this,NotificationMonitor.class ), new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName arg0) {
}
@Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
NotificationMonitor.MyBinder localBinder = (MyBinder)arg1;
NotificationMonitor mMonitor = localBinder.getService();
}
}, BIND_AUTO_CREATE);
[java] view plaincopy
//NotificationMonitor的onBind方法返回構造的Binder對象
public class NotificationMonitor extends NotificationListenerService {
private MyBinder mBinder = new MyBinder();
public class MyBinder extends Binder{
public NotificationMonitor getService(){
return NotificationMonitor.this;
}
}
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
getActiveNotifications();
cancelAllNotifications();
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
}
}
那這樣操做以後是否是就意味着能夠拿到NotificationMonitor的對象並直接調用getActiveNotifications()方法,用於獲取當前系統通知的信息了呢?很抱歉,事實證實這樣是不行的。這裏簡單的分析下,在後面的NotificationListenerService原理分析中再詳細講解。在NotificationListenerService的源碼中能夠看到:
[java] view plaincopy
@Override
public IBinder onBind(Intent intent) {
if (mWrapper == null) {
mWrapper = new INotificationListenerWrapper();
}
return mWrapper;
}
這裏的INotificationListenerWrapper是NotificationListenerService的一個內部類:
[java] view plaincopy
private class INotificationListenerWrapper extends INotificationListener.Stub
而NotificationMonitor繼承自NotificationListenerService,默認的onBind方法倒是:
[java] view plaincopy
@Override
public IBinder onBind(Intent intent) {
return super.onBind(intent);
}
這裏注意,通常狀況下service的onBind方法返回要麼是null要麼是Binder對象,可這裏直接調用父類NotificationListenerService的onBind方法,而父類返回的是INotificationListenerWrapper的對象。這說明Binder對象已經被指定了,不能再給NotificationMonitor指定其它的Binder對象。若是你非要給NotificationMonitor指定其它的Binder對象,那麼就沒法使用INotificationListenerWrapper提供的方法。也就是說要麼就用系統NotificationListenerService提供的方法,要麼就把NotificationMonitor當一個普通的Service來用,系統提供的方法都不能使用。
那應該如何使用NotificationListenerService中的方法呢?在拙做《Android中程序與Service交互的方式——交互方式》中,已經提供了不少的例子,這裏僅以廣播的方式爲例。
既然NotificationMonitor可使用NotificationListenerService的方法,那經過NotificationMonitor把通知狀態的改變以及數據獲取到,並使用static數據進行存儲,以後再在MainActivity中直接使用便可。在MainActivity中控制通知的單個刪除和所有刪除,則使用廣播的方式發送給NotificationMonitor進行處理。MainActivity與NotificationMonitor的關係類圖以下:
圖 4 結構類圖
NotificationMonitor和MainActivity關鍵代碼以下:
[java] view plaincopy
public class NotificationMonitor extends NotificationListenerService {
private static final String TAG = "SevenNLS";
private static final String TAG_PRE = "[" + NotificationMonitor.class.getSimpleName() + "] ";
private static final int EVENT_UPDATE_CURRENT_NOS = 0;
public static final String ACTION_NLS_CONTROL = "com.seven.notificationlistenerdemo.NLSCONTROL";
//用於存儲當前全部的Notification的StatusBarNotification對象數組
public static List<StatusBarNotification[]> mCurrentNotifications = new ArrayList<StatusBarNotification[]>();
public static int mCurrentNotificationsCounts = 0;
//收到新通知後將通知的StatusBarNotification對象賦值給mPostedNotification
public static StatusBarNotification mPostedNotification;
//刪除一個通知後將通知的StatusBarNotification對象賦值給mRemovedNotification
public static StatusBarNotification mRemovedNotification;
private CancelNotificationReceiver mReceiver = new CancelNotificationReceiver();
// String a;
private Handler mMonitorHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_UPDATE_CURRENT_NOS:
updateCurrentNotifications();
break;
default:
break;
}
}
};
class CancelNotificationReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action;
if (intent != null && intent.getAction() != null) {
action = intent.getAction();
if (action.equals(ACTION_NLS_CONTROL)) {
String command = intent.getStringExtra("command");
if (TextUtils.equals(command, "cancel_last")) {
if (mCurrentNotifications != null && mCurrentNotificationsCounts >= 1) {
//每次刪除通知最後一個
StatusBarNotification sbnn = getCurrentNotifications()[mCurrentNotificationsCounts - 1];
cancelNotification(sbnn.getPackageName(), sbnn.getTag(), sbnn.getId());
}
} else if (TextUtils.equals(command, "cancel_all")) {
//刪除全部通知
cancelAllNotifications();
}
}
}
}
}
@Override
public void onCreate() {
super.onCreate();
logNLS("onCreate...");
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_NLS_CONTROL);
registerReceiver(mReceiver, filter);
//在onCreate時第一次調用getActiveNotifications()
mMonitorHandler.sendMessage(mMonitorHandler.obtainMessage(EVENT_UPDATE_CURRENT_NOS));
}
@Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
@Override
public IBinder onBind(Intent intent) {
// a.equals("b");
logNLS("onBind...");
return super.onBind(intent);
}
@Override
public void onNotificationPosted(StatusBarNotification sbn) {
//當系統收到新的通知後,更新mCurrentNotifications列表
updateCurrentNotifications();
logNLS("onNotificationPosted...");
logNLS("have " + mCurrentNotificationsCounts + " active notifications");
mPostedNotification = sbn;
//經過如下方式能夠獲取Notification的詳細信息
/*
* Bundle extras = sbn.getNotification().extras; String
* notificationTitle = extras.getString(Notification.EXTRA_TITLE);
* Bitmap notificationLargeIcon = ((Bitmap)
* extras.getParcelable(Notification.EXTRA_LARGE_ICON)); Bitmap
* notificationSmallIcon = ((Bitmap)
* extras.getParcelable(Notification.EXTRA_SMALL_ICON)); CharSequence
* notificationText = extras.getCharSequence(Notification.EXTRA_TEXT);
* CharSequence notificationSubText =
* extras.getCharSequence(Notification.EXTRA_SUB_TEXT);
* Log.i("SevenNLS", "notificationTitle:"+notificationTitle);
* Log.i("SevenNLS", "notificationText:"+notificationText);
* Log.i("SevenNLS", "notificationSubText:"+notificationSubText);
* Log.i("SevenNLS",
* "notificationLargeIcon is null:"+(notificationLargeIcon == null));
* Log.i("SevenNLS",
* "notificationSmallIcon is null:"+(notificationSmallIcon == null));
*/
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {
//當有通知被刪除後,更新mCurrentNotifications列表
updateCurrentNotifications();
logNLS("removed...");
logNLS("have " + mCurrentNotificationsCounts + " active notifications");
mRemovedNotification = sbn;
}
private void updateCurrentNotifications() {
try {
StatusBarNotification[] activeNos = getActiveNotifications();
if (mCurrentNotifications.size() == 0) {
mCurrentNotifications.add(null);
}
mCurrentNotifications.set(0, activeNos);
mCurrentNotificationsCounts = activeNos.length;
} catch (Exception e) {
logNLS("Should not be here!!");
e.printStackTrace();
}
}
//獲取當前狀態欄顯示通知總數
public static StatusBarNotification[] getCurrentNotifications() {
if (mCurrentNotifications.size() == 0) {
logNLS("mCurrentNotifications size is ZERO!!");
return null;
}
return mCurrentNotifications.get(0);
}
private static void logNLS(Object object) {
Log.i(TAG, TAG_PRE + object);
}
}
而MainActivity主要負責界面顯示與交互,關鍵代碼以下:
[java] view plaincopy
public class MainActivity extends Activity {
private static final String TAG = "SevenNLS";
private static final String TAG_PRE = "["+MainActivity.class.getSimpleName()+"] ";
private static final int EVENT_SHOW_CREATE_NOS = 0;
private static final int EVENT_LIST_CURRENT_NOS = 1;
private static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
private boolean isEnabledNLS = false;
private TextView mTextView;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_SHOW_CREATE_NOS:
//顯示建立的Notification對應的pkgName、Tag、Id
showCreateNotification();
break;
case EVENT_LIST_CURRENT_NOS:
//顯示當前全部的Notification數量及其包名
listCurrentNotification();
break;
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.textView);
}
@Override
protected void onResume() {
super.onResume();
//判斷是否有開啓Notification access
isEnabledNLS = isEnabled();
logNLS("isEnabledNLS = " + isEnabledNLS);
if (!isEnabledNLS) {
//若是沒有開啓則顯示確認對話框
showConfirmDialog();
}
}
public void buttonOnClicked(View view) {
mTextView.setTextColor(Color.BLACK);
switch (view.getId()) {
case R.id.btnCreateNotify:
logNLS("Create notifications...");
//建立可清除的Notification
createNotification(this);
//顯示當前狀態欄中全部Notification數量及其包名
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_SHOW_CREATE_NOS), 50);
break;
case R.id.btnClearLastNotify:
logNLS("Clear Last notification...");
//清除最後一個Notification
clearLastNotification();
//顯示當前狀態欄中全部Notification數量及其包名
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_LIST_CURRENT_NOS), 50);
break;
case R.id.btnClearAllNotify:
logNLS("Clear All notifications...");
//清除全部"可被清除"的Notification
clearAllNotifications();
mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_LIST_CURRENT_NOS), 50);
break;
case R.id.btnListNotify:
logNLS("List notifications...");
listCurrentNotification();
break;
case R.id.btnEnableUnEnableNotify:
logNLS("Enable/UnEnable notification...");
//打開Notification access啓動/取消界面
openNotificationAccess();
break;
default:
break;
}
}
//......省略
}
若是細心察看代碼的童鞋,必定發現代碼中有使用Handler,以及一些奇怪但又被註釋掉的代碼,好比"a.equals("b")"。從使用上來講,沒有必要使用handler,那幹嗎要屢次一舉?這裏就給你們分享一下在寫NotificationListenerDemo時遇到的一些坑。
①. NotificationMonitor的onCreate方法中使用handler來調用getActiveNotifications()方法
若直接在onCreate或者onBind方法中調用getActiveNotifications()方法是沒法獲取當前系統通知。主要是由於NotificationMonitor還未完成初始化,而根本緣由則是INotificationListenerWrapper對象mWrapper還未初始化,此時使用getActiveNotifications()方法又會調用到mWrapper,所以沒法返回正常數據。在NotificationListenerService中能夠看到getActiveNotifications()的源碼:
[java] view plaincopy
public StatusBarNotification[] getActiveNotifications() {
try {
return getNotificationInterface().getActiveNotificationsFromListener(mWrapper);
} catch (android.os.RemoteException ex) {
Log.v(TAG, "Unable to contact notification manager", ex);
}
return null;
}
也就是說只要在onBind方法完成以後,再調用getActiveNotifications()方法就能夠正常獲取數據了,所以這裏使用了handler多線程的方式。固然,爲了保險可使用sendEmptyMessgeDelay加上延時。
②. 若是NotificationMonitor在onCreate或onBind方法中crash,則該service已經失效,需重啓手機才能進行後續開發驗證
若是在onCreate或者onBind方法中,出現異常致使NotificationMonitor發生crash,就算找到問題並將其改正,以後的驗證仍是沒法繼續進行的,也就是沒法收到通知的新增和刪除消息,onNotificationPosted和onNotificationRemoved方法不會被調用。
這也是我在onBind方法中故意註釋致使空指針異常的代碼,有興趣的童鞋能夠把註釋去掉後嘗試,去掉註釋會致使NotificationListenerDemo異常中止,此時你再加上註釋再次運行NotificationListenerDemo,雖然程序能夠正常啓動,但沒法正常執行NotificationMonitor中的onNotificationPosted和onNotificationRemoved方法。這個涉及NotificationListenerService的原理,後面會另行分析。
③. MainActivity中onClick方法裏使用handler操做
當點擊刪除通知時,系統通知相關狀態還未更新,此時尚未回調到NotificationMonitor中,因此獲取的數據就仍是上一次的數據。爲了可以獲取到正確的Notification數據,可使用handler並加上延時,這樣再去獲取Notification信息時,系統已經觸發了NotificationMonitor回調,數據也有正常了。另外,50ms的延時幾乎是感知不到的。
④. 爲何要使用ArrayList來保存StatusBarNotification數組對象
當新增或者刪除通知時,會觸發onNotificationPosted或onNotificationRemoved回調,在該方法中調用getActiveNotifications()方法用以獲取當前系統通知信息。而getActiveNotifications()返回的是StatusBarNotification[]數組,由於這個數組是可變長的,也就是長度會隨時變化,所以沒法直接存儲。使用ArrayList能夠很好的解決這個問題,在ArrayList對象中添加一個StatusBarNotification[]對象,以後使用ArrayList.set(0,statusbar[])方法對數據進行更新便可。
NotificationListenerService是Android 4.3 以後新增的接口服務,用於獲取系統Notification信息,這在以前的Android版本是沒法直接辦到的。在Android 4.4中,增長了Notification.extra變量,使得獲取Notification相關信息更加豐富,這些接口的開放更加利於三方應用的使用,但同時也會帶來一些隱私問題。
本文針對NotificationListenerService的使用進行了詳細分析,固然其中不乏有失偏頗的地方,本着互聯網知識共享精神也將本身的一些記錄發佈出來,一來可作筆記,二來但願可以給苦苦尋覓的童鞋一些幫助。
後續會對NotificationListenerService的原理進行分析,敬請期待。
NotificationMonitor代碼免積分下載:下載Demo
爲了後續可以更新,已經代碼傳到github上,有興趣的童鞋能夠在github上查看,鏈接戳這裏。