SystemUI之StatusBar通知图标控制分析

StatusBar的创建与图标控制 分析了状态栏上系统图标区图标的控制流程,本文承接上文,分析状态栏上通知区图标的控制流程。

本文是基于Android 9.0源码。

初始化通知图标区

由前文可知,状态栏视图的创建是在 StatusBar#makeStatusBarView() 中。同时,通知图标区的初始化也是在这里

protected void makeStatusBarView() {
	// 1. 创建通知图标控制器
    mNotificationIconAreaController = SystemUIFactory.getInstance()
    	.createNotificationIconAreaController(context, this);
    	
	FragmentHostManager.get(mStatusBarWindow)
		.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
			CollapsedStatusBarFragment statusBarFragment =
                            (CollapsedStatusBarFragment) fragment;
            // 2. 用通知图标控制器,初始化通知图标区
            statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
			
			// ...
    	}).getFragmentManager()
    	.beginTransaction()
    	.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(),
    		CollapsedStatusBarFragment.TAG)
    	.commit();          
}

NotificationIconAreaController 是通知图标控制器,控制着状态栏上通知图标区所有的图标。在它的构造函数中,会调用initializeNotificationAreaViews() 方法,实例化一个布局,这个布局就是状态栏通知图标的父布局。

    protected void initializeNotificationAreaViews(Context context) {
		// ...
		
        LayoutInflater layoutInflater = LayoutInflater.from(context);
        // 加载通知图标的父布局
        mNotificationIconArea = inflater.inflate(R.layout.notification_icon_area, null);
        // 真正存放图标的区域
        mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
                R.id.notificationIcons);

		// ...
    }

notification_icon_area.xml 布局如下

<com.android.keyguard.AlphaOptimizedLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/notification_icon_area_inner"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false">
    <com.android.systemui.statusbar.phone.NotificationIconContainer
        android:id="@+id/notificationIcons"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_alignParentStart="true"
        android:gravity="center_vertical"
        android:orientation="horizontal"
        android:clipChildren="false"/>
</com.android.keyguard.AlphaOptimizedLinearLayout>

NotificationIconAreaController 实例化通知图标的父View, 然后在CollapsedStatusBarFragment 创建状态栏时,调用 CollapsedStatusBarFragment#initNotificationIconArea() 方法把刚创建出来的通知图标的父View,添加到状态栏上的通知图标容器中。

    public void initNotificationIconArea(NotificationIconAreaController
                                                 notificationIconAreaController) {
        // 获取状态栏上的通知图标容器
        ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
        
        // 添加通知图标控制器中获取的通知图标的父View
        mNotificationIconAreaInner =
                notificationIconAreaController.getNotificationInnerAreaView();
        if (mNotificationIconAreaInner.getParent() != null) {
            ((ViewGroup) mNotificationIconAreaInner.getParent())
                    .removeView(mNotificationIconAreaInner);
        }
        notificationIconArea.addView(mNotificationIconAreaInner);

        // 设置通知图标区域可见
        showNotificationIconArea(false);
    }

监听通知更新

现在已经知道通知图标是由 NotificationIconAreaController 控制的,但是要显示通知图标,首先还得获取通知信息,这就需要监听 NotificationManagerService 。这一监听步骤就是从 StatusBar#start() 中开始

    public void start() {
        // NotificationListener是用来监听通知的
        mNotificationListener =  Dependency.get(NotificationListener.class);

        // 向NotificationManagerService注册一个服务,用于获取通知更新消息
        mNotificationListener.setUpWithPresenter(this, mEntryManager);
    }

NotificationListener#setUpWithPresenter() 会调用父类的 registerAsSystemService() 向通知的服务端 NotificationManagerService 注册一个"服务"

    public void registerAsSystemService(Context context, ComponentName componentName,
            int currentUser) throws RemoteException {
        // NotificationListenerWrapper是用于向 NotificationManagerService 注册的服务
        if (mWrapper == null) {
            mWrapper = new NotificationListenerWrapper();
        }
        mSystemContext = context;
        INotificationManager noMan = getNotificationInterface();
        mHandler = new MyHandler(context.getMainLooper());
        mCurrentUser = currentUser;
        // 向 NotificationManagerService 注册服务
        noMan.registerListener(mWrapper, componentName, currentUser);
    }

当这个服务注册成功后,通知的服务端 NotificationManagerService 会保存这个服务,并通过 NotificationListenerWrapper#onListenerConnected() 回调客户端,通知客户端服务已经建立连接,并且还会附带客户端可见的所有通知信息。客户端会根据这些通知信息,显示这些通知信息,例如可以在状态栏上显示通知图标,也可以在下拉通知面板中显示通知信息。

获取新通知

可能我们更关心的的是,当一条新通知来临时,SystemUI 如何显示通知图标,以及在下拉通知面板中显示通知信息。那么接下来,就分析这一过程中通知图标如何被添加到状态栏上,并且还附带着会分析到下拉通知面板中,通知信息如何被添加的。

当通知服务端 NotificationManagerService 收到一条新通知信息后,它会通过 NotificationListenerWrapper#onNotificationPosted() 通知 SystemUI

    protected class NotificationListenerWrapper extends INotificationListener.Stub {
        @Override
        public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
                NotificationRankingUpdate update) {
            StatusBarNotification sbn;
            try {
            	// 获取通知
                sbn = sbnHolder.get();
            } catch (RemoteException e) {
                Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
                return;
            }

            // 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;
                    // 通过onNotificationPosted()处理
                    mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
                            args).sendToTarget();
                } else {

                }
            }
        }
	}        

onNotificationPosted(IStatusBarNotificationHolder sbnHolder, NotificationRankingUpdate update) 的第一个参数代表新的通知信息,第二个参数代表 SystemUI 可见的所有通知信息。

NotificationListenerWrapper 获取到这些通知信息后,会调用子类的 onNotificationPosted() 来处理,这个方法由 NotificationListener 实现

    public void onNotificationPosted(final StatusBarNotification sbn,
            final RankingMap rankingMap) {
        if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
            mPresenter.getHandler().post(() -> {

                String key = sbn.getKey();
                // 判断是否已经有了这个通知
                boolean isUpdate =
                        mEntryManager.getNotificationData().get(key) != null;
				// ...
				
                if (isUpdate) {
                	// 更新通知
                    mEntryManager.updateNotification(sbn, rankingMap);
                } else {
                	// 添加通知
                    mEntryManager.addNotification(sbn, rankingMap);
                }
            });
        }
    }

NotificationListener#onNotificationPosted() 会把新来的通知信息交给 NotificationEntryManager 处理。

处理通知

NotificationEntryManager 是用来管理通知的,例如添加,删除,更新通知,而且还包括通知视图的加载,等等操作。这里通过 NotificationEntryManager#addNotification() 来分析它是如何实现通知的添加操作。

NotificationEntryManager#addNotification() 内部是由 addNotificationInternal() 实现的,它会调用 NotificationEntryManager#createNotificationViews() 来为这条通知创建通知图标以及通知视图的容器。

    protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
            throws InflationException {
        NotificationData.Entry entry = new NotificationData.Entry(sbn);
        // 1. 创建图标
        entry.createIcons(mContext, sbn);
        // 2. 实例化一个通知视图的容器
        inflateViews(entry, mListContainer.getViewParentForNotification(entry));
        return entry;
    }
    
    private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
        PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
                entry.notification.getUser().getIdentifier());
                
        final StatusBarNotification sbn = entry.notification;
        if (entry.row != null) {
        	// ...
        } else {
            // 使用异步线程加载视图,然后回调
            new RowInflaterTask().inflate(mContext, parent, entry,
                    // 这个lambda表达式就是视图加载完成的回调,参数row就是加载完成的视图
                    row -> {
                        // 为通知视图绑定信息
                        bindRow(entry, pmUser, sbn, row);
                        // entry根据数据创建真正的通知视图,并添加到row中,然后进行回调
                        // 这个回调,是在上面的bindRow()中设置的
                        updateNotification(entry, pmUser, sbn, row);
                    });
        }
    }

RowInflaterTask 使用一个异步线程,通过 LayoutInflater 加载 status_bar_notification_row.xml 布局作为通知视图的容器。注意,此时只是创建通知视图的父View而已,还没有创建真正显示通知信息的View。

RowInflaterTask#inflate() 加载完通知视图的父View后,就会通过最后一个参数进行回调,在这个回调才会真正创建显示通知信息的View。

回调中,首先调用 NotificationEntryManager#bindRow() 来为这个通知容器绑定各种信息,其中与创建通知View相关的绑定如下

    private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
            StatusBarNotification sbn, ExpandableNotificationRow row) {
         // ...
         
         // 设置通知视图加载完成的回调,这个回调方法是onAsyncInflationFinished()
		 row.setInflationCallback(this);
		
		// ...
	}

与此同时,第二个回调NotificationEntryManager#updateNotification() 就触发了通知视图的加载

    protected void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
            StatusBarNotification sbn, ExpandableNotificationRow row) {
		// ...
		
        // entry保存通知视图的父View
        entry.row = row;

		// ...
        		
        // 用数据创建通知视图,并添加到参数父View中
        row.updateNotification(entry);
    }

ExpandableNotificationRow#updateNotification() 会根据各种参数来决定创建哪些种类的通知视图,并把这个通知视图添加到刚才创建的父View中,最后会通过 onAsyncInflationFinished() 进行回调,而这个回调就是刚才在 NotificationEntryManager#bindRow() 设置的,由 NotificationEntryManager 实现。

    public void onAsyncInflationFinished(NotificationData.Entry entry) {
        mPendingNotifications.remove(entry.key);
        boolean isNew = mNotificationData.get(entry.key) == null;
        if (isNew && !entry.row.isRemoved()) {
            // 保存通知,并通过updateNotificationViews()回调,通知StatusBar更新通知图标和通知视图
            addEntry(entry);
        } else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {

        }
    }

NotificationEntryManager#addEntry() 会把新通知进行保存,然后对通知集合进行过滤和排序,最后通过 updateNotificationViews() 回调来通知 StatusBar 更新通知图标以及通知视图。

    public void updateNotificationViews() {
		// ...
		
		// 添加到通知面板的通知容器中
		mViewHierarchyManager.updateNotificationViews();

		// ...
		
        // 更新状态栏上,通知图标区的图标
        mNotificationIconAreaController.updateNotificationIcons();
    }

StatusBar 接收到更新通知视图的信息后,首先会通过 NotificationViewHierarchyManager#updateNotificationViews() 把新通知添加到下拉通知面板的通知容器中,然后再通过 NotificationIconAreaController 更新状态栏上的通知图标。

至此,状态栏上通知图标的控制流程已经分析完毕,那么用一副图总结下这个流程

通知图标显示流程图

发布了44 篇原创文章 · 获赞 30 · 访问量 400万+

猜你喜欢

转载自blog.csdn.net/zwlove5280/article/details/103893608