Android6.0 Notification工作原理源码解析(二)

末鹿安然 提交于 2019-12-05 04:44:14

时序图


通知的发送是通过NotificationManager的notify()方法:

NotificationManger->notify()
    public void notify(int id, Notification notification)
    {
        notify(null, id, notification);
    }

    public void notify(String tag, int id, Notification notification)
    {
        int[] idOut = new int[1];
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        ...
        try {
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    stripped, idOut, UserHandle.myUserId());
            if (id != idOut[0]) {
                Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
            }
        } catch (RemoteException e) {
        }
    }

/** @hide */
    static public INotificationManager getService()
    {
        if (sService != null) {
            return sService;
        }
        IBinder b = ServiceManager.getService("notification");
        sService = INotificationManager.Stub.asInterface(b);
        return sService;
    }

可以明显示的看到上面代码中的getService()通过Binder获取到NotificationManager对应的Service,按Android系统中的命令惯例即是 NotificationManagerService, 真正的处理在此Service中。

NotificationManagerService->enqueueNotificationWithTag()
enqueueNotificationWithTag() -> enqueueNotificationInternal():

void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
            final int callingPid, final String tag, final int id, final Notification notification,
            int[] idOut, int incomingUserId) {
...
        //检测是否是同一APP发出的,是个通知安全检查,有问题则抛出异常。
        checkCallerIsSystemOrSameApp(pkg);
        final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));  //是否为系统通知
        final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);  //所有系统服务与应用已注册的服务。
        ...

        if (!isSystemNotification && !isNotificationFromListener) {
            synchronized (mNotificationList) {
                int count = 0;
                final int N = mNotificationList.size();
                for (int i=0; i<N; i++) {
...
                        count++;
                        //最大50条
                        if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                            Slog.e(TAG, "Package has already posted " + count
                                    + " notifications.  Not showing more.  package=" + pkg);
                            return;
                        }
                    }
                }
            }
        }
...
        if (notification.getSmallIcon() != null) {
            if (!notification.isValid()) {
                throw new IllegalArgumentException("Invalid notification (): pkg=" + pkg
                        + " id=" + id + " notification=" + notification);
            }
        }

        mHandler.post(new Runnable() {
            @Override
            public void run() {

                synchronized (mNotificationList) {
...
                    // blocked apps 应用被设置不允许弹出通知
                    if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
                        if (!isSystemNotification) {
                            r.score = JUNK_SCORE;
                            Slog.e(TAG, "Suppressing notification from package " + pkg
                                    + " by user request.");
                            mUsageStats.registerBlocked(r);
                        }
                    }

                    if (r.score < SCORE_DISPLAY_THRESHOLD) {
                        // Notification will be blocked because the score is too low.
                        return;
                    }
                    if (notification.getSmallIcon() != null) {
                        StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                        //通知通知消息观察者有新通知或有通知更新。
                        mListeners.notifyPostedLocked(n, oldSbn);
                    } else {
                        Slog.e(TAG, "Not posting notification without small icon: " + notification);  //没图标的通知不显示。
                        if (old != null && !old.isCanceled) {
                            mListeners.notifyRemovedLocked(n);
                        }
...
                    }
                    //通知语言与震动相关处理
                    buzzBeepBlinkLocked(r);
                }
            }
        });

        idOut[0] = id;
    }

从上面的方法中我们可以得知: 
1. 如果当前应用不是系统应用并且不是已注册的服务的话,那么Android系统最多让同时存在50条通知消息。 
2. 应用被设置禁止弹出通知或通知没设置图标的话通知也不能被弹出。

NotificationListenerService->notifyPostedLocked()
通知是被上面的mListeners.notifyPostedLocked()->notifyPosted()->INotificationListener->onNotificationPosted()方法通知到各个服务的,INotificationListener对应的处理服务即是NotificationListenerService:

 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) {
                Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
            }
        }

BaseStatusBar中的NotificationListenerService实例接收通知消息
那么这是在哪里处理的吗? 
在Android源码中全局搜索下就可以找到以下代码:

private final NotificationListenerService mNotificationListener =
            new NotificationListenerService() {
...
@Override
        public void onNotificationPosted(final StatusBarNotification sbn,
                final RankingMap rankingMap) {
            if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
            if (sbn != null) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                            ...
                            //// Remove existing notification to avoid stale data.
                            if (isUpdate) {
                                removeNotification(key, rankingMap);
                            } else {
                                mNotificationData.updateRanking(rankingMap);  //排序
                            }
                            return;
                        }
                        if (isUpdate) {
                            updateNotification(sbn, rankingMap);
                        } else {
                            addNotification(sbn, rankingMap, null /* oldEntry */);  //创建新通知
                        }
                    }
                });
            }
        }
}

mNotificationListener即是NotificationListenerService的实例,它在BaseStatusBar的start方法中将此mNotificationListener关联到NotificationManagerService中:

//BaseStatusBar.java
public void start() {
    // Set up the initial notification state.
        try {
            mNotificationListener.registerAsSystemService(mContext,
                    new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
                    UserHandle.USER_ALL);
        } catch (RemoteException e) {
            Log.e(TAG, "Unable to register notification listener", e);
        }
}

//NotificationListenerService.java
@SystemApi
    public void registerAsSystemService(Context context, ComponentName componentName,
            int currentUser) throws RemoteException {
        mSystemContext = context;
        if (mWrapper == null) {
            mWrapper = new INotificationListenerWrapper();
        }
        INotificationManager noMan = getNotificationInterface();
        //即上一小节的final INotificationListener listener = (INotificationListener)info.service
        noMan.registerListener(mWrapper, componentName, currentUser);
        mCurrentUser = currentUser;
    }

PhoneStatusBar创建通知视图并显示
BaseStatusBar即是用来处理状态栏相关业务的类,继承BaseStatusBar的有PhoneStatusBar、TvStatusBar,看名字就可以得知PhoneStatusBar是用于手机屏幕而TvStatusBar是用于TV的,再继续看PhoneStatusBar中的处理:

//PhoneStatusBar.java
@Override
    public void addNotification(StatusBarNotification notification, RankingMap ranking,
            Entry oldEntry) {
...
        //创建通知对应的视图
        Entry shadeEntry = createNotificationViews(notification);
        if (shadeEntry == null) {
            return;
        }
        //收到通知时在屏幕上方弹出的通知提示相关处理。
        boolean isHeadsUped = mUseHeadsUp && shouldInterrupt(shadeEntry);
        if (isHeadsUped) {
            mHeadsUpManager.showNotification(shadeEntry);
            // Mark as seen immediately
            setNotificationShown(notification);
        }
...
        addNotificationViews(shadeEntry, ranking);
...
    }

上面的处理中可以很明显的看到通过我们在最初调用 NotificationManager.notify() 时创建出的StatusBarNotification来创建出一个用来状态栏通知显示的Entry,里面存有创建好的单个通知视图: 
createNotificationViews()->inflateViews():

//BaseStatusBar.java
protected boolean inflateViews(Entry entry, ViewGroup parent) {
        PackageManager pmUser = getPackageManagerForUser(
                entry.notification.getUser().getIdentifier());

        int maxHeight = mRowMaxHeight;
        final StatusBarNotification sbn = entry.notification;
        RemoteViews contentView = sbn.getNotification().contentView;
        ...
        // create the row view
            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
                    Context.LAYOUT_INFLATER_SERVICE);
            row = (ExpandableNotificationRow) inflater.inflate(R.layout.status_bar_notification_row,
                    parent, false);
         //绑定点击事件处理
          mNotificationClicker.register(row, sbn);
          ...
          //调用RemoteViews->apply()方法绑定视图
          contentViewLocal = contentView.apply(
                    sbn.getPackageContext(mContext),
                    contentContainer,
                    mOnClickHandler);
          ...
          entry.row.setHeightRange(mRowMinHeight, maxHeight);
          ...

从上面的代码中可以看到: 
1. 每个通知都有个高度范围,64dp-256dp。 
2. 通知的layout模板 R.layout.status_bar_notification_row 。 
3. PhoneStatusBar收到通知后最终调用RemoteViews->apply()来进行视图一绑定,证实了我们上一篇文章的推测。

后面的就将View加入到StatusBarView的NotificationStackScrollLayout中,StatusBarView是在BaseStatusBar->start()方法中被调用。
 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!