Android?NotificationListenerService?通知服務(wù)原理解析
前言
在上一篇通知服務(wù)NotificationListenerService使用方法 中,我們已經(jīng)介紹了如何使用NotificationListenerService來監(jiān)聽消息通知,在最后我們還模擬了如何實(shí)現(xiàn)微信自動(dòng)搶紅包功能。
那么NotificationListenerService是如何實(shí)現(xiàn)系統(tǒng)通知監(jiān)聽的呢?(本篇源碼分析基于API 32)
NotificationListenerService方法集
NotificationLisenerService是Service的子類
public abstract class NotificationListenerService extends Service
除了Service的方法屬性外,NotificationListenerService還為我們提供了收到通知、通知被移除、連接到通知管理器等方法,如下圖所示。
一般業(yè)務(wù)中我們只關(guān)注有標(biāo)簽的那四個(gè)方法即可。
NotificationListenerService接收流程
既然NotificationListenerService是繼承自Service的,我們先來看它的onBind方法,代碼如下所示。
@Override public IBinder onBind(Intent intent) { if (mWrapper == null) { mWrapper = new NotificationListenerWrapper(); } return mWrapper; }
在onBind方法中返回了一個(gè)NotificationListenerWrapper實(shí)例,NotificationListenerWrapper對(duì)象是定義在NotificationListenerService中的一個(gè)內(nèi)部類。主要方法如下所示。
/** @hide */ protected class NotificationListenerWrapper extends INotificationListener.Stub { @Override public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update) { StatusBarNotification sbn; try { sbn = sbnHolder.get(); } catch (RemoteException e) { Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e); return; } if (sbn == null) { Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification"); return; } try { // convert icon metadata to legacy format for older clients createLegacyIconExtras(sbn.getNotification()); maybePopulateRemoteViews(sbn.getNotification()); maybePopulatePeople(sbn.getNotification()); } catch (IllegalArgumentException e) { // warn and drop corrupt notification Log.w(TAG, "onNotificationPosted: can't rebuild notification from " + sbn.getPackageName()); sbn = null; } // protect subclass from concurrent modifications of (@link mNotificationKeys}. synchronized (mLock) { applyUpdateLocked(update); if (sbn != null) { SomeArgs args = SomeArgs.obtain(); args.arg1 = sbn; args.arg2 = mRankingMap; mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED, args).sendToTarget(); } else { // still pass along the ranking map, it may contain other information mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_RANKING_UPDATE, mRankingMap).sendToTarget(); } } ...省略onNotificationRemoved等方法 }
NotificationListenerWrapper繼承自INotificationListener.Stub,當(dāng)我們看到Stub這一關(guān)鍵字的時(shí)候,就應(yīng)該知道這里是使用AIDL實(shí)現(xiàn)了跨進(jìn)程通信。
在NotificationListenerWrapper的onNotificationPosted中通過代碼
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED, args).sendToTarget();
將消息發(fā)送出去,handler接受后,又調(diào)用NotificationListernerService的onNotificationPosted方法,進(jìn)而實(shí)現(xiàn)通知消息的監(jiān)聽。代碼如下所示。
private final class MyHandler extends Handler { public static final int MSG_ON_NOTIFICATION_POSTED = 1; @Override public void handleMessage(Message msg) { if (!isConnected) { return; } switch (msg.what) { case MSG_ON_NOTIFICATION_POSTED: { SomeArgs args = (SomeArgs) msg.obj; StatusBarNotification sbn = (StatusBarNotification) args.arg1; RankingMap rankingMap = (RankingMap) args.arg2; args.recycle(); onNotificationPosted(sbn, rankingMap); } break; ... } } }
那么,消息通知發(fā)送時(shí),又是如何與NotificationListenerWrapper通信的呢?
通知消息發(fā)送流程
當(dāng)客戶端發(fā)送一個(gè)通知的時(shí)候,會(huì)調(diào)用如下所示的代碼
notificationManager.notify(1, notification)
notify又會(huì)調(diào)用notifyAsUser方法,代碼如下所示
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) { INotificationManager service = getService(); String pkg = mContext.getPackageName(); try { if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id, fixNotification(notification), user.getIdentifier()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }
緊接著又會(huì)走到INotificationManager的enqueueNotificationWithTag方法中,enqueueNotificationWithTag是聲明在INotificationManager.aidl文件中的接口
/** {@hide} */ interface INotificationManager { @UnsupportedAppUsage void cancelAllNotifications(String pkg, int userId); ... void cancelToast(String pkg, IBinder token); void finishToken(String pkg, IBinder token); void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, in Notification notification, int userId); ... }
這個(gè)接口是在NotificationManagerService中實(shí)現(xiàn)的,接著我們轉(zhuǎn)到NotificationManagerService中去查看,相關(guān)主要代碼如下所示。
@VisibleForTesting final IBinder mService = new INotificationManager.Stub() { @Override public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id, Notification notification, int userId) throws RemoteException { enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(), Binder.getCallingPid(), tag, id, notification, userId); } }
enqueueNotificationWithTag方法會(huì)走進(jìn)enqueueNotificationInternal方法,在方法最后會(huì)通過Handler發(fā)送一個(gè)EnqueueNotificationRunnable,代碼如下所示。
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid, final int callingPid, final String tag, final int id, final Notification notification, int incomingUserId, boolean postSilently) { ... //構(gòu)造StatusBarNotification,用于分發(fā)監(jiān)聽服務(wù) final StatusBarNotification n = new StatusBarNotification( pkg, opPkg, id, tag, notificationUid, callingPid, notification, user, null, System.currentTimeMillis()); // setup local book-keeping String channelId = notification.getChannelId(); if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) { channelId = (new Notification.TvExtender(notification)).getChannelId(); } ... // 設(shè)置intent的白名點(diǎn),是否盛典、是否后臺(tái)啟動(dòng)等 if (notification.allPendingIntents != null) { final int intentCount = notification.allPendingIntents.size(); if (intentCount > 0) { final long duration = LocalServices.getService( DeviceIdleInternal.class).getNotificationAllowlistDuration(); for (int i = 0; i < intentCount; i++) { PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i); if (pendingIntent != null) { mAmi.setPendingIntentAllowlistDuration(pendingIntent.getTarget(), ALLOWLIST_TOKEN, duration, TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, REASON_NOTIFICATION_SERVICE, "NotificationManagerService"); mAmi.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(), ALLOWLIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER | FLAG_SERVICE_SENDER)); } } } } ... mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground)); }
EnqueueNotificationRunnable源碼如下所示。
protected class EnqueueNotificationRunnable implements Runnable { private final NotificationRecord r; private final int userId; private final boolean isAppForeground; EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground) { this.userId = userId; this.r = r; this.isAppForeground = foreground; } @Override public void run() { synchronized (mNotificationLock) { ... //將通知加入隊(duì)列 mEnqueuedNotifications.add(r); scheduleTimeoutLocked(r); ... if (mAssistants.isEnabled()) { mAssistants.onNotificationEnqueuedLocked(r); mHandler.postDelayed(new PostNotificationRunnable(r.getKey()), DELAY_FOR_ASSISTANT_TIME); } else { mHandler.post(new PostNotificationRunnable(r.getKey())); } } } }
在EnqueueNotificationRunnable最后又會(huì)發(fā)送一個(gè)PostNotificationRunable,
PostNotificationRunable源碼如下所示。
protected class PostNotificationRunnable implements Runnable { private final String key; PostNotificationRunnable(String key) { this.key = key; } @Override public void run() { synchronized (mNotificationLock) { try { ... //發(fā)送通知 if (notification.getSmallIcon() != null) { StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; mListeners.notifyPostedLocked(r, old); if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) && !isCritical(r)) { mHandler.post(new Runnable() { @Override public void run() { mGroupHelper.onNotificationPosted( n, hasAutoGroupSummaryLocked(n)); } }); } else if (oldSbn != null) { final NotificationRecord finalRecord = r; mHandler.post(() -> mGroupHelper.onNotificationUpdated( finalRecord.getSbn(), hasAutoGroupSummaryLocked(n))); } } else { //... } } finally { ... } } } }
從代碼中可以看出,PostNotificationRunable類中會(huì)調(diào)用notifyPostedLocked方法,這里你可能會(huì)有疑問:這里分明判斷notification.getSmallIcon()是否為null,不為null時(shí)才會(huì)進(jìn)入notifyPostedLocked方法。為什么這里直接默認(rèn)了呢?這是因?yàn)樵贏ndroid5.0中規(guī)定smallIcon不可為null,且NotificationListenerService僅適用于5.0以上,所以這里是必然會(huì)執(zhí)行到notifyPostedLocked方法的。
其方法源碼如下所示。
private void notifyPostedLocked(NotificationRecord r, NotificationRecord old, boolean notifyAllListeners) { try { // Lazily initialized snapshots of the notification. StatusBarNotification sbn = r.getSbn(); StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; TrimCache trimCache = new TrimCache(sbn); //循環(huán)通知每個(gè)ManagedServiceInfo對(duì)象 for (final ManagedServiceInfo info : getServices()) { ... mHandler.post(() -> notifyPosted(info, sbnToPost, update)); } } catch (Exception e) { Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e); } }
notifyPostedLocked方法最終會(huì)調(diào)用notifyPosted方法,我們?cè)賮砜磏otifyPosted方法。
private void notifyPosted(final ManagedServiceInfo info, final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) { final INotificationListener listener = (INotificationListener) info.service; StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn); try { listener.onNotificationPosted(sbnHolder, rankingUpdate); } catch (RemoteException ex) { Slog.e(TAG, "unable to notify listener (posted): " + info, ex); } }
notifyPosted方法,最終會(huì)調(diào)用INotificationListerner的onNotificationPosted方法,這樣就通知到了NotificationListenerService的onNotificationPosted方法。
上述方法的流程圖如下圖所示。
NotificationListenerService注冊(cè)
在NotificationListenerService中通過registerAsSystemService方法注冊(cè)服務(wù),代碼如下所示。
@SystemApi public void registerAsSystemService(Context context, ComponentName componentName, int currentUser) throws RemoteException { if (mWrapper == null) { mWrapper = new NotificationListenerWrapper(); } mSystemContext = context; INotificationManager noMan = getNotificationInterface(); mHandler = new MyHandler(context.getMainLooper()); mCurrentUser = currentUser; noMan.registerListener(mWrapper, componentName, currentUser); }
registerAsSystemService方法將NotificationListenerWrapper對(duì)象注冊(cè)到NotificationManagerService中。如此就實(shí)現(xiàn)了對(duì)系統(tǒng)通知的監(jiān)聽。
總結(jié)
NotificationListenerService實(shí)現(xiàn)對(duì)系統(tǒng)通知的監(jiān)聽可以概括為三步:
- NotificationListenerService將 NotificationListenerWrapper注冊(cè)到NotificationManagerService中。
- 當(dāng)有通知被發(fā)送時(shí) ,NotificationManagerService跨進(jìn)程通知到每個(gè)NotificationListenerWrapper。
- NotificationListenerWrapper中信息由NotificationListenerService類中的Handler中處理,從而調(diào)用NotificationListenerService中對(duì)應(yīng)的回調(diào)方法。
以上就是Android NotificationListenerService 通知服務(wù)原理解析的詳細(xì)內(nèi)容,更多關(guān)于Android NotificationListenerService的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Android?Service啟動(dòng)綁定流程詳解
- Android布局控件View?ViewRootImpl?WindowManagerService關(guān)系
- android?微信搶紅包工具AccessibilityService實(shí)現(xiàn)詳解
- Android?O對(duì)后臺(tái)Service限制詳解
- Android?NotificationListenerService通知監(jiān)聽服務(wù)使用
- Android開發(fā)InputManagerService創(chuàng)建與啟動(dòng)流程
- Android 10 啟動(dòng)之servicemanager源碼解析
- Android 開機(jī)自啟動(dòng)Service實(shí)現(xiàn)詳解
相關(guān)文章
Android 實(shí)現(xiàn)文字左右對(duì)齊
這篇文章主要介紹了Android 實(shí)現(xiàn)文字左右對(duì)齊效果的方法,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-05-05Android使用TextView實(shí)現(xiàn)無下劃線超鏈接的方法
這篇文章主要介紹了Android使用TextView實(shí)現(xiàn)無下劃線超鏈接的方法,結(jié)合實(shí)例形式分析了Android中TextView超鏈接去除下劃線的相關(guān)實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2016-08-08ExpandableListView實(shí)現(xiàn)手風(fēng)琴效果
這篇文章主要為大家詳細(xì)介紹了ExpandableListView實(shí)現(xiàn)手風(fēng)琴效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08android實(shí)現(xiàn)注冊(cè)頁(yè)面開發(fā)
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)注冊(cè)頁(yè)面開發(fā),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04Android實(shí)現(xiàn)Gesture手勢(shì)識(shí)別用法分析
這篇文章主要介紹了Android實(shí)現(xiàn)Gesture手勢(shì)識(shí)別用法,結(jié)合實(shí)例形式較為詳細(xì)的分析了Android基于Gesture實(shí)現(xiàn)手勢(shì)識(shí)別的原理與具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-09-09Android8.1 通過黑名單屏蔽系統(tǒng)短信和來電功能
最近小編接到一個(gè)新的需求,需要將8.1 設(shè)備的來電功能和短信功能都屏蔽掉,特殊產(chǎn)品就是特殊定制。接下來通過本文給大家介紹Android8.1 通過黑名單屏蔽系統(tǒng)短信和來電功能,需要的朋友參考下吧2019-05-05Android 沉浸式狀態(tài)欄與隱藏導(dǎo)航欄實(shí)例詳解
沉浸式狀態(tài)欄是指狀態(tài)欄與ActionBar顏色相匹配,隱藏導(dǎo)航欄,就是將導(dǎo)航欄隱藏,去掉下面的黑條。下面通過實(shí)例給大家詳解android沉浸式狀態(tài)欄與隱藏導(dǎo)航欄,感興趣的朋友一起看看2017-07-07Android實(shí)現(xiàn)屏幕旋轉(zhuǎn)方法總結(jié)
這篇文章主要介紹了Android實(shí)現(xiàn)屏幕旋轉(zhuǎn)方法,實(shí)例總結(jié)了屏幕旋轉(zhuǎn)的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-04-04