概述
NotificationListenerService是android api18(Android 4.3)引入的一個(gè)類傻铣。主要作用就是用來監(jiān)聽系統(tǒng)接收到的通知。 具體可以做什么事情大家可以發(fā)揮想象九串,比如:紅包插件中就可以使用該類残吩。
本文來解釋下該service的實(shí)現(xiàn)原理
使用
首先看下如何使用
- AndroidManifest.xml中注冊(cè)
<service android:name=".MonitorNotificationService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
- 繼承NotificationListenerService
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class MonitorNotificationService extends NotificationListenerService {
...
@Override
public void onNotificationPosted(StatusBarNotification sbn) {...}
@Override
public void onNotificationRemoved(StatusBarNotification sbn) {}
}
只要這2步,就完成了對(duì)通知欄監(jiān)聽的準(zhǔn)備工作哺眯。接下來只要在系統(tǒng)設(shè)置中打開開關(guān)就可以監(jiān)聽通知了。筆者嘗試了許多機(jī)型扒俯,在設(shè)置中找到通知欄權(quán)限太難了奶卓。所以都是通知如下代碼幫助用戶進(jìn)入系統(tǒng)設(shè)置指定界面一疯,然后引導(dǎo)用戶打開開關(guān)
Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
startActivity(intent);
至此,所有工作都做完了夺姑,在onNotificationPosted
的回調(diào)里面就可以收到系統(tǒng)的所有通知欄消息了墩邀。
拋磚
這里,先拋出2個(gè)問題供大家思考盏浙,然后下文再給出答案
- AndroidManifest中的service聲明了permission和action磕蒲,這個(gè)有什么用?
- 當(dāng)我們的程序啟動(dòng)的時(shí)候只盹,MonitorNotificationService自動(dòng)就啟動(dòng)了,但是代碼里面并沒有對(duì)該service做顯示啟動(dòng)兔院,那它是如何啟動(dòng)的呢殖卑?
對(duì)于如何研究NotificationListenerService的實(shí)現(xiàn)原理,筆者是從系統(tǒng)設(shè)置界面開始的坊萝,畢竟這個(gè)地方的開關(guān)決定了該功能是否可用
setting
通過Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS
可以進(jìn)入到setting指定界面孵稽,我們就從這里入手,找到該界面十偶,繼承關(guān)系如下
NotificationAccessSettings extends ManagedServiceSettings extends ListFragment
那么先看下該界面的list數(shù)據(jù)是如何填充的
private static int getServices(Config c, ArrayAdapter<ServiceInfo> adapter, PackageManager pm) {
...
List<ResolveInfo> installedServices = pm.queryIntentServicesAsUser(
new Intent(c.intentAction),
PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
user);
for (int i = 0, count = installedServices.size(); i < count; i++) {
ResolveInfo resolveInfo = installedServices.get(i);
ServiceInfo info = resolveInfo.serviceInfo;
if (!c.permission.equals(info.permission)) {
Slog.w(c.tag, "Skipping " + c.noun + " service "
+ info.packageName + "/" + info.name
+ ": it does not require the permission "
+ c.permission);
continue;
}
if (adapter != null) {
adapter.add(info);
}
...
}
return services;
}
- 通過pm查找指定service菩鲜,該service需要滿足符合參數(shù)
new Intent(c.intentAction)
- 對(duì)查找出來的service進(jìn)行遍歷,如果沒有配置
c.permission
的service則不顯示在列表中
那這個(gè)c.intentAction和c.permission的值是多少呢惦积?答案在NotificationAccessSettings
中
private static Config getNotificationListenerConfig() {
...
c.intentAction = NotificationListenerService.SERVICE_INTERFACE;
c.permission = android.Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE;
...
return c;
}
這2個(gè)值分別對(duì)應(yīng)我們之前在AndroidManifest.xml中的service配置的<intent-filter>中的action和android:permission的值接校。如果我們?cè)陂_發(fā)過程中service少配了一個(gè)選項(xiàng),就沒有辦法在setting找到服務(wù)并開啟狮崩,所以之前拋磚中的問題1也就迎刃而解了蛛勉。
接下來再看看點(diǎn)開該服務(wù)后,是不是啟動(dòng)了我們配置的service睦柴。
先找到點(diǎn)擊后的代碼
private void saveEnabledServices() {
StringBuilder sb = null;
for (ComponentName cn : mEnabledServices) {
if (sb == null) {
sb = new StringBuilder();
} else {
sb.append(':');
}
sb.append(cn.flattenToString());
}
Settings.Secure.putString(mCR,
mConfig.setting,
sb != null ? sb.toString() : "");
}
what?!诽凌, 居然只是往setting的Secure
表中寫了一個(gè)值而已?并沒有啟動(dòng)service
其中mConfig.setting
也是在NotificationAccessSettings
中配置的
c.setting = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS; //-->enabled_notification_listeners
(插曲
我們讀取setting中Secure表的ENABLED_NOTIFICATION_LISTENERS字段的值
String flat = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
該值是包含了系統(tǒng)當(dāng)前所有授權(quán)了的服務(wù)列表坦敌,以:
作為分割侣诵,如下所示
com.qihoo360.mobilesafe/com.qihoo360.mobilesafe.service.NLService: //360
com.huajiao/com.huajiao.service.AppStoreNotificationListenerService: //花椒
)
既然只是往數(shù)據(jù)庫中寫了一個(gè)值就開啟了服務(wù),那么一定是采用了觀察者模式狱窘,其他地方對(duì)該數(shù)據(jù)庫進(jìn)行了監(jiān)聽杜顺,得到回調(diào)。
在源碼中全局搜索ENABLED_NOTIFICATION_LISTENERS
训柴,最后定位到NotificationManagerService.java
中
NotificationManagerService
public class NotificationListeners extends ManagedServices {
...
@Override
protected Config getConfig() {
...
c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_LISTENERS;
...
return c;
}
...
}
這個(gè)寫法是不是相當(dāng)熟悉哑舒,在系統(tǒng)的設(shè)置界面就是使用的該寫法。
我們到父類ManagedServices
中看看是如何使用getConfig
的
ManagedServices
public ManagedServices(Context context, Handler handler, Object mutex,
UserProfiles userProfiles) {
...
mConfig = getConfig();
mSettingsObserver = new SettingsObserver(handler);
}
private class SettingsObserver extends ContentObserver {
private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(mConfig.secureSettingName);
...
private void observe() {
ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(mSecureSettingsUri,
false, this, UserHandle.USER_ALL);
update(null);
}
...
}
構(gòu)造方法中給Config和ContentObserver對(duì)象賦值.
看到ContentObserver是不是豁然開朗幻馁,它所監(jiān)聽的Uri正好又是Settings.Secure.ENABLED_NOTIFICATION_LISTENERS
已經(jīng)越來越接近答案了洗鸵,我們看看ContentObserver的回調(diào)函數(shù)
@Override
public void onChange(boolean selfChange, Uri uri) {
update(uri);
}
private void update(Uri uri) {
if (uri == null || mSecureSettingsUri.equals(uri)) {
if (DEBUG) Slog.d(TAG, "Setting changed: mSecureSettingsUri=" + mSecureSettingsUri +
" / uri=" + uri);
rebindServices();
}
}
這里只響應(yīng)Null和Settings.Secure.ENABLED_NOTIFICATION_LISTENERS
rebindServices看名字就能猜到是一個(gè)bind services的操作
rebindServices
private void rebindServices() {
...
final SparseArray<String> flat = new SparseArray<String>();
//根據(jù)不同用戶越锈,讀取setting數(shù)據(jù)庫中對(duì)應(yīng)的值
for (int i = 0; i < nUserIds; ++i) {
flat.put(userIds[i], Settings.Secure.getStringForUser(
mContext.getContentResolver(),
mConfig.secureSettingName,
userIds[i]));
}
...
for (int i = 0; i < nUserIds; ++i) {
String toDecode = flat.get(userIds[i]);
if (toDecode != null) {
//使用冒號(hào)作為分割符號(hào),保存已經(jīng)開啟了服務(wù)的ComponentName
String[] components = toDecode.split(ENABLED_SERVICES_SEPARATOR);
for (int j = 0; j < components.length; j++) {
final ComponentName component
= ComponentName.unflattenFromString(components[j]);
if (component != null) {
...
add.add(component);
}
}
}
}
...
final int N = add.size();
for (int j = 0; j < N; j++) {
final ComponentName component = add.get(j);
Slog.v(TAG, "enabling " + getCaption() + " for user " + userIds[i] + ": "
+ component);
//注冊(cè)每一個(gè)授權(quán)了的ComponentName
registerService(component, userIds[i]);
}
...
}
由插曲可以知道膘滨,數(shù)據(jù)庫中的字符串是以冒號(hào)的形式的拼接的甘凭,所以這里讀取出來后會(huì)以冒號(hào)的形式進(jìn)行分割。
從代碼可以看出火邓,這里是區(qū)分了不同用戶的丹弱,畢竟android現(xiàn)在已經(jīng)支持了多用戶。
registerService
這個(gè)方法就不細(xì)說了铲咨,實(shí)現(xiàn)十分簡單躲胳,就是調(diào)用了bindServiceAsUser
方法,正式啟動(dòng)了服務(wù)
mContext.bindServiceAsUser(intent,
new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
...
try {
...
info = newServiceInfo(mService, name,
userid, false /*isSystem*/, this, targetSdkVersion);
added = mServices.add(info);
} catch (RemoteException e) {}
...
}
},
...)
關(guān)于問題二的答案就是在上面.
服務(wù)bind成功以后纤勒,app中的監(jiān)聽服務(wù)代理對(duì)象會(huì)保存在ManagedServices
的mServices(ArrayList數(shù)據(jù)結(jié)構(gòu))中.
流程圖
接受通知
上面講解了三方app中監(jiān)聽通知欄服務(wù)啟動(dòng)的過程坯苹,那么系統(tǒng)中有了通知來了以后,是如何回調(diào)到三方app中的呢摇天?
這就不得不看下Notification之----Android5.0實(shí)現(xiàn)原理(二)粹湃,由于篇幅原因這里簡單說下。
- app通過notify方法 借助NotificationMange(NM)將通知傳遞給NotificationManagerService(NMS)
- NMS接受到該通知后泉坐,遍歷ManagedServices中注冊(cè)了的listener为鳄,并且調(diào)用回調(diào)方法
- 監(jiān)聽方回調(diào)onNotificationPosted方法
相關(guān)閱讀
Notification之----Android5.0實(shí)現(xiàn)原理(二)
Notification之----Android5.0實(shí)現(xiàn)原理(一)
Notification之----自定義樣式
Notification之----默認(rèn)樣式
Notification之----任務(wù)棧