Notifications 通知

Notifications 通知

通知是指 Android 在應用的界面之外顯示的消息,旨在向用戶提供提醒、來自他人的通信信息或應用中的其他實時信息。用戶可以點按通知來打開應用,也可以直接在通知中執行某項操作。

通知顯示

通知可以在不同的位置以不同的格式顯示,例如,狀態欄中的圖標、抽屜式通知欄中比較詳細的條目、應用圖標上的標誌,以及在配對的穿戴式設備上自動顯示。

狀態欄和抽屜式通知欄

發出通知後,通知先以圖標的形式顯示在狀態欄中。
在這裏插入圖片描述
用戶可以在狀態欄向下滑動以打開抽屜式通知欄,並在其中查看更多詳情及對通知執行操作。
在這裏插入圖片描述
用戶可以向下拖動抽屜式通知欄中的某條通知以查看展開後視圖,其中會顯示更多內容以及操作按鈕(如果有)。
在應用或用戶關閉通知之前,通知會一直顯示在抽屜式通知欄中。

提醒式通知

從 Android 5.0 開始,通知可以短暫地顯示在浮動窗口中,稱之爲提醒式通知。這種行爲通常適用於用戶應立即知曉的重要通知,而且僅在設備未鎖定時才顯示。
在這裏插入圖片描述
提醒式通知會在應用發出通知後立即出現,稍後便會消失,但仍照常顯示在抽屜式通知欄中。

可能會觸發提醒式通知的條件示例:

  • 用戶的 Activity 處於全屏模式(應用使用 fullScreenIntent)。
  • 通知的優先級很高,且在搭載 Android 7.1(API 級別 25)及更低版本的設備上使用鈴聲或振動。
  • 在搭載 Android 8.0(API 級別 26)及更高版本的設備上,通知渠道的重要程度比較高。

鎖定屏幕

從 Android 5.0 開始,通知可以顯示在鎖定屏幕上。

您可以程序化地設置您的應用在安全鎖定屏幕上所發佈通知的詳情可見等級,甚至可以設置通知是否顯示在鎖定屏幕上。

用戶可以通過系統設置來選擇鎖定屏幕通知的詳情可見等級,包括選擇停用所有鎖定屏幕通知。從 Android 8.0 開始,用戶可以選擇停用或啓用各個通知渠道的鎖定屏幕通知。
在這裏插入圖片描述
要了解詳情,請參閱如何設置鎖定屏幕的可見性

應用圖標的標誌

在所支持的設備(搭載 Android 8.0(API 級別 26)及更高版本)啓動器中,應用圖標通過在相應的應用啓動器圖標上顯示彩色“標誌”(又稱“通知圓點”)來表示有新通知。

用戶可以長按應用圖標以查看該應用的通知。然後,用戶可以關閉通知或者在長按菜單中對通知執行操作(類似於抽屜式通知欄)。
在這裏插入圖片描述
要詳細瞭解標誌的工作原理,請參閱通知標誌

APP側應用

實例 https://github.com/googlearchive/android-Notifications

通知剖析

通知的設計由系統模板決定,您的應用只需要定義模板中各個部分的內容即可。通知的部分詳情僅在展開後視圖中顯示。
在這裏插入圖片描述

  1. 小圖標:必須提供,通過 setSmallIcon() 進行設置。
  2. 應用名稱:由系統提供。
  3. 時間戳:由系統提供,但您可以通過 setWhen() 將其替換掉或者通過 setShowWhen(false) 將其隱藏。
  4. 大圖標:可選內容(通常僅用於聯繫人照片,請勿將其用於應用圖標),通過 setLargeIcon() 進行設置。
  5. 標題:可選內容,通過 setContentTitle() 進行設置。
  6. 文本:可選內容,通過 setContentText() 進行設置。

要詳細瞭解如何創建包含上述功能及其他功能的通知,請參閱創建通知

通知渠道

從 Android 8.0(API 級別 26)開始,必須爲所有通知分配渠道,否則通知將不會顯示。通過將通知歸類爲不同的渠道,用戶可以停用您應用的特定通知渠道(而非停用您的所有通知),還可以控制每個渠道的視覺和聽覺選項,所有這些操作都在 Android 系統設置中完成(如圖 11 所示)。用戶還可以長按通知以更改所關聯渠道的行爲。

在搭載 Android 7.1(API 級別 25)及更低版本的設備上,用戶僅可以按應用來管理通知(在搭載 Android 7.1 及更低版本的設備上,每個應用其實只有一個渠道)。
在這裏插入圖片描述

注意:界面將渠道稱作“類別”。

發佈限制

從 Android 8.1(API 級別 27)開始,應用無法每秒發出一次以上的通知提示音。如果應用在一秒內發出了多條通知,這些通知都會按預期顯示,但是每秒中只有第一條通知發出提示音。

不過,Android 還對通知更新頻率設定了限制。如果您過於頻繁地發佈有關某條通知的更新(不到一秒內發佈多個),系統可能會放棄部分更新。

通知的兼容性

自 Android 1.0 開始,通知系統界面以及與通知相關的 API 就在不斷髮展。要在支持舊設備的同時使用最新的通知 API 功能,請使用支持庫通知 API NotificationCompat 及其子類,以及 NotificationManagerCompat。這樣一來,您就無需編寫條件代碼來檢查 API 級別,因爲這些 API 會爲您代勞。

NotificationCompat 隨着平臺的發展不斷更新,旨在納入最新的方法。需要注意的是,某個方法在 NotificationCompat 中可用並不能保證可以在舊設備上採用相應功能。在某些情況下,調用新推出的 API 會在舊設備上產生空操作。例如,NotificationCompat.addAction() 僅在搭載 Android 4.1(API 級別 16)及更高版本的設備上顯示操作按鈕。

以下是最明顯的 Android 通知行爲變化的摘要:
 
Android 4.1,API 級別 16

  • 推出了展開式通知模板(稱爲通知樣式),可以提供較大的通知內容區域來顯示信息。用戶可以通過單指向上/向下滑動的手勢展開通知。
  • 還支持以按鈕的形式向通知添加其他操作。
  • 允許用戶在設置中按應用關閉通知。
     

Android 4.4,API 級別 19 和 20

  • 向 API 中添加了通知監聽器服務。
  • API 級別 20 中新增了 Android Wear(現已更名爲 Wear OS)支持。
     

Android 5.0,API 級別 21

  • 推出了鎖定屏幕和提醒式通知。
  • 用戶現在可以將手機設爲勿擾模式,並配置允許哪些通知在設備處於優先模式時打擾他們。
  • 向 API 集添加了通知是否在鎖定屏幕上顯示的方法 (setVisibility()),以及指定通知文本的“公開”版本的方法。
  • 添加了 setPriority() 方法,告知系統通知的“干擾性”(例如,將其設爲“高”可使通知以提醒式通知的形式顯示)。
  • 向 Android Wear(現已更名爲 Wear OS)設備添加了通知堆棧支持。使用 setGroup() 將通知放入堆棧。注意,平板電腦和手機尚不支持通知堆棧。通知堆棧在以後會稱爲組或集合。
     

Android 7.0,API 級別 24

  • 重新設置了通知模板的樣式以強調主打圖片和頭像。
  • 添加了三個通知模板:一個用於短信應用,另外兩個用於藉助展開式選項和其他系統裝飾來裝飾自定義內容視圖。
  • 向手持設備(手機和平板電腦)添加了對通知組的支持。使用與 Android 5.0(API 級別 21)中推出的 Android Wear(現已更名爲 Wear OS)通知堆棧相同的 API。
  • 用戶可以使用內嵌回覆功能直接在通知內進行回覆(他們輸入的文本將轉發到通知的父級應用)。
     

Android 8.0,API 級別 26

  • 現在必須將各個通知放入特定渠道中。
  • 現在,用戶可以按渠道關閉通知,而非關閉來自某個應用的所有通知。
  • 包含有效通知的應用將在主屏幕/啓動器屏幕上相應應用圖標的上方顯示通知“標誌”。
  • 現在,用戶可以從抽屜式通知欄中暫停某個通知。您可以爲通知設置自動超時時間。
  • 您還可以設置通知的背景顏色。
  • 部分與通知行爲相關的 API 從 Notification 移至了 NotificationChannel。例如,在搭載 Android 8.0 及更高版本的設備中,使用 NotificationChannel.setImportance(),而非 NotificationCompat.Builder.setPriority()。

創建通知

build.gradle 添加支持庫

dependencies {
    implementation "com.android.support:support-compat:28.0.0"
}

創建基本通知

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
        .setSmallIcon(R.drawable.notification_icon)
        .setContentTitle(textTitle)
        .setContentText(textContent)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT);
通知渠道(CHANNEL_ID)

從 Android 8.0(API 級別 26)開始,必須爲所有通知分配渠道,否則通知將不會顯示。
 
Android 實例創建

    public static String createNotificationChannel(Context context) {

        // NotificationChannels are required for Notifications on O (API 26) and above.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // The id of the channel.
            String channelId = context.getPackageName();
            // The user-visible name of the channel.
            CharSequence channelName = "XhBruce";
            // Initializes NotificationChannel.
            NotificationChannel notificationChannel =
                    new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT);

            NotificationManager notificationManager =
                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(notificationChannel);

            return channelId;
        } else {
            // Returns null for pre-O (26) devices.
            return null;
        }
    }

發送通知

NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);

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

上面這個是支持包中的,實質上:

mNotificationManager = (NotificationManager) mContext.getSystemService(
                Context.NOTIFICATION_SERVICE);
mNotificationManager.notify(tag, id, notification);

取消通知

mNotificationManager.cancel(notifyId); //取消指定ID的通知
mNotificationManager.cancelAll(); //取消所有通知

NotificationManagerService

android-9.0.0_r3

gityuan.com 通知處理流程圖

Gityuan notification_seq.jpg

gityuan.com 核心類圖

在這裏插入圖片描述

通知發送原理分析

1 NM.notify

[-> NotificationManager.java]

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

public void notify(String tag, int id, Notification notification) {
    notifyAsUser(tag, id, notification, mContext.getUser());
}

public void notifyAsUser(String tag, int id, Notification notification, UserHandle user) {
    //獲取通知的代理對象
    INotificationManager service = getService();
    String pkg = mContext.getPackageName();
    //將包名和userId保存到通知的extras
    Notification.addFieldsFromContext(mContext, notification);
    
    if (notification.sound != null) {
        notification.sound = notification.sound.getCanonicalUri();
        if (StrictMode.vmFileUriExposureEnabled()) {
            notification.sound.checkFileUriExposed("Notification.sound");
        }

    }
    fixLegacySmallIcon(notification, pkg);
    //對於Android 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);
        }
    }
    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);
    final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
    try {
        //binder調用,進入system_server進程【2.2】
        service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
                copy, user.getIdentifier());
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}

在App端調用NotificationManager類的notify()方法,最終通過binder調用,會進入system_server進程的 NotificationManagerService(簡稱NMS),執行enqueueNotificationWithTag()方法,走到enqueueNotificationInternal()。

2 NMS.enqueueNotificationInternal

[-> NotificationManagerService.java]

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); 
    
    ...
    //從 Android 8.0(API 級別 26)開始,必須爲所有通知分配渠道,否則通知將不會顯示
    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());
    //創建記錄通知實體的對象NotificationRecord
    final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
    ...
    //檢查是否可以發佈通知:
    //1、除了系統通知和已註冊的監聽器允許入隊列,其他通知都會限制數量上限,默認是一個package上限MAX_PACKAGE_NOTIFICATIONS=50個、入隊間隔MIN_PACKAGE_OVERRATE_LOG_INTERVAL=5000
    //2、snoozed apps
    //3、blocked apps
    if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
            r.sbn.getOverrideGroupKey() != null)) {
        return;
    }
    ...
    //將通知異步發送到handler線程【見小節2.3】
    mHandler.post(new EnqueueNotificationRunnable(userId, r));
}

這個過程主要功能:

  • 從 Android 8.0(API 級別 26)開始,必須爲所有通知分配渠道,否則通知將不會顯示
  • 創建NotificationRecord對象,裏面包含了notification相關信息
  • 採用異步方式,將任務交給mHandler線程來處理,mHandler是WorkerHandler類的實例對象
    接下來看看WorkerHandler到底運行在哪個線程,這需要從NMS服務初始化過程來說起:
2.1 SS.startOtherServices

[-> SystemServer.java]

private void startOtherServices() {
    ...
    //【見小節2.2】
    traceBeginAndSlog("StartNotificationManager");
    mSystemServiceManager.startService(NotificationManagerService.class);
    SystemNotificationChannels.createAll(context);
    notification = INotificationManager.Stub.asInterface(
            ServiceManager.getService(Context.NOTIFICATION_SERVICE));
    traceEnd();
    ...
}

該過程運行在system_server進程的主線程。

2.2 SSM.startService

[-> SystemServiceManager.java]

public <T extends SystemService> T startService(Class<T> serviceClass) {
    try {
        final String name = serviceClass.getName();
        Slog.i(TAG, "Starting " + name);
        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "StartService " + name);

        // Create the service.
        if (!SystemService.class.isAssignableFrom(serviceClass)) {
            throw new RuntimeException(...);
        }
        final T service;
        try {
            Constructor<T> constructor = serviceClass.getConstructor(Context.class);
            //創建NotificationManagerService對象
            service = constructor.newInstance(mContext);
        } catch (InstantiationException ex) {
            ...
        }

        //註冊該服務,並調用NMS的onStart方法,【見小節2.3】
        startService(service);
        return service;
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
    }
}

該過程先創建NotificationManagerService(簡稱NMS),然後再調用其onStart方法。

2.3 NMS.onStart

[-> NMS.java]

public void onStart() {
    ...
    init(Looper.myLooper(),
            AppGlobals.getPackageManager(), getContext().getPackageManager(),
            getLocalService(LightsManager.class),
            new NotificationListeners(AppGlobals.getPackageManager()),
            new NotificationAssistants(getContext(), mNotificationLock, mUserProfiles,
                    AppGlobals.getPackageManager()),
            new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
            null, snoozeHelper, new NotificationUsageStats(getContext()),
            new AtomicFile(new File(systemDir, "notification_policy.xml"), "notification-policy"),
            (ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE),
            getGroupHelper(), ActivityManager.getService(),
            LocalServices.getService(UsageStatsManagerInternal.class),
            LocalServices.getService(DevicePolicyManagerInternal.class));
    ...
    publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
            DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL);
    publishLocalService(NotificationManagerInternal.class, mInternalService);
}
void init(Looper looper, IPackageManager packageManager,
        PackageManager packageManagerClient,
        LightsManager lightsManager, NotificationListeners notificationListeners,
        NotificationAssistants notificationAssistants, ConditionProviders conditionProviders,
        ICompanionDeviceManager companionManager, SnoozeHelper snoozeHelper,
        NotificationUsageStats usageStats, AtomicFile policyFile,
        ActivityManager activityManager, GroupHelper groupHelper, IActivityManager am,
        UsageStatsManagerInternal appUsageStats, DevicePolicyManagerInternal dpm) {
    ...
    mHandler = new WorkerHandler(looper); //運行在system_server的主線程
    mRankingThread.start(); //線程名爲"ranker"的handler線程
    ...
    mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
    ...
    //用於記錄所有的listeners的MangedServices對象
    mListeners = notificationListeners;

onStart()過程創建的mHandler運行在system_server的主線程。那麼上面的執行流便進入了 system_server 主線程。

3 EnqueueNotificationRunnable\PostNotificationRunnable

[-> NMS.java]

private 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) {
            ...
            final StatusBarNotification n = r.sbn;
            ...
            // 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 {
    ...
    @Override
    public void run() {
        synchronized (mNotificationLock) {
            try {
                NotificationRecord r = null;
                int N = mEnqueuedNotifications.size();
                for (int i = 0; i < N; i++) {
                    final NotificationRecord enqueued = mEnqueuedNotifications.get(i);
                    if (Objects.equals(key, enqueued.getKey())) {
                        r = enqueued;
                        break;
                    }
                }
                if (r == null) {
                    Slog.i(TAG, "Cannot find enqueued record for key: " + key);
                    return;
                }

                r.setHidden(isPackageSuspendedLocked(r));
                NotificationRecord old = mNotificationsByKey.get(key);
                final StatusBarNotification n = r.sbn;
                final Notification notification = n.getNotification();
                //從通知列表mNotificationList查看是否存在該通知
                int index = indexOfNotificationLocked(n.getKey());
                if (index < 0) {
                    mNotificationList.add(r);
                    mUsageStats.registerPostedByApp(r);
                    r.setInterruptive(isVisuallyInterruptive(null, r));
                } else {
                    old = mNotificationList.get(index);
                    mNotificationList.set(index, r);
                    mUsageStats.registerUpdatedByApp(r, old);
                    // 確保通知的前臺服務屬性不會被丟棄
                    notification.flags |=
                            old.getNotification().flags & FLAG_FOREGROUND_SERVICE;
                    r.isUpdate = true;
                    r.setTextChanged(isVisuallyInterruptive(old, r));
                }

                mNotificationsByKey.put(n.getKey(), r);

                // 如果是前臺服務的通知,則添加不允許被清除和正在運行的標籤
                if ((notification.flags & FLAG_FOREGROUND_SERVICE) != 0) {
                    notification.flags |= Notification.FLAG_ONGOING_EVENT
                            | Notification.FLAG_NO_CLEAR;
                }

                applyZenModeLocked(r);
                mRankingHelper.sort(mNotificationList);

                //當設置小圖標,則通知NotificationListeners處理 【2.4】
                if (notification.getSmallIcon() != null) {
                    StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
                    mListeners.notifyPostedLocked(r, old);
                    ...
                } else {
                    Slog.e(TAG, "Not posting notification without small icon: " + notification);
                    if (old != null && !old.isCanceled) {
                        mListeners.notifyRemovedLocked(r,
                                NotificationListenerService.REASON_ERROR, null);
                        ...
                    }
                    ...
                }
                //r.isHidden()由於應用pkg已暫停,此通知是否被隱藏?
                if (!r.isHidden()) {
                    //處理該通知,主要是是否發聲,震動,Led
                    buzzBeepBlinkLocked(r);
                }
                maybeRecordInterruptionLocked(r);
            } finally {
                ...
            }
        }
    }
}

這裏的mListeners是指NotificationListeners對象

4 NotificationListeners.notifyPostedLocked

[-> NMS.java]

public class NotificationListeners extends ManagedServices {
    ...
    public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {
        notifyPostedLocked(r, old, true);
    }
    ...
    public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {
        notifyPostedLocked(r, old, true);
    }
    
    private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
            boolean notifyAllListeners) {
        // Lazily initialized snapshots of the notification.
        StatusBarNotification sbn = r.sbn;
        StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
        TrimCache trimCache = new TrimCache(sbn);
        // 遍歷整個ManagedServices中的所有ManagedServiceInfo
        for (final ManagedServiceInfo info : getServices()) {
            boolean sbnVisible = isVisibleToListener(sbn, info);
            boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
            // 此通知尚未出現,仍然不可見,則忽略。
            if (!oldSbnVisible && !sbnVisible) {
                continue;
            }

            // 如果通知是隱藏的,則不要通知目標已發佈的偵聽器。(< P)
            // 相反,當取消隱藏通知時,這些偵聽器將收到notifyPosted。
            if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
                continue;
            }

            // 如果我們不通知所有偵聽器,則意味着通知的隱藏狀態已更改。 不要通知已發佈的偵聽器定位。( >= P)
            // 相反,這些偵聽器將收到notifyRankingUpdate。
            if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
                continue;
            }

            final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
            
            // 通知變得不可見,則移除老的通知
            if (oldSbnVisible && !sbnVisible) {
                final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        notifyRemoved(
                                info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
                    }
                });
                continue;
            }

            // Grant access before listener is notified
            final int targetUserId = (info.userid == UserHandle.USER_ALL)
                    ? UserHandle.USER_SYSTEM : info.userid;
            updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);

            final StatusBarNotification sbnToPost = trimCache.ForListener(info);
            mHandler.post(new Runnable() {
                @Override
                public void run() {
                    notifyPosted(info, sbnToPost, update); //【見小節5】
                }
            });
        }
    }
    ...
}

5 NotificationListeners.notifyPosted

private void notifyPosted(final ManagedServiceInfo info,
        final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
    final INotificationListener listener = (INotificationListener) info.service;
    StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
    try {
        // 【見小節6】
        listener.onNotificationPosted(sbnHolder, rankingUpdate);
    } catch (RemoteException ex) {
        Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
    }
}

此處的 listener 來自於ManagedServiceInfo的service成員變量,listener 數據類型是NotificationListenerWrapper的代理對象,詳見第三大節。 此處 sbnHolder 的數據類型爲StatusBarNotificationHolder,繼承於IStatusBarNotificationHolder.Stub對象,經過binder調用進入到 systemui 進程的 便是IStatusBarNotificationHolder.Stub.Proxy對象。

6 NotificationListenerWrapper.onNotificationPosted

[-> NotificationListenerService.java]

protected class NotificationListenerWrapper extends INotificationListener.Stub {
    public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
            NotificationRankingUpdate update) {
        StatusBarNotification sbn;
        try {
            sbn = sbnHolder.get(); //向 system_server 進程來獲取sbn對象
        } catch (RemoteException e) {
            Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
            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;
                //【見小節7】
                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();
            }
        }

    }
    ...
}

此時運行在systemui進程,sbnHolder 是IStatusBarNotificationHolder的代理端。 此處mHandler = new MyHandler(getMainLooper()),也就是運行在systemui主線程的handler

7 MyHandler

[-> NotificationListenerService.java]

private final class MyHandler extends Handler {
    ...
    public void handleMessage(Message msg) {
        ...
        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;
    		...
        }
    }
}

此處調用NotificationListenerService實例對象的onNotificationPosted()

8 NLS.onNotificationPosted

[SystemUI -> StatusBar.java -> NotificationListener.java]

public class NotificationListener extends NotificationListenerWithPlugins {
    ...
    public void onNotificationPosted(final StatusBarNotification sbn,
            final RankingMap rankingMap) {
        if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
        if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
            mPresenter.getHandler().post(() -> {
                processForRemoteInput(sbn.getNotification(), mContext);
                String key = sbn.getKey();
                mEntryManager.removeKeyKeptForRemoteInput(key);
                boolean isUpdate = mEntryManager.getNotificationData().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
                        && mPresenter.getGroupManager().isChildInGroupWithSummary(sbn)) {
                    if (DEBUG) {
                        Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
                    }

                    // Remove existing notification to avoid stale data.
                    if (isUpdate) {
                        mEntryManager.removeNotification(key, rankingMap);
                    } else {
                        mEntryManager.getNotificationData()
                                .updateRanking(rankingMap);
                    }
                    return;
                }
                if (isUpdate) {
                    mEntryManager.updateNotification(sbn, rankingMap);
                } else {
                    //【9】
                    mEntryManager.addNotification(sbn, rankingMap);
                }
            });
        }
    }
    ...
}

9 addNotification

[SystemUI -> NotificationEntryManager.java -> ForegroundServiceController.java]

public void addNotification(StatusBarNotification notification,
        NotificationListenerService.RankingMap ranking) {
    try {
        addNotificationInternal(notification, ranking);
    } catch (InflationException e) {
        handleInflationException(notification, e);
    }
}
private void addNotificationInternal(StatusBarNotification notification,
        NotificationListenerService.RankingMap ranking) throws InflationException {
    String key = notification.getKey();
    if (DEBUG) Log.d(TAG, "addNotification key=" + key);

    mNotificationData.updateRanking(ranking); //更新排序
    //創建通知視圖【9.1】
    NotificationData.Entry shadeEntry = createNotificationViews(notification);
    boolean isHeadsUped = shouldPeek(shadeEntry);
    if (!isHeadsUped && notification.getNotification().fullScreenIntent != null) {
        if (shouldSuppressFullScreenIntent(shadeEntry)) {
            if (DEBUG) {
                Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + key);
            }
        } else if (mNotificationData.getImportance(key)
                < NotificationManager.IMPORTANCE_HIGH) {
            if (DEBUG) {
                Log.d(TAG, "No Fullscreen intent: not important enough: "
                        + key);
            }
        } else {
            // Stop screensaver if the notification has a fullscreen intent.
            // (like an incoming phone call)
            SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();

            // not immersive & a fullscreen alert should be shown
            if (DEBUG)
                Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
            try {
                EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
                        key);
                notification.getNotification().fullScreenIntent.send();
                shadeEntry.notifyFullScreenIntentLaunched();
                mMetricsLogger.count("note_fullscreen", 1);
            } catch (PendingIntent.CanceledException e) {
            }
        }
    }
    abortExistingInflation(key);

    mForegroundServiceController.addNotification(notification,
            mNotificationData.getImportance(key));

    mPendingNotifications.put(key, shadeEntry);
    mGroupManager.onPendingEntryAdded(shadeEntry);
}

[ForegroundServiceControllerImpl.java]

public class ForegroundServiceControllerImpl
        implements ForegroundServiceController {
    ...
    public void addNotification(StatusBarNotification sbn, int importance) {
        updateNotification(sbn, importance);
    }
    ...
    public void updateNotification(StatusBarNotification sbn, int newImportance) {
        synchronized (mMutex) {
            UserServices userServices = mUserServices.get(sbn.getUserId());
            if (userServices == null) {
                userServices = new UserServices();
                mUserServices.put(sbn.getUserId(), userServices);
            }

            if (isDungeonNotification(sbn)) {
                final Bundle extras = sbn.getNotification().extras;
                if (extras != null) {
                    final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
                    userServices.setRunningServices(svcs, sbn.getNotification().when);
                }
            } else {
                userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
                if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)) {
                    if (newImportance > NotificationManager.IMPORTANCE_MIN) {
                        userServices.addImportantNotification(sbn.getPackageName(), sbn.getKey());
                    }
                    final Notification.Builder builder = Notification.Builder.recoverBuilder(
                            mContext, sbn.getNotification());
                    if (builder.usesStandardHeader()) {
                        userServices.addStandardLayoutNotification(
                                sbn.getPackageName(), sbn.getKey());
                    }
                }
            }
        }
    }
    ...
    /**
     * Struct to track relevant packages and notifications for a userid's foreground services.
     */
    private static class UserServices {
        ...
        public void addImportantNotification(String pkg, String key) {
            addNotification(mImportantNotifications, pkg, key);
        }
        ...
        public void addStandardLayoutNotification(String pkg, String key) {
            addNotification(mStandardLayoutNotifications, pkg, key);
        }
        ...
        public void addNotification(ArrayMap<String, ArraySet<String>> map, String pkg,
                String key) {
            if (map.get(pkg) == null) {
                map.put(pkg, new ArraySet<>());
            }
            map.get(pkg).add(key);
        }
        ...
    }
}
9.1 createNotificationViews

[SystemUI -> NotificationEntryManager.java]

protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
        throws InflationException {
    if (DEBUG) {
        Log.d(TAG, "createNotificationViews(notification=" + sbn);
    }
    NotificationData.Entry entry = new NotificationData.Entry(sbn);
    Dependency.get(LeakDetector.class).trackInstance(entry);
    entry.createIcons(mContext, sbn);
    // Construct the expanded view.
    inflateViews(entry, mListContainer.getViewParentForNotification(entry));
    return entry;
}

SystemUI

1 startOtherServices

[-> SystemServer.java]

private void startOtherServices() {
    ...
    traceBeginAndSlog("StartSystemUI");
    try {
        startSystemUi(context, windowManagerF);
    } catch (Throwable e) {
        reportWtf("starting System UI", e);
    }
    traceEnd();
    ...
}

2 startSystemUi

[-> SystemServer.java]

static final void startSystemUi(Context context, WindowManagerService windowManager) {
    Intent intent = new Intent();
    intent.setComponent(new ComponentName("com.android.systemui",
                "com.android.systemui.SystemUIService"));
    intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
    //【見3】
    context.startServiceAsUser(intent, UserHandle.SYSTEM);
    windowManager.onSystemUiStarted();
}

啓動服務SystemUIService,運行在進程com.android.systemui,接下來進入systemui進程

3 SystemUIService

[-> SystemUIService.java]

public class SystemUIService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        //【見小節4】
        ((SystemUIApplication) getApplication()).startServicesIfNeeded();
        ...
    }
    ...
}

服務啓動後,先執行其onCreate()方法

4 startServicesIfNeeded

[-> SystemUIApplication.java]

public void startServicesIfNeeded() {
    String[] names = getResources().getStringArray(R.array.config_systemUIServiceComponents);
    startServicesIfNeeded(names);
}

[-> ./res/values/config.xml]

    <!-- SystemUI Services: The classes of the stuff to start. -->
    <string-array name="config_systemUIServiceComponents" translatable="false">
        <item>com.android.systemui.Dependency</item>
        <item>com.android.systemui.util.NotificationChannels</item>
        <item>com.android.systemui.statusbar.CommandQueue$CommandQueueStart</item>
        <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
        <item>com.android.systemui.recents.Recents</item>
        <item>com.android.systemui.volume.VolumeUI</item>
        <item>com.android.systemui.stackdivider.Divider</item>
        <item>com.android.systemui.SystemBars</item>
        <item>com.android.systemui.usb.StorageNotification</item>
        <item>com.android.systemui.power.PowerUI</item>
        <item>com.android.systemui.media.RingtonePlayer</item>
        <item>com.android.systemui.keyboard.KeyboardUI</item>
        <item>com.android.systemui.pip.PipUI</item>
        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
        <item>@string/config_systemUIVendorServiceComponent</item>
        <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
        <item>com.android.systemui.LatencyTester</item>
        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
        <item>com.android.systemui.ScreenDecorations</item>
        <item>com.android.systemui.fingerprint.FingerprintDialogImpl</item>
        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
    </string-array>

此處以SystemBars爲例來展開

5 startServicesIfNeeded

[-> SystemUIApplication.java]

private void startServicesIfNeeded(String[] services) {
    if (mServicesStarted) {
        return;
    }
    mServices = new SystemUI[services.length];

    if (!mBootCompleted) {
        // check to see if maybe it was already completed long before we began
        // see ActivityManagerService.finishBooting()
        if ("1".equals(SystemProperties.get("sys.boot_completed"))) {
            mBootCompleted = true;
            if (DEBUG) Log.v(TAG, "BOOT_COMPLETED was already sent");
        }
    }

    Log.v(TAG, "Starting SystemUI services for user " +
            Process.myUserHandle().getIdentifier() + ".");
    TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
            Trace.TRACE_TAG_APP);
    log.traceBegin("StartServices");
    final int N = services.length;
    for (int i = 0; i < N; i++) {
        String clsName = services[i];
        if (DEBUG) Log.d(TAG, "loading: " + clsName);
        log.traceBegin("StartServices" + clsName);
        long ti = System.currentTimeMillis();
        Class cls;
        try {
            //初始化對象
            cls = Class.forName(clsName);
            mServices[i] = (SystemUI) cls.newInstance();
        } catch(ClassNotFoundException ex){
            throw new RuntimeException(ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(ex);
        }
        ...
        //【見小節6】
        mServices[i].start();
        log.traceEnd();

        // Warn if initialization of component takes too long
        ti = System.currentTimeMillis() - ti;
        if (ti > 1000) {
            Log.w(TAG, "Initialization of " + cls.getName() + " took " + ti + " ms");
        }
        if (mBootCompleted) {
            mServices[i].onBootCompleted();
        }
    }
    log.traceEnd();
    ...
    mServicesStarted = true;
}

6 SystemBars.start

[-> SystemBars.java]

public class SystemBars extends SystemUI {
    ...
    @Override
    public void start() {
        if (DEBUG) Log.d(TAG, "start");
        createStatusBarFromConfig();
    }
    ...
    private void createStatusBarFromConfig() {
        if (DEBUG) Log.d(TAG, "createStatusBarFromConfig");
        //config_statusBarComponent 是指 phone.StatusBar
        final String clsName = mContext.getString(R.string.config_statusBarComponent);
        if (clsName == null || clsName.length() == 0) {
            throw andLog("No status bar component configured", null);
        }
        Class<?> cls = null;
        try {
            cls = mContext.getClassLoader().loadClass(clsName);
        } catch (Throwable t) {
            throw andLog("Error loading status bar component: " + clsName, t);
        }
        try {
            mStatusBar = (SystemUI) cls.newInstance();
        } catch (Throwable t) {
            throw andLog("Error creating status bar component: " + clsName, t);
        }
        mStatusBar.mContext = mContext;
        mStatusBar.mComponents = mComponents;
        //【見小節7】
        mStatusBar.start();
        if (DEBUG) Log.d(TAG, "started " + mStatusBar.getClass().getSimpleName());
    }
    ...
}

config_statusBarComponent的定義位於文件config.xml中,其值爲PhoneStatusBar。

7 StatusBar

[-> StatusBar.java]

public void start() {
    ...
    mNotificationListener =  Dependency.get(NotificationListener.class);
    ...
    createAndAddWindows(); //添加狀態欄
    ...
    //【見小節8】
    mNotificationListener.setUpWithPresenter(this, mEntryManager);
    ...
}

8 NotificationListener

[-> NotificationListener.java]

public void setUpWithPresenter(NotificationPresenter presenter,
        NotificationEntryManager entryManager) {
    mPresenter = presenter;
    mEntryManager = entryManager;

    try {
        //安裝通知的初始化狀態【9】
        registerAsSystemService(mContext,
                new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()),
                UserHandle.USER_ALL);
    } catch (RemoteException e) {
        Log.e(TAG, "Unable to register notification listener", e);
    }
}

9 NLS.registerAsSystemService

[-> NotificationListenerService.java]

public void registerAsSystemService(Context context, ComponentName componentName,
        int currentUser) throws RemoteException {
    if (mWrapper == null) {
        mWrapper = new NotificationListenerWrapper();
    }
    mSystemContext = context;
    //獲取NMS的接口代理對象
    INotificationManager noMan = getNotificationInterface();
    //運行在主線程的handler
    mHandler = new MyHandler(context.getMainLooper());
    mCurrentUser = currentUser;
    //經過binder調用,向system_server中的NMS註冊監聽器【10】
    noMan.registerListener(mWrapper, componentName, currentUser);
}

經過binder調用,向system_server中的NMS註冊監聽器

10 registerListener

[-> NMS.java]

private final IBinder mService = new INotificationManager.Stub() {{
    ...
    public void registerListener(final INotificationListener listener,
            final ComponentName component, final int userid) {
        enforceSystemOrSystemUI("INotificationManager.registerListener");
        //此處的INotificationListener便是NotificationListenerWrapper代理對象 【10.1】
        mListeners.registerService(listener, component, userid);
    }
    ...
}

mListeners的對象類型爲ManagedServices。此處的INotificationListener便是NotificationListenerWrapper的代理對象

10.1 registerService

[-> ManagedServices.java]

public void registerService(IInterface service, ComponentName component, int userid) {
    checkNotNull(service);
    //【10.2】
    ManagedServiceInfo info = registerServiceImpl(service, component, userid);
    if (info != null) {
        onServiceAdded(info);
    }
}

10.2 registerService

[-> ManagedServices.java]

private ManagedServiceInfo registerServiceImpl(final IInterface service,
        final ComponentName component, final int userid) {
    //將NotificationListenerWrapper對象保存到ManagedServiceInfo.service
    ManagedServiceInfo info = newServiceInfo(service, component, userid,
            true /*isSystem*/, null /*connection*/, Build.VERSION_CODES.LOLLIPOP);
    //【10.3】
    return registerServiceImpl(info);
}

11.3 registerServiceImpl

[-> ManagedServices.java]

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

可見,前面的 listener 的對端便是運行在 systemui 中的NotificationListenerWrapper的代理對象。

參考文獻

Google 通知概覽
Gityuan: NotificationManagerService原理分析
Android 7.0 NotificationManagerService源碼分析(應用層App,Fragmework中Service層,SystemUI系統App)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章