Android P通知发送源码分析

Notification源码分析

  • Android 0 以上发送一条消息的示例代码:
 
    @RequiresApi(api = Build.VERSION_CODES.O)
    public void sendNotification(View view) {
        String id = "channel_1";
        String des = "101";
        NotificationChannel channel = new NotificationChannel(id, des, NotificationManager.IMPORTANCE_MIN);
        notificationManager.createNotificationChannel(channel);
        Notification notification = new Notification.Builder(MainActivity.this, id)
                .setContentTitle("Base Notification View")
                .setContentText("您有一条新通知")
                .setSmallIcon(R.drawable.jd_icon)
                .setStyle(new Notification.MediaStyle())
                .setAutoCancel(false)
                .build();
        notificationManager.notify(1, notification);
    }
  • 发送通知的源码

应用调用Notification.notify()接口发送通知,然后会进入system_server进程,调用NotificationManagerService的enqueueNotificationWithTag()。

@UnsupportedAppUsage
    public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
    {
    	//获取NotificationManagerService
        INotificationManager service = getService();
        String pkg = mContext.getPackageName();
        // Fix the notification as best we can.
        主要是向notification加入应用信息和用户ID
        Notification.addFieldsFromContext(mContext, notification);

        if (notification.sound != null) {
            notification.sound = notification.sound.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                notification.sound.checkFileUriExposed("Notification.sound");
            }

        }
        //修复一些icon问题,如果没有就给他指定一个
        fixLegacySmallIcon(notification, pkg);
        if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
            if (notification.getSmallIcon() == null) {
                throw new IllegalArgumentException("Invalid notification (no valid small icon): "
                        + notification);
            }
        }
        if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
        notification.reduceImageSizes(mContext);

        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
        boolean isLowRam = am.isLowRamDevice();
        final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam,
                mContext);
        try {
        //通知信息发送给NotificationManagerService
            service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                    copy, user.getIdentifier());
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
  • 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 incomingUserId) {
        if (DBG) {
            Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
                    + " notification=" + notification);
        }
        //检查一下通知是否由系统发送过来的还是同一个通知发送过来的,会获取通知信息提交者的uid,
        //并与PackageManager中获取分配给拥有指定包名的应用程序UID比对
        checkCallerIsSystemOrSameApp(pkg);

        final int userId = ActivityManager.handleIncomingUser(callingPid,
                callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
        final UserHandle user = new UserHandle(userId);

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

        // Fix the notification as best we can.
        try {
            final ApplicationInfo ai = mPackageManagerClient.getApplicationInfoAsUser(
                    pkg, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
                    (userId == UserHandle.USER_ALL) ? 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);

        // setup local book-keeping
        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);
            boolean appNotificationsOff = mRankingHelper.getImportance(pkg, notificationUid)
                    == NotificationManager.IMPORTANCE_NONE;

            if (!appNotificationsOff) {
                doChannelWarningToast("Developer warning for package \"" + pkg + "\"\n" +
                        "Failed to post notification on channel \"" + channelId + "\"\n" +
                        "See log for more details");
            }
            return;
        }
		//创建一个StatusBarNotification对象,时间获取的是当前系统时间
        final StatusBarNotification n = new StatusBarNotification(
                pkg, opPkg, id, tag, notificationUid, callingPid, notification,
                user, null, System.currentTimeMillis());
        final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
        r.setIsAppImportanceLocked(mRankingHelper.getIsAppImportanceLocked(pkg, callingUid));

        if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
            final boolean fgServiceShown = channel.isFgServiceShown();
            if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
                        || !fgServiceShown)
                    && (r.getImportance() == IMPORTANCE_MIN
                            || r.getImportance() == IMPORTANCE_NONE)) {
                // Increase the importance of foreground service notifications unless the user had
                // an opinion otherwise (and the channel hasn't yet shown a fg service).
                if (TextUtils.isEmpty(channelId)
                        || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
                    r.setImportance(IMPORTANCE_LOW, "Bumped for foreground service");
                } else {
                    channel.setImportance(IMPORTANCE_LOW);
                    if (!fgServiceShown) {
                        channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
                        channel.setFgServiceShown(true);
                    }
                    mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
                    r.updateNotificationChannel(channel);
                }
            } else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
                    && !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
                channel.setFgServiceShown(true);
                r.updateNotificationChannel(channel);
            }
        }

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

        // 白名单待处理的intent
        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执行EnqueueNotificationRunnable
        mHandler.post(new EnqueueNotificationRunnable(userId, r));
    }
  • 通知消息具体的权限拦截代码
private int resolveNotificationUid(String opPackageName, int callingUid, int userId) {
        // The system can post notifications on behalf of any package it wants
        //系统应用发出的通知不受限制
        if (isCallerSystemOrPhone() && opPackageName != null && !"android".equals(opPackageName)) {
            try {
                return getContext().getPackageManager()
                        .getPackageUidAsUser(opPackageName, userId);
            } catch (NameNotFoundException e) {
                /* ignore */
            }
        }
        return callingUid;
    }
    /*
     * 
     *检查通知的规则
     * Has side effects.
     */
    private boolean checkDisqualifyingFeatures(int userId, int callingUid, int id, String tag,
            NotificationRecord r, boolean isAutogroup) {
        final String pkg = r.sbn.getPackageName();
        final boolean isSystemNotification =
                isUidSystemOrPhone(callingUid) || ("android".equals(pkg));
        final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);

		//除了系统的通知或者是来自于注册监听的通知, 限制所有的通知数量, 防止DOS攻击和处理泄露
        if (!isSystemNotification && !isNotificationFromListener) {
            synchronized (mNotificationLock) {
                if (mNotificationsByKey.get(r.sbn.getKey()) == null && isCallerInstantApp(pkg)) {
                    
                    throw new SecurityException("Instant app " + pkg
                            + " cannot create notifications");
                }

                // rate limit updates that aren't completed progress notifications
                if (mNotificationsByKey.get(r.sbn.getKey()) != null
                        && !r.getNotification().hasCompletedProgress()
                        && !isAutogroup) {

                    final float appEnqueueRate = mUsageStats.getAppEnqueueRate(pkg);
                    if (appEnqueueRate > mMaxPackageEnqueueRate) {
                        mUsageStats.registerOverRateQuota(pkg);
                        final long now = SystemClock.elapsedRealtime();
                        if ((now - mLastOverRateLogTime) > MIN_PACKAGE_OVERRATE_LOG_INTERVAL) {
                            Slog.e(TAG, "Package enqueue rate is " + appEnqueueRate
                                    + ". Shedding " + r.sbn.getKey() + ". package=" + pkg);
                            mLastOverRateLogTime = now;
                        }
                        return false;
                    }
                }

                // limit the number of outstanding notificationrecords an app can have
                int count = getNotificationCountLocked(pkg, userId, id, tag);
                if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                    mUsageStats.registerOverCountQuota(pkg);
                    Slog.e(TAG, "Package has already posted or enqueued " + count
                            + " notifications.  Not showing more.  package=" + pkg);
                    return false;
                }
            }
        }

        // snoozed apps
        if (mSnoozeHelper.isSnoozed(userId, pkg, r.getKey())) {
            MetricsLogger.action(r.getLogMaker()
                    .setType(MetricsProto.MetricsEvent.TYPE_UPDATE)
                    .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED));
            if (DBG) {
                Slog.d(TAG, "Ignored enqueue for snoozed notification " + r.getKey());
            }
            mSnoozeHelper.update(userId, r);
            savePolicyFile();
            return false;
        }


        // blocked apps
        if (isBlocked(r, mUsageStats)) {
            return false;
        }

        return true;
    }
protected boolean isBlocked(NotificationRecord r, NotificationUsageStats usageStats) {
        final String pkg = r.sbn.getPackageName();
        final int callingUid = r.sbn.getUid();
        // blocked apps
        // 这里是blocked的apps,就是你在settings中选择Block all的app,这里就会帮通知都屏蔽了
        final boolean isPackageSuspended = isPackageSuspendedForUser(pkg, callingUid);
        if (isPackageSuspended) {
            Slog.e(TAG, "Suppressing notification from package due to package "
                    + "suspended by administrator.");
            usageStats.registerSuspendedByAdmin(r);
            return isPackageSuspended;
        }
        final boolean isBlocked =
                mRankingHelper.isGroupBlocked(pkg, callingUid, r.getChannel().getGroup())
                || mRankingHelper.getImportance(pkg, callingUid)
                        == NotificationManager.IMPORTANCE_NONE
                || r.getChannel().getImportance() == NotificationManager.IMPORTANCE_NONE;
        if (isBlocked) {
            Slog.e(TAG, "Suppressing notification from package by user request.");
            usageStats.registerBlocked(r);
        }
        return isBlocked;
    }

上面的代码主要是过滤特殊通知处理,创建了StatusBarNotification、NotificationRecord对象,并防止DOS攻击,对单个应用(非系统应用)的通知条数做了未读消息只有50条的限制。

  • PostNotificationRunnable进行最后的推送通知
protected class EnqueueNotificationRunnable implements Runnable {
        private final NotificationRecord r;
        private final int userId;

        EnqueueNotificationRunnable(int userId, NotificationRecord r) {
            this.userId = userId;
            this.r = r;
        };

        @Override
        public void run() {
            synchronized (mNotificationLock) {
                mEnqueuedNotifications.add(r);
                scheduleTimeoutLocked(r);

                final StatusBarNotification n = r.sbn;
                if (DBG) Slog.d(TAG, "EnqueueNotificationRunnable.run for: " + n.getKey());
                //根据key值获取Old NotificationRecord
                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();

                // Handle grouped notifications and bail out early if we
                // can to avoid extracting signals.
                handleGroupedNotificationLocked(r, old, callingUid, callingPid);

                // if this is a group child, unsnooze parent summary
                if (n.isGroup() && notification.isGroupChild()) {
                    mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey());
                }

                // 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;
                    }
                    //打印Event Log,可通过该LOG查看当前通知的信息,判断当前通知是否发送成功;
                    EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
                            pkg, id, tag, userId, notification.toString(),
                            enqueueStatus);
                }
	 更新NotificationRecord内容,RankingHelper管理了通知的开关、channel维护,比如横幅通知是否开启,在此处更新;
                mRankingHelper.extractSignals(r);

                // 
                if (mAssistants.isEnabled()) {
                    mAssistants.onNotificationEnqueued(r);
                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                            DELAY_FOR_ASSISTANT_TIME);
                } else {
                    mHandler.post(new PostNotificationRunnable(r.getKey()));
                }
            }
        }
    }

这里我们需要关注有异步通信
启动NotificationManagerService的SystemServer的主线程,如下代码。

SystemServer.java

public static void main(String[] args) {
        new SystemServer().run();
    }
 private void run() {
// ......
 // Start services.
        try {
            traceBeginAndSlog("StartServices");
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
            SystemServerInitThreadPool.shutdown();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        } finally {
            traceEnd();
        }
// ......
}
 private void startOtherServices() {
// .......
  traceBeginAndSlog("StartNotificationManager");
            mSystemServiceManager.startService(NotificationManagerService.class);
            SystemNotificationChannels.createAll(context);
            notification = INotificationManager.Stub.asInterface(
                    ServiceManager.getService(Context.NOTIFICATION_SERVICE));
            traceEnd();
// .......
 }    

在NotificationManagerService处理通知消息的时候,使用了子线程。

public class NotificationManagerService extends SystemService {
//.......
private NotificationListeners mListeners;
private WorkerHandler mHandler;
private final HandlerThread mRankingThread = new HandlerThread("ranker",
            Process.THREAD_PRIORITY_BACKGROUND);
void init(.........){
mHandler = new WorkerHandler(looper);
        mRankingThread.start();
}            
//.......
 void enqueueNotificationInternal(......) {
 mHandler.post(new EnqueueNotificationRunnable(userId, r));
 }

protected class EnqueueNotificationRunnable implements Runnable {
// tell the assistant service about the notification
                if (mAssistants.isEnabled()) {
                    mAssistants.onNotificationEnqueued(r);
                    mHandler.postDelayed(new PostNotificationRunnable(r.getKey()),
                            DELAY_FOR_ASSISTANT_TIME);
                } else {
                    mHandler.post(new PostNotificationRunnable(r.getKey()));
                }
}
protected class PostNotificationRunnable implements Runnable {
//......
 if (notification.getSmallIcon() != null) {
  StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                        mListeners.notifyPostedLocked(r, old);
 }else{
  mListeners.notifyRemovedLocked(r, NotificationListenerService.REASON_ERROR, null);
 }
//....... 
}
 }

NotificationListeners 的notifyPosted中将通知推送至SystemUI中显示,在该方法中listener数据类型是NotificationListenerWrapper的代理对象,NotificationListenerWrapper在SystemUI进程,在此处listener是client, NotificationListenerWrapper是server。

  • 关于android8.0通知通道的代码,首先会创建通道,然后通过RankingHelper来更新Channels的信息,最终由NotificationListeners推送到SystemUI中。

 @Override
 public void createNotificationChannels(String pkg,
             ParceledListSlice channelsList) throws RemoteException {
            checkCallerIsSystemOrSameApp(pkg);
            createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList);
        }

private void createNotificationChannelsImpl(String pkg, int uid,
                ParceledListSlice channelsList) {
            List<NotificationChannel> channels = channelsList.getList();
            final int channelsSize = channels.size();
            for (int i = 0; i < channelsSize; i++) {
                final NotificationChannel channel = channels.get(i);
                Preconditions.checkNotNull(channel, "channel in list is null");
                mRankingHelper.createNotificationChannel(pkg, uid, channel,
                        true /* fromTargetApp */, mConditionProviders.isPackageOrComponentAllowed(
                                pkg, UserHandle.getUserId(uid)));
                mListeners.notifyNotificationChannelChanged(pkg,
                        UserHandle.getUserHandleForUid(uid),
                        mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), false),
                        NOTIFICATION_CHANNEL_OR_GROUP_ADDED);
            }
            //本地存储信息
            savePolicyFile();
        }
	//NotificationManagerService 在init的时候会调用此方法
    private void loadPolicyFile() {
        if (DBG) Slog.d(TAG, "loadPolicyFile");
        synchronized (mPolicyFile) {

            InputStream infile = null;
            try {
                infile = mPolicyFile.openRead();
                readPolicyXml(infile, false /*forRestore*/);
            } catch (FileNotFoundException e) {
                // No data yet
                // Load default managed services approvals
                readDefaultApprovedServices(USER_SYSTEM);
            } catch (IOException e) {
                Log.wtf(TAG, "Unable to read notification policy", e);
            } catch (NumberFormatException e) {
                Log.wtf(TAG, "Unable to parse notification policy", e);
            } catch (XmlPullParserException e) {
                Log.wtf(TAG, "Unable to parse notification policy", e);
            } finally {
                IoUtils.closeQuietly(infile);
            }
        }
    }

    public void savePolicyFile() {
        mHandler.removeMessages(MESSAGE_SAVE_POLICY_FILE);
        mHandler.sendEmptyMessage(MESSAGE_SAVE_POLICY_FILE);
    }

    private void handleSavePolicyFile() {
        if (DBG) Slog.d(TAG, "handleSavePolicyFile");
        synchronized (mPolicyFile) {
            final FileOutputStream stream;
            try {
                stream = mPolicyFile.startWrite();
            } catch (IOException e) {
                Slog.w(TAG, "Failed to save policy file", e);
                return;
            }

            try {
                writePolicyXml(stream, false /*forBackup*/);
                mPolicyFile.finishWrite(stream);
            } catch (IOException e) {
                Slog.w(TAG, "Failed to save policy file, restoring backup", e);
                mPolicyFile.failWrite(stream);
            }
        }
        BackupManager.dataChanged(getContext().getPackageName());
    }

时序图
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值