通知栏显示流程

1、相关代码:

frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\StatusBar.java
frameworks\base\packages\SystemUI\src\com\android\systemui\statusbar\phone\NotificationListenerWithPlugins.java
frameworks\base\core\java\android\service\notification\NotificationListenerService.java
frameworks\base\core\java\android\app\Notification.java
frameworks\base\core\java\android\app\NotificationManager.java
frameworks\base\services\core\java\com\android\server\notification\NotificationManagerService.java

2、通知栏显示示例代码如下:
    NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    NotificationChannel channel = new NotificationChannel("thhtest",
                "com.thh.test", NotificationManager.IMPORTANCE_LOW);
        channel.enableLights(true);
        channel.setShowBadge(true);
        channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
        notificationManager.createNotificationChannel(channel);

        Notification noti = new Notification.Builder(getApplicationContext())
                            .setContentTitle("测试通知栏")
                            .setContentText("测试通知栏内容")
                            .setSmallIcon(R.mipmap.ic_launcher)
                            .setChannelId("thhtest")
                            .build();
        notificationManager.notify(1001,noti);

以上就是最为简单的通知栏使用方式,显示效果仅仅是一个标题一个信息,均不超过一行,其余都是使用系统默认。

3、Notification分析:

Notification作为主要通知信息的载体,包含了所有通知信息的设置,如标题、信息、图标、点击后的intent、删除是的intent、提示音、led灯、自定义的view、大字体风格、添加按钮等等:

//通知栏点击时候触发的intent
public PendingIntent contentIntent;
//通知栏删除时候触发的intent
public PendingIntent deleteIntent;
//设置该intent,则效果类似短信提醒,会先在屏幕顶部弹出个小banner显示通知栏
public PendingIntent fullScreenIntent;
//6.0开始已经不再显示了
public CharSequence tickerText;
//6.0开始已经不再显示了
public RemoteViews tickerView;
//自定义显示的通知栏
public RemoteViews contentView;
//大版本的自定义通知栏,可以允许显示更多信息
public RemoteViews bigContentView;
//多媒体版本的自定义通知栏,可以添加一些案件
public RemoteViews headsUpContentView;
//大的图片用来显示在通知栏区域
public Bitmap largeIcon;
//通知提示音
public Uri sound;

//给通知栏添加的按钮,Notification.Builder#addAction(Notification.Action)
public static class Action
//通知栏信息载体,6.0后主要使用该方式:
* Notification noti = new Notification.Builder(mContext)
*         .setContentTitle("New mail from " + sender.toString())
*         .setContentText(subject)
*         .setSmallIcon(R.drawable.new_mail)
*         .setLargeIcon(aBitmap)
*         .build();
public static class Builder
//大图片style:
     * Notification notif = new Notification.Builder(mContext)
     *     .setContentTitle("New photo from " + sender.toString())
     *     .setContentText(subject)
     *     .setSmallIcon(R.drawable.new_post)
     *     .setLargeIcon(aBitmap)
     *     .setStyle(new Notification.BigPictureStyle()
     *         .bigPicture(aBigBitmap))
     *     .build();
public static class BigPictureStyle
//大文本style,可以显示下更多的内容
     * Notification notif = new Notification.Builder(mContext)
     *     .setContentTitle("New mail from " + sender.toString())
     *     .setContentText(subject)
     *     .setSmallIcon(R.drawable.new_mail)
     *     .setLargeIcon(aBitmap)
     *     .setStyle(new Notification.BigTextStyle()
     *         .bigText(aVeryLongString))
     *     .build();
public static class BigTextStyle
//信息排列模式,将显示信息一条条排列:
     * Notification noti = new Notification.Builder()
     *     .setContentTitle("2 new messages wtih " + sender.toString())
     *     .setContentText(subject)
     *     .setSmallIcon(R.drawable.new_message)
     *     .setLargeIcon(aBitmap)
     *     .setStyle(new Notification.MessagingStyle(resources.getString(R.string.reply_name))
     *         .addMessage(messages[0].getText(), messages[0].getTime(), messages[0].getSender())
     *         .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getSender()))
     *     .build();
public static class MessagingStyle

4、通知栏显示流程分析
4.1 NotificationManager

调用nofity之后,进入notifyAsUser,如下:

public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    {
        int[] idOut = new int[1];
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        //补充notification的EXTRA_BUILDER_APPLICATION_INFO和EXTRA_ORIGINATING_USERID信息
        Notification.addFieldsFromContext(mContext, notification);
        //校验提示音文件可行性
        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                notification.sound.checkFileUriExposed("Notification.sound");
            }
        }
        //修复smallIcon设置
        fixLegacySmallIcon(notification, pkg);
        //5.0后没有设置smallIcon的不让显示
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }
        //如果设置了style又设置了contentView,则去掉contentView使用系统默认布局
        final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
        try {
            //进入NotificationManagerService服务发送通知
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    copy, idOut, user.getIdentifier());
            if (localLOGV && id != idOut[0]) {
                Log.v(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
4.2 NotificationManagerService

enqueueNotificationInternal函数分析如下:
1、检查当前调用者是系统身份或者同一app
2、检查各参数,notification、pkg
3、完善notification信息
4、注册活动跟踪监听mUsageStats
5、检查channel,如无则toast提示
6、检查flag与whitelist
7、进入发送流程

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) {
        //检查调用者是系统还是同一app
        checkCallerIsSystemOrSameApp(pkg);

        final int userId = ActivityManager.handleIncomingUser(callingPid,
                callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
        final UserHandle user = new UserHandle(userId);
        //检查包名与notification不能为空
        if (pkg == null || notification == null) {
            throw new IllegalArgumentException("null not allowed: pkg=" + pkg
                    + " id=" + id + " notification=" + notification);
        }
        // The system can post notifications for any package, let us resolve that.
        final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId);

        //补充notification信息
        try {
            final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                    pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                    (userId == UserHandle.USER_ALL) ? UserHandle.USER_SYSTEM : userId);
            
            Notification.addFieldsFromContext(ai, notification);

            int canColorize = mPackageManagerClient.checkPermission(
                    android.Manifest.permission.USE_COLORIZED_NOTIFICATIONS, pkg);
            if (canColorize == PERMISSION_GRANTED) {
                notification.flags |= Notification.FLAG_CAN_COLORIZE;
            } else {
                notification.flags &= ~Notification.FLAG_CAN_COLORIZE;
            }

        } catch (NameNotFoundException e) {
            Slog.e(TAG, "Cannot create a context for sending app", e);
            return;
        }
        //注册通知跟踪,跟踪通知活动和显示、用户交互等
        mUsageStats.registerEnqueuedByApp(pkg);

        // 检查channelId信息,如果缺少,则不允许通知
        String channelId = notification.getChannelId();
        if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
            channelId = (new Notification.TvExtender(notification)).getChannelId();
        }
        final NotificationChannel channel = mRankingHelper.getNotificationChannel(pkg,
                notificationUid, channelId, false /* includeDeleted */);
        if (channel == null) {
            final String noChannelStr = "No Channel found for "
                    + "pkg=" + pkg
                    + ", channelId=" + channelId
                    + ", id=" + id
                    + ", tag=" + tag
                    + ", opPkg=" + opPkg
                    + ", callingUid=" + callingUid
                    + ", userId=" + userId
                    + ", incomingUserId=" + incomingUserId
                    + ", notificationUid=" + notificationUid
                    + ", notification=" + notification;
            Log.e(TAG, noChannelStr);
            doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
                    "Failed to post notification on channel \"" + channelId + "\"\n" +
                    "See log for more details");
            return;
        }
        
        final StatusBarNotification n = new StatusBarNotification(
                pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                user, null, System.currentTimeMillis());
        final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
        
        if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
                && (channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
                && (r.getImportance() == IMPORTANCE_MIN || r.getImportance() == IMPORTANCE_NONE)) {
            // Increase the importance of foreground service notifications unless the user had an
            // opinion otherwise
            if (TextUtils.isEmpty(channelId)
                    || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
                r.setImportance(IMPORTANCE_LOW, "Bumped for foreground service");
            } else {
                channel.setImportance(IMPORTANCE_LOW);
                mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
                r.updateNotificationChannel(channel);
            }
        }

        if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                r.sbn.getOverrideGroupKey() != null)) {
            return;
        }

        // Whitelist pending intents.
        if (notification.allPendingIntents != null) {
            final int intentCount = notification.allPendingIntents.size();
            if (intentCount > 0) {
                final ActivityManagerInternal am = LocalServices
                        .getService(ActivityManagerInternal.class);
                final long duration = LocalServices.getService(
                        DeviceIdleController.LocalService.class).getNotificationWhitelistDuration();
                for (int i = 0; i < intentCount; i++) {
                    PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
                    if (pendingIntent != null) {
                        am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(),
                                WHITELIST_TOKEN, duration);
                    }
                }
            }
        }
        启动线程发送通知
        mHandler.post(new EnqueueNotificationRunnable(userId, r));
    }

EnqueueNotificationRunnable分析如下:
1、处理通知记录的排名信息
2、根据FLAG_GROUP_SUMMARY字段信息处理通知栏群组信息
3、处理阻塞app信息
4、添加通知跟踪信息
5、添加到当前通知列表中
6、处理zen mode下灭屏时候的通知
7、重新对通知列表进行排列
8、发送通知
9、如果有提示音,发出提示音

@Override
        public void run() {

            synchronized (mNotificationList) {
                final StatusBarNotification n = r.sbn;
                
                NotificationRecord old = mNotificationsByKey.get(n.getKey());
                if (old != null) {
                    // 从现有记录中保留排名信息
                    r.copyRankingInformation(old);
                }

                final int callingUid = n.getUid();
                final int callingPid = n.getInitialPid();
                final Notification notification = n.getNotification();
                final String pkg = n.getPackageName();
                final int id = n.getId();
                final String tag = n.getTag();
                final boolean isSystemNotification = isUidSystem(callingUid) ||
                        ("android".equals(pkg));

               
                // 处理通知分组信息
                handleGroupedNotificationLocked(r, old, callingUid, callingPid);

                // This conditional is a dirty hack to limit the logging done on
                //     behalf of the download manager without affecting other apps.
                if (!pkg.equals("com.android.providers.downloads")
                        || Log.isLoggable("DownloadManager", Log.VERBOSE)) {
                    int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
                    if (old != null) {
                        enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
                    }
                    EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
                            pkg, id, tag, userId, notification.toString(),
                            enqueueStatus);
                }

                mRankingHelper.extractSignals(r);

                final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);

                //没有通知权限、当前app是suspend状态,当前notification优先级是None
                //则阻塞该通知,不发送。
                if (r.getImportance() == NotificationListenerService.Ranking.IMPORTANCE_NONE
                        || !noteNotificationOp(pkg, callingUid) || isPackageSuspended) {
                    if (!isSystemNotification) {
                        if (isPackageSuspended) {
                            mUsageStats.registerSuspendedByAdmin(r);
                        } else {
                            mUsageStats.registerBlocked(r);
                        }
                        return;
                    }
                }

                // 将该通知加入排名服务中
                if (mRankerServices.isEnabled()) {
                    mRankerServices.onNotificationEnqueued(r);
                }

                int index = indexOfNotificationLocked(n.getKey());
                if (index < 0) {
                    mNotificationList.add(r);
                    mUsageStats.registerPostedByApp(r);
                } else {
                    old = mNotificationList.get(index);
                    mNotificationList.set(index, r);
                    mUsageStats.registerUpdatedByApp(r, old);
                    // Make sure we don't lose the foreground service state.
                    notification.flags |=
                            old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
                    r.isUpdate = true;
                }
                //加入到现有的通知列表中
                mNotificationsByKey.put(n.getKey(), r);

                // Ensure if this is a foreground service that the proper additional
                // flags are set.
                if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
                    notification.flags |= Notification.FLAG_ONGOING_EVENT
                            | Notification.FLAG_NO_CLEAR;
                }
                //处理zen mode
                applyZenModeLocked(r);
                mRankingHelper.sort(mNotificationList);

                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);
                    }
                    // ATTENTION: in a future release we will bail out here
                    // so that we do not play sounds, show lights, etc. for invalid
                    // notifications
                    Slog.e(TAG, "WARNING: In a future release this will crash the app: "
                            + n.getPackageName());
                }
                //发出通知提示音
                buzzBeepBlinkLocked(r);
            }
        }

notifyPostedLocked流程如下:

public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
            // Lazily initialized snapshots of the notification.
            TrimCache trimCache = new TrimCache(sbn);

            for (final ManagedServiceInfo info : mServices) {
                boolean sbnVisible = isVisibleToListener(sbn, info);
                boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
                // This notification hasn't been and still isn't visible -> ignore.
                if (!oldSbnVisible && !sbnVisible) {
                    continue;
                }
                final NotificationRankingUpdate update = makeRankingUpdateLocked(info);

                // This notification became invisible -> remove the old one.
                if (oldSbnVisible && !sbnVisible) {
                    final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            notifyRemoved(info, oldSbnLightClone, update);
                        }
                    });
                    continue;
                }

                final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        notifyPosted(info, sbnToPost, update);
                    }
                });
            }
        }
        
private void notifyPosted(final ManagedServiceInfo info,
                final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
            final INotificationListener listener = (INotificationListener)info.service;
            StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
            try {
            //进入systemui显示该通知
                listener.onNotificationPosted(sbnHolder, rankingUpdate);
            } catch (RemoteException ex) {
                Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
            }
        }
4.3 mListeners注册流程

systemui中:
NotificationListenerWithPlugins继承自NotificationListenerService
在StatusBar中,则是新建了NotificationListenerWithPlugins,并且重载了onListenerConnected、onNotificationPosted、onNotificationRemoved、onNotificationRankingUpdate四个方法,并且进行了注册registerAsSystemService。

public class NotificationListenerWithPlugins extends NotificationListenerService{
    
     @Override
    public void registerAsSystemService(Context context, ComponentName componentName,
            int currentUser) throws RemoteException {
        super.registerAsSystemService(context, componentName, currentUser);
        Dependency.get(PluginManager.class).addPluginListener(this,
                NotificationListenerController.class);
    }

    @Override
    public void unregisterAsSystemService() throws RemoteException {
        super.unregisterAsSystemService();
        Dependency.get(PluginManager.class).removePluginListener(this);
    }
}

public class StatusBar extends SystemUI {
    private final NotificationListenerWithPlugins mNotificationListener =
            new NotificationListenerWithPlugins() {
        @Override
        public void onListenerConnected() {
            if (DEBUG) Log.d(TAG, "onListenerConnected");
            onPluginConnected();
            final StatusBarNotification[] notifications = getActiveNotifications();
            if (notifications == null) {
                Log.w(TAG, "onListenerConnected unable to get active notifications.");
                return;
            }
            final RankingMap currentRanking = getCurrentRanking();
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    for (StatusBarNotification sbn : notifications) {
                        try {
                            addNotification(sbn, currentRanking);
                        } catch (InflationException e) {
                            handleInflationException(sbn, e);
                        }
                    }
                }
            });
        }

        @Override
        public void onNotificationPosted(final StatusBarNotification sbn,
                final RankingMap rankingMap) {
            if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
            if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        processForRemoteInput(sbn.getNotification());
                        String key = sbn.getKey();
                        mKeysKeptForRemoteInput.remove(key);
                        boolean isUpdate = mNotificationData.get(key) != null;
                        // In case we don't allow child notifications, we ignore children of
                        // notifications that have a summary, since we're not going to show them
                        // anyway. This is true also when the summary is canceled,
                        // because children are automatically canceled by NoMan in that case.
                        if (!ENABLE_CHILD_NOTIFICATIONS
                            && mGroupManager.isChildInGroupWithSummary(sbn)) {
                            if (DEBUG) {
                                Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
                            }

                            // Remove existing notification to avoid stale data.
                            if (isUpdate) {
                                removeNotification(key, rankingMap);
                            } else {
                                mNotificationData.updateRanking(rankingMap);
                            }
                            return;
                        }
                        try {
                            if (isUpdate) {
                                updateNotification(sbn, rankingMap);
                            } else {
                                addNotification(sbn, rankingMap);
                            }
                        } catch (InflationException e) {
                            handleInflationException(sbn, e);
                        }
                    }
                });
            }
        }

        @Override
        public void onNotificationRemoved(StatusBarNotification sbn,
                final RankingMap rankingMap) {
            if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
            if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
                final String key = sbn.getKey();
                mHandler.post(() -> removeNotification(key, rankingMap));
            }
        }

        @Override
        public void onNotificationRankingUpdate(final RankingMap rankingMap) {
            if (DEBUG) Log.d(TAG, "onRankingUpdate");
            if (rankingMap != null) {
                RankingMap r = onPluginRankingUpdate(rankingMap);
                mHandler.post(() -> updateNotificationRanking(r));
            }
        }

    };
    
    //调用注册方法,
     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);
        }
}

NotificationListenerWithPlugins中registerAsSystemService如下:

@Override
    public void registerAsSystemService(Context context, ComponentName componentName,
            int currentUser) throws RemoteException {
        super.registerAsSystemService(context, componentName, currentUser);
        Dependency.get(PluginManager.class).addPluginListener(this,
                NotificationListenerController.class);
    }

NotificationListenerService中的方法如下:

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

NotificationManagerService中方法如下:

        /**
         * Register a listener binder directly with the notification manager.
         *
         * Only works with system callers. Apps should extend
         * {@link android.service.notification.NotificationListenerService}.
         */
        @Override
        public void registerListener(final INotificationListener listener,
                final ComponentName component, final int userid) {
            enforceSystemOrSystemUI("INotificationManager.registerListener");
            mListeners.registerService(listener, component, userid);
        }
        
        

最终在ManagedServices中将该listener加入到mServices列表中:

protected final ArrayList<ManagedServiceInfo> mServices = new ArrayList<ManagedServiceInfo>();

private ManagedServiceInfo registerServiceImpl(ManagedServiceInfo info) {
        synchronized (mMutex) {
            try {
                info.service.asBinder().linkToDeath(info, 0);
                mServices.add(info);
                return info;
            } catch (RemoteException e) {
                // already dead
            }
        }
        return null;
    }

自此,完成了通知栏监听器的注册,以后每次系统发送通知,就会回调到systemui中的mNotificationListener的回调方法中,然后在systemui中完成通知栏的显示。

猜你喜欢

转载自blog.csdn.net/thh159/article/details/88290198