Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解

Android 4.4 KitKat NotificationManagerService使用詳解與原理分析(一)__使用詳解

編輯:關於Android編程

概況

Android在4.3的版本中(即API 18)加入了NotificationListenerService,根據SDK的描述(AndroidDeveloper)可以知道,當系統收到新的通知或者通知被刪除時,會觸發NotificationListenerService的回調方法。同時在Android 4.4 中新增了Notification.extras 字段,也就是說可以使用NotificationListenerService獲取系統通知具體信息,這在以前是需要用反射來實現的。

轉載請務必注明出處:http://blog.csdn.net/yihongyuelan

重要關系

對於系統通知,三方APP使用NotificationListenerService主要目的是為了獲取系統通知相關信息,主要包括:通知的新增和刪除,獲取當前通知數量,通知內容相關信息等。這些信息可以通過NotificationListenerService類提供的方法以及StatusBarNotification類對象來獲取。

NotificationListenerService主要方法(成員變量):

cancelAllNotifications() :刪除系統中所有可被清除的通知;
cancelNotification(String pkg, String tag, int id) :刪除具體某一個通知;
getActiveNotifications() :返回當前系統所有通知到StatusBarNotification[];
onNotificationPosted(StatusBarNotification sbn) :當系統收到新的通知後出發回調;
onNotificationRemoved(StatusBarNotification sbn) :當系統通知被刪掉後出發回調;

以上是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需要注意三點:

(1). 新建一個類並繼承自NotificationListenerService,override其中重要的兩個方法;

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並聲明相關權限;

 
     
         
     
 
(3). 開啟NotificationMonitor的監聽功能;

完成以上兩步之後,將程序編譯並安裝到手機上,但此時該程序是無法監聽到新增通知和刪除通知的,還需要在"Settings > Security > Notification access"中,勾選NotificationMonitor。此時如果系統收到新的通知或者通知被刪除就會打印出相應的log了。

這裡需要注意,如果手機上沒有安裝使用NotificationListenerService類的APP,Notification access是不會顯示出來的。可以在源碼/packages/apps/Settings/src/com/android/settings/SecuritySettings.java中看到,如果沒有使用NotificationListenerService的APK,直接就不顯示這一項了。

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 主界面

功能分析

1. 如何檢測應用已開啟Notification access監聽功能?

在程序啟動時,執行Notification access的檢測,查看是否訪問Notification的權限。如果用戶沒有Enable Notification access,則彈出提示對話框,點擊OK跳轉到Notification access設置頁面。

\\

圖 3 首次啟動 isEnable

使用NotificationListenerService的應用如果開啟了Notification access,系統會將包名等相關信息寫入SettingsProver數據庫中,因此可以從數據庫中獲取相關信息並過濾,從而判斷應用是否開啟了Notification access,代碼如下:

<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHByZSBjbGFzcz0="brush:java;">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,反之則表示沒有開啟。

2. 能不能主動跳轉到Notification access監聽頁面?

通過查看可以知道,Notification access界面接收action為"android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"的intent啟動,因此使用startActivity可以很容易的跳轉到該頁面,從而避免用戶在Settings中查找。代碼如下:

private static final String ACTION_NOTIFICATION_LISTENER_SETTINGS = "android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS";
private void openNotificationAccess() {
    startActivity(new Intent(ACTION_NOTIFICATION_LISTENER_SETTINGS));
}

3. 如何與NotificationListenerService交互?

因為NotificationListenerService中包含了四個重要的方法,分別是:onNotificationPosted、onNotificationRemoved、cancelNotification、cancelAllNotifications。通過這些方法我們才能實現諸如通知信息的獲取以及刪除等功能,雖然這些方法是public的,那是不是意味著我們只要拿到NotificationListenerService的對象就可以直接調用這些方法了呢?那如何拿到Service的對象呢?在之前的博文中,曾有提到與Service的交互( 具體可參考拙作《Android中程序與Service交互的方式——交互方式》),可以看到與Service的交互有很多種方法,但如果要拿到Service的對象,歸根到底還是需要Binder。

也就是說得使用bindService的辦法,將onServiceConnected回調中的IBinder對象轉型成NotificationListenerService的對象。測試代碼如下:

//在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);
//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的源碼中可以看到:
@Override
public IBinder onBind(Intent intent) {
    if (mWrapper == null) {
        mWrapper = new INotificationListenerWrapper();
    }   
    return mWrapper;
}
這裡的INotificationListenerWrapper是NotificationListenerService的一個內部類:

private class INotificationListenerWrapper extends INotificationListener.Stub

而NotificationMonitor繼承自NotificationListenerService,默認的onBind方法卻是:

@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關鍵代碼如下:

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 mCurrentNotifications = new ArrayList();
    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主要負責界面顯示與交互,關鍵代碼如下:

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;
        }
    }

    //......省略
}

4. NotificationListenerService使用過程中有哪些注意事項?

如果細心察看代碼的童鞋,一定發現代碼中有使用Handler,以及一些奇怪但又被注釋掉的代碼,比如"a.equals("b")"。從使用上來說,沒有必要使用handler,那干嘛要多次一舉?這裡就給大家分享一下在寫NotificationListenerDemo時遇到的一些坑。

①. NotificationMonitor的onCreate方法中使用handler來調用getActiveNotifications()方法

若直接在onCreate或者onBind方法中調用getActiveNotifications()方法是無法獲取當前系統通知。主要是因為NotificationMonitor還未完成初始化,而根本原因則是INotificationListenerWrapper對象mWrapper還未初始化,此時使用getActiveNotifications()方法又會調用到mWrapper,因此無法返回正常數據。在NotificationListenerService中可以看到getActiveNotifications()的源碼:

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上查看,連接戳這裡。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved