Android 10 通知-源码解析

创建通知

下面代码来自https://developer.android.google.cn/training/notify-user/channels

//默认情况下,通知的文本内容会被截断以放在一行。如果您想要更长的通知,可以使用 setStyle() 添加样式模板来启用可展开的通知。

    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.notification_icon)
            .setContentTitle("My notification")
            .setContentText("Much longer text that cannot fit one line...")
            .setStyle(new NotificationCompat.BigTextStyle()
                    .bigText("Much longer text that cannot fit one line..."))
            .setPriority(NotificationCompat.PRIORITY_DEFAULT);

 //Android 8.0 及更高版本上提供通知,必须先通过向 createNotificationChannel() 传递 NotificationChannel 的实例在系统中注册应用的通知渠道

    private void createNotificationChannel() {
        // Create the NotificationChannel, but only on API 26+ because
        // the NotificationChannel class is new and not in the support library
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            CharSequence name = getString(R.string.channel_name);
            String description = getString(R.string.channel_description);
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
            channel.setDescription(description);
            // Register the channel with the system; you can't change the importance
            // or other notification behaviors after this
            NotificationManager notificationManager = getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(channel);
        }
    }

//以下代码段展示了如何创建基本 Intent,以在用户点按通知时打开 Activity:

    // Create an explicit intent for an Activity in your app
    Intent intent = new Intent(this, AlertDetails.class);
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);

    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.notification_icon)
            .setContentTitle("My notification")
            .setContentText("Hello World!")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            // Set the intent that will fire when the user taps the notification
            .setContentIntent(pendingIntent)
            .setAutoCancel(true);
//如需显示通知,请调用 NotificationManagerCompat.notify(),并将通知的唯一 ID 和 NotificationCompat.Builder.build() 的结果传递给它。

    NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

    // notificationId is a unique int for each notification that you must define
    notificationManager.notify(notificationId, builder.build());

//如需添加操作按钮,请将 addAction() 传递给 PendingIntent 方法。

    Intent snoozeIntent = new Intent(this, MyBroadcastReceiver.class);
    snoozeIntent.setAction(ACTION_SNOOZE);
    snoozeIntent.putExtra(EXTRA_NOTIFICATION_ID, 0);
    PendingIntent snoozePendingIntent =
            PendingIntent.getBroadcast(this, 0, snoozeIntent, 0);

    NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.notification_icon)
            .setContentTitle("My notification")
            .setContentText("Hello World!")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .addAction(R.drawable.ic_snooze, getString(R.string.snooze),
                    snoozePendingIntent);
// 显示紧急消息
您的应用可能需要显示紧急的时效性消息,例如来电或响铃警报。在这些情况下,您可以将全屏 Intent 与通知关联。调用通知时,根据设备的锁定状态,用户会看到以下情况之一:

如果用户设备被锁定,会显示全屏 Activity,覆盖锁屏。
如果用户设备处于解锁状态,通知以展开形式显示,其中包含用于处理或关闭通知的选项。
紧急消息弹出屏幕,时效时间30s。

    Intent fullScreenIntent = new Intent(this, CallActivity.class);
    PendingIntent fullScreenPendingIntent = PendingIntent.getActivity(this, 0,
            fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);

    NotificationCompat.Builder notificationBuilder =
            new NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle("Incoming call")
        .setContentText("(919) 555-1234")
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .setCategory(NotificationCompat.CATEGORY_CALL)

        // Use a full-screen intent only for the highest-priority alerts where you
        // have an associated activity that you would like to launch after the user
        // interacts with the notification. Also, if your app targets Android 10
        // or higher, you need to request the USE_FULL_SCREEN_INTENT permission in
        // order for the platform to invoke this notification.
        .setFullScreenIntent(fullScreenPendingIntent, true);

    Notification incomingCallNotification = notificationBuilder.build();


渠道的重要性

通知发出流程

 

 

通知的类型

   com/android/systemui/statusbar/notification/row/NotificationContentInflater.java

   /** 默认的收缩视图。看到时,阴影被拉下来,并在锁屏,如果没有担心内容的敏感性。
     * The default, contracted view.  Seen when the shade is pulled down and in the lock screen
     * if there is no worry about content sensitivity.
     */
    public static final int FLAG_CONTENT_VIEW_CONTRACTED = 1;

    /** 展开的视图。在用户展开通知时看到。
     * The expanded view.  Seen when the user expands a notification.
     */
    public static final int FLAG_CONTENT_VIEW_EXPANDED = 1 << 1;

    /** 抬头视图  当一个高优先级的通知从顶部窥视进来时就会看到。
     * The heads up view.  Seen when a high priority notification peeks in from the top.
     */
    public static final int FLAG_CONTENT_VIEW_HEADS_UP = 1 << 2;

    /** 环境视图。当收到高优先级通知且手机休眠时出现。
     * The ambient view.  Seen when a high priority notification is received and the phone
     * is dozing.
     */
    public static final int FLAG_CONTENT_VIEW_AMBIENT = 1 << 3;

    /**公众视图。这是压缩视图的一个版本,它隐藏敏感信息,如果我们确定通知的内容应该被隐藏,它将在锁屏上使用。
     * The public view.  This is a version of the contracted view that hides sensitive
     * information and is used on the lock screen if we determine that the notification's
     * content should be hidden.
     */
    public static final int FLAG_CONTENT_VIEW_PUBLIC = 1 << 4;

通知监听器

 NotificationEntryListener监听接口,用于监听NotificationEntryManager 发出的通知变化
/**
 * Listener interface for changes sent by NotificationEntryManager.
 */
public interface NotificationEntryListener {
    /**在发布新通知时调用。在这一点上,通知是“待定的”:它的观点还没有被夸大,系统的大部分假装它还不存在。
     * Called when a new notification is posted. At this point, the notification is "pending": its
     * views haven't been inflated yet and most of the system pretends like it doesn't exist yet.
     */
    default void onPendingEntryAdded(NotificationEntry entry) {
    }

    // TODO: Combine this with onPreEntryUpdated into "onBeforeEntryFiltered" or similar
    /**在创建新条目时,但在对用户进行筛选或显示之前调用。
     * Called when a new entry is created but before it has been filtered or displayed to the user.
     */
    default void onBeforeNotificationAdded(NotificationEntry entry) {
    }

    /**创建新条目时调用。
     * Called when a new entry is created.
     */
    default void onNotificationAdded(NotificationEntry entry) {
    }

    /** 在通知即将更新时调用
     * Called when a notification is about to be updated. Notification- and ranking-derived fields
     * on the entry have already been updated but the following have not yet occurred:
     * (a) View binding (i.e. the associated view has not yet been updated / inflation has not yet
     *      been kicked off.
     * (b) Notification filtering
     */
    default void onPreEntryUpdated(NotificationEntry entry) {
    }

    /**在对通知进行任何筛选后更新通知时调用。
     * Called when a notification was updated, after any filtering of notifications have occurred.
     */
    default void onPostEntryUpdated(NotificationEntry entry) {
    }

    /** 在通知的视图首次填充时调用。
     * Called when a notification's views are inflated for the first time.
     */
    default void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) {
    }

    /**当现有通知的视图重新展开时调用(通常是由于向该通知发布了更新)。
     * Called when an existing notification's views are reinflated (usually due to an update being
     * posted to that notification).
     *
     * @param entry notification data entry that was reinflated.
     */
    default void onEntryReinflated(NotificationEntry entry) {
    }

    /**在为通知填充视图出错时调用。
     * Called when an error occurred inflating the views for a notification.
     */
    default void onInflationError(StatusBarNotification notification, Exception exception) {
    }

    /**当通知被删除时调用(可能是因为用户刷走了通知,也可能是因为开发人员收回了通知)。
     * Called when a notification has been removed (either because the user swiped it away or
     * because the developer retracted it).
     * @param entry notification data entry that was removed.  Null if no entry existed for the
     *              removed key at the time of removal.
     * @param visibility logging data related to the visibility of the notification at the time of
     *                   removal, if it was removed by a user action.  Null if it was not removed by
     *                   a user action.
     * @param removedByUser true if the notification was removed by a user action
     */
    default void onEntryRemoved(
            NotificationEntry entry,
            @Nullable NotificationVisibility visibility,
            boolean removedByUser) {
    }

    /**在通知排名更改时调用
     * Called whenever notification ranking changes, in response to
     * {@link NotificationListenerService#onNotificationRankingUpdate}. This is called after
     * NotificationData has processed the update and notifications have been re-sorted and filtered.
     *
     * @param rankingMap provides access to ranking information on currently active notifications
     */
    default void onNotificationRankingUpdated(RankingMap rankingMap) {
    }
}
NotificationAlertingManager 构造函数中给NotificationEntryManager 加 NotificationEntryListener ,以便在通知视图首次填充(onEntryInflated) 时 感知并弹出悬浮通知。
com/android/systemui/statusbar/notification/NotificationAlertingManager.java    

    notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
            @Override
            public void onEntryInflated(NotificationEntry entry, int inflatedFlags) {
                showAlertingView(entry, inflatedFlags);
            }

            @Override
            public void onPostEntryUpdated(NotificationEntry entry) {
                updateAlertState(entry);
            }

            @Override
            public void onEntryRemoved(
                    NotificationEntry entry,
                    NotificationVisibility visibility,
                    boolean removedByUser) {
                stopAlerting(entry.key);
            }
        });
    }

    /**如果内容视图已填充,并且条目仍应保持警报,则将条目添加到相应的警报管理器。
     * Adds the entry to the respective alerting manager if the content view was inflated and
     * the entry should still alert.
     *
     * @param entry         entry to add
     * @param inflatedFlags flags representing content views that were inflated
     */
    private void showAlertingView(NotificationEntry entry, @InflationFlag int inflatedFlags) {
        if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
            // Possible for shouldHeadsUp to change between the inflation starting and ending.
            // If it does and we no longer need to heads up, we should free the view.
            if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
                // 展示悬浮通知
                mHeadsUpManager.showNotification(entry);
                // Mark as seen immediately 标记为立即看到
                setNotificationShown(entry.notification);
            } else {
                entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_HEADS_UP);
            }
        }
        if ((inflatedFlags & FLAG_CONTENT_VIEW_AMBIENT) != 0) {
            if (mNotificationInterruptionStateProvider.shouldPulse(entry)) {
                mAmbientPulseManager.showNotification(entry);
            } else {
                entry.freeContentViewWhenSafe(FLAG_CONTENT_VIEW_AMBIENT);
            }
        }
    }

展示悬浮通知

当通知为高优先级,有提示音并在应用上方显示悬浮通知,停留30s后消失。进入 HeadsUpManager.showNotification(entry) 由其父类 AlertingNotificationManager实现。

   com/android/systemui/statusbar/AlertingNotificationManager.java
/**在发布新通知时调用,该通知应提醒用户并显示在屏幕上。
     * Called when posting a new notification that should alert the user and appear on screen.
     * Adds the notification to be managed.
     * @param entry entry to show
     */
    public void showNotification(@NonNull NotificationEntry entry) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "showNotification");
        }
        addAlertEntry(entry);
        updateNotification(entry.key, true /* alert */);
        entry.setInterruption();
    }


    /**在通知状态已更新时调用。
     * Called when the notification state has been updated.
     * @param key the key of the entry that was updated
     * @param alert whether the notification should alert again and force reevaluation of
     *              removal time
     */
    public void updateNotification(@NonNull String key, boolean alert) {
        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "updateNotification");
        }

        AlertEntry alertEntry = mAlertEntries.get(key);
        if (alertEntry == null) {
            // the entry was released before this update (i.e by a listener) This can happen
            // with the groupmanager
            return;
        }

        alertEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
        if (alert) {
            alertEntry.updateEntry(true /* updatePostTime */);
        }
    }


// 标记为立即看到
    private void setNotificationShown(StatusBarNotification n) {
        try {
            mNotificationListener.setNotificationsShown(new String[]{n.getKey()});
        } catch (RuntimeException e) {
            Log.d(TAG, "failed setNotificationsShown: ", e);
        }
    }

// NotificationListener.setNotificationsShown()在哪实现呢?

NotificationListener 继承自NotificationListenerWithPlugins,NotificationListenerWithPlugins又继承自NotificationListenerService,setNotificationShown在NotificationListenerService中实现。

android/service/notification/NotificationListenerService.java  

  /**
     * Inform the notification manager that these notifications have been viewed by the
     * user. This should only be called when there is sufficient confidence that the user is
     * looking at the notifications, such as when the notifications appear on the screen due to
     * an explicit user interaction.
     *
     * <p>The service should wait for the {@link #onListenerConnected()} event
     * before performing this operation.
     *
     * @param keys Notifications to mark as seen.
     */
    public final void setNotificationsShown(String[] keys) {
        if (!isBound()) return;
        try {
            getNotificationInterface().setNotificationsShownFromListener(mWrapper, keys);
        } catch (android.os.RemoteException ex) {
            Log.v(TAG, "Unable to contact notification manager", ex);
        }
    }

//com/android/server/notification/NotificationManagerService.java

  @Override
        public void setNotificationsShownFromListener(INotificationListener token, String[] keys) {
            long identity = Binder.clearCallingIdentity();
            try {
                synchronized (mNotificationLock) {
                    final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
                    if (keys == null) {
                        return;
                    }
                    ArrayList<NotificationRecord> seen = new ArrayList<>();
                    final int n = keys.length;
                    for (int i = 0; i < n; i++) {
                        NotificationRecord r = mNotificationsByKey.get(keys[i]);
                        if (r == null) continue;
                        final int userId = r.sbn.getUserId();
                        if (userId != info.userid && userId != UserHandle.USER_ALL
                                && !mUserProfiles.isCurrentProfile(userId)) {
                            throw new SecurityException("Disallowed call from listener: "
                                    + info.service);
                        }
                        seen.add(r);
                        if (!r.isSeen()) {
                            if (DBG) Slog.d(TAG, "Marking notification as seen " + keys[i]);
                            reportSeen(r);
                            r.setSeen();
                            maybeRecordInterruptionLocked(r);
                        }
                    }
                    if (!seen.isEmpty()) {
                        mAssistants.onNotificationsSeenLocked(seen);
                    }
                }
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

//android/service/notification/NotificationAssistantService.java

        @Override
        public void onNotificationsSeen(List<String> keys) {
            SomeArgs args = SomeArgs.obtain();
            args.arg1 = keys;
            mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATIONS_SEEN,
                    args).sendToTarget();
        }


....


                case MSG_ON_NOTIFICATIONS_SEEN: {
                    SomeArgs args = (SomeArgs) msg.obj;
                    List<String> keys = (List<String>) args.arg1;
                    args.recycle();
                    onNotificationsSeen(keys);
                    break;
                }


....

 

通知构建布局

以下从分析最高级的紧急提示通知入手分析相关布局。

附:可能的重要程度等级如下所示:

  • 紧急:发出提示音,并以提醒式通知的形式显示。
  • 高:发出提示音。
  • 中:无提示音。
  • 低:无提示音,且不会在状态栏中显示。

 

//com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java

// 更新布局
 private void updateNotification(
            NotificationEntry entry,
            PackageManager pmUser,
            StatusBarNotification sbn,
            ExpandableNotificationRow row) {
        row.setIsLowPriority(entry.ambient);

        // Extract target SDK version.
        try {
            ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
            entry.targetSdk = info.targetSdkVersion;
        } catch (PackageManager.NameNotFoundException ex) {
            Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
        }
        row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
                && entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);

        // TODO: should updates to the entry be happening somewhere else?
        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
        entry.autoRedacted = entry.notification.getNotification().publicVersion == null;

        entry.setRow(row);
        row.setOnActivatedListener(mPresenter);

// PRIVARY_LOW 及以上消息使用增加折叠的高度 
        boolean useIncreasedCollapsedHeight =
                mMessagingUtil.isImportantMessaging(sbn, entry.importance);

// 全屏折叠下的使用折叠的抬头
        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
                && !mPresenter.isPresenterFullyCollapsed();
        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
        row.setEntry(entry);

// IMPORTANCE_HIGH 设置展示悬浮框
        if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
            row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */);
        }
// IMPORTANCE_HIGH 从休眠中唤醒 展示在屏幕上
        if (mNotificationInterruptionStateProvider.shouldPulse(entry)) {
            row.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, true /* shouldInflate */);
        }
        row.setNeedsRedaction(
                Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));

// 填充布局
        row.inflateViews();

        // bind the click event to the content area
        checkNotNull(mNotificationClicker).register(row, sbn);
    }

紧急通知样式flag = FLAG_CONTENT_VIEW_HEADS_UP

com/android/systemui/statusbar/notification/row/NotificationContentInflater.java

    public static CancellationSignal apply(
            boolean inflateSynchronously,
            InflationProgress result,
            @InflationFlag int reInflateFlags,
            ArrayMap<Integer, RemoteViews> cachedContentViews,
            ExpandableNotificationRow row,
            boolean redactAmbient,
            RemoteViews.OnClickHandler remoteViewClickHandler,
            @Nullable InflationCallback callback) {
        NotificationContentView privateLayout = row.getPrivateLayout();
        NotificationContentView publicLayout = row.getPublicLayout();
        final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
......
        flag = FLAG_CONTENT_VIEW_HEADS_UP;
        if ((reInflateFlags & flag) != 0) {
            if (result.newHeadsUpView != null) {
                boolean isNewView =
                        !canReapplyRemoteView(result.newHeadsUpView,
                                cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP));
                ApplyCallback applyCallback = new ApplyCallback() {
                    @Override
                    public void setResultView(View v) {
                        result.inflatedHeadsUpView = v;
                    }

                    @Override
                    public RemoteViews getRemoteView() {
                        return result.newHeadsUpView;
                    }
                };
                applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
                        cachedContentViews, row, redactAmbient, isNewView, remoteViewClickHandler,
                        callback, privateLayout, privateLayout.getHeadsUpChild(),
                        privateLayout.getVisibleWrapper(
                                VISIBLE_TYPE_HEADSUP), runningInflations,
                        applyCallback);
            }
        }
......

    @VisibleForTesting
    static void applyRemoteView(
            boolean inflateSynchronously,
            final InflationProgress result,
            final @InflationFlag int reInflateFlags,
            @InflationFlag int inflationId,
            final ArrayMap<Integer, RemoteViews> cachedContentViews,
            final ExpandableNotificationRow row,
            final boolean redactAmbient,
            boolean isNewView,
            RemoteViews.OnClickHandler remoteViewClickHandler,
            @Nullable final InflationCallback callback,
            NotificationContentView parentLayout,
            View existingView,
            NotificationViewWrapper existingWrapper,
            final HashMap<Integer, CancellationSignal> runningInflations,
            ApplyCallback applyCallback) {
        RemoteViews newContentView = applyCallback.getRemoteView();
......

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Android Framework 层是 Android 系统中非常重要的一层,它提供了丰富的 API 和服务,包括 Activity、Service、Content Provider、Broadcast Receiver、PackageManager、Window Manager 等等,它们为应用程序提供了一个丰富的运行环境和开发框架。 Android Framework 层的源码主要包括以下几个部分: 1. Activity Manager:负责管理 Android 应用程序的生命周期、进程和任务栈等。 2. Package Manager:负责管理应用程序的安装、卸载和更新等操作。 3. Content Provider:提供了一种标准的接口,让应用程序之间可以共享数据。 4. Telephony Manager:提供了访问手机通讯功能的接口和服务。 5. Location Manager:提供了访问 GPS 和其他位置服务的接口和服务。 6. Notification Manager:提供了管理通知的接口和服务。 7. View System:提供了 Android 应用程序的 UI 界面的渲染和事件处理等功能。 8. Resource Manager:提供了访问 Android 应用程序的资源文件的接口和服务。 9. Window Manager:提供了窗口管理和界面绘制等功能。 10. Activity Manager Service:提供了 Activity Manager 的服务接口。 11. System Server:提供了 Android 系统的核心服务,包括 PackageManager、ActivityManager、WindowManager 等。 以上是 Android Framework 层源码的主要部分,通过阅读 Android Framework 层源码可以深入了解 Android 系统的实现原理和应用程序的开发框架。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值