前言
上一篇我们具体分析了状态栏上状态图标,例如 wifi、蓝牙等图标的控制流程,本篇文章我们继续来分析下状态栏上通知图标的控制流程。主要分析当一个新通知来临时,新通知的图标是如何一步步显示到状态栏上的。
一、通知图标控制器
1、通过上一篇系统状态栏图标控制可知,状态栏图标是由一个叫StatusBarIconController接口控制显示的,而通知图标区域也有一个控制器,叫NotificationIconAreaController(它不是一个接口)。
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
public class NotificationIconAreaController implements DarkReceiver {
...
public NotificationIconAreaController(Context context, StatusBar statusBar) {
mStatusBar = statusBar;
mNotificationColorUtil = NotificationColorUtil.getInstance(context);//通知栏颜色工具
mContext = context;
mEntryManager = Dependency.get(NotificationEntryManager.class);
initializeNotificationAreaViews(context);//初始化通知栏区域视图
}
...
}
2、在NotificationIconAreaController的构造函数中会调用如下方法来创建通知图标的容器
public class NotificationIconAreaController implements DarkReceiver {
protected View mNotificationIconArea;
private NotificationIconContainer mNotificationIcons;
private NotificationStackScrollLayout mNotificationScrollLayout;
/**
* 初始化化通知图标区域视图
*/
protected void initializeNotificationAreaViews(Context context) {
reloadDimens(context);
LayoutInflater layoutInflater = LayoutInflater.from(context);
// 实例化通知图标区域视图
mNotificationIconArea = inflateIconArea(layoutInflater);
// mNotificationIcons才是真正存放通知图标的父容器
mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(R.id.notificationIcons);
mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
}
...
protected View inflateIconArea(LayoutInflater inflater) {
// 实例化通知图标区域视图
return inflater.inflate(R.layout.notification_icon_area, null);
}
...
}
3、在NotificationIconAreaController的inflateIconArea方法中会加载了R.layout.notification_icon_are.xml布局,来看下这个布局:
frameworks/base/packages/SystemUI/res/layout/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>
id为notificationIcons的控件就是状态栏通知图标容器,对应于上面代码的mNotificationIcons变量。
二、初始化通知图标区域
1、既然是NotificationIconAreaController自己创建了通知图标容器,那么通知图标是如何被添加到状态栏视图中的呢?这个过程主要是在StatusBar的makeStatusBarView方法中实现的,关键代码如下所示:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
...
// 创建状态栏视图
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment = (CollapsedStatusBarFragment) fragment;
// 初始化了通知图标区域
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);
...
})
.getFragmentManager()
.beginTransaction()
// CollapsedStatusBarFragment实现了状态栏的添加
.replace(R.id.status_bar_container, new CollapsedStatusBarFragment(), CollapsedStatusBarFragment.TAG)
.commit();
...
}
2、CollapsedStatusBarFragment的initNotificationIconArea方法如下所示:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
Bundle savedInstanceState) {
//CollapsedStatusBarFragment加载布局文件
return inflater.inflate(R.layout.status_bar, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mStatusBar = (PhoneStatusBarView) view;
}
// 初始化通知图标区域
public void initNotificationIconArea(NotificationIconAreaController
notificationIconAreaController) {
// 通知图标区域
ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
// 这个才是通知图标的父容器
mNotificationIconAreaInner = notificationIconAreaController.getNotificationInnerAreaView();
if (mNotificationIconAreaInner.getParent() != null) {
((ViewGroup) mNotificationIconAreaInner.getParent())
.removeView(mNotificationIconAreaInner);
}
// 把通知图标父容器添加到通知图标区域里
notificationIconArea.addView(mNotificationIconAreaInner);
// 这里其实显示了通知图标区域和中心图标区域
showNotificationIconArea(false);
}
上面代码的主要目的就是将通知图标控制器所构建的视图mNotificationIconAreaInner添加到布局status_bar.xml中id等于notification_icon_area的容器中,notification_icon_area容器在status_bar.xml布局中的位置如下所示:
frameworks/base/packages/SystemUI/res/layout/status_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<com.android.systemui.statusbar.phone.PhoneStatusBarView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:layout_width="match_parent"
android:layout_height="@dimen/status_bar_height"
android:id="@+id/status_bar"
android:background="@drawable/system_bar_background"
android:orientation="vertical"
android:focusable="false"
android:descendantFocusability="afterDescendants"
android:accessibilityPaneTitle="@string/status_bar"
>
...
<LinearLayout android:id="@+id/status_bar_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end"
android:orientation="horizontal"
>
...
<FrameLayout
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1">
...
<LinearLayout
android:id="@+id/status_bar_left_side"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:clipChildren="false"
>
...
<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
android:id="@+id/notification_icon_area"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="horizontal"
android:clipChildren="false"/>
</LinearLayout>
</FrameLayout>
</LinearLayout>
</com.android.systemui.statusbar.phone.PhoneStatusBarView>
三、监听通知的服务端
1、当一条新通知发送后,它会存储到通知服务端,也就是NotificationManagerService,那SystemUI是如何知道新通知来临的?这就需要SystemUI向NotificationManagerService注册一个"服务"(一个Binder)。
这个"服务"就相当于客户端SystemUI在服务端NotificationManagerService注册的一个回调。当有通知来临的时候,就会通过这个"服务"通知SystemUI。这个注册是在StatusBar#start()中完成的:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
...
protected NotificationListener mNotificationListener;
protected NotificationEntryManager mEntryManager;
...
public void start() {
...
// 向通知服务端注册一个"服务",用于接收通知信息的回调
mNotificationListener = Dependency.get(NotificationListener.class);
mNotificationListener.setUpWithPresenter(this, mEntryManager);
...
}
...
}
2、继续来看NotificationListener的setUpWithPresenter方法,该方法会调用父类的NotificationListenerWithPlugins的registerAsSystemService方法:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListener .java
public class NotificationListener extends NotificationListenerWithPlugins {
...
public void setUpWithPresenter(NotificationPresenter presenter, NotificationEntryManager entryManager) {
mPresenter = presenter;
mEntryManager = entryManager;
try {
registerAsSystemService(mContext, new ComponentName(mContext.getPackageName(), getClass().getCanonicalName()), UserHandle.USER_ALL);
} catch (RemoteException e) {
Log.e(TAG, "Unable to register notification listener", e);
}
}
//连接成功
@Override
public void onListenerConnected() {
...
}
//收到新的通知
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
...
}
//移除通知
@Override
public void onNotificationRemoved(StatusBarNotification sbn,
final RankingMap rankingMap) {
...
}
//更新通知
@Override
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
...
}
}
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationListenerWithPlugins.java
public class NotificationListenerWithPlugins extends NotificationListenerService implements
PluginListener<NotificationListenerController> {
...
@Override
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
super.registerAsSystemService(context, componentName, currentUser);
//获取PluginManager的具体实现类PluginManagerImpl,并将自身添加到PluginManagerImpl实例对象中
Dependency.get(PluginManager.class).addPluginListener(this, NotificationListenerController.class);
}
...
}
四、服务端接收到新的通知消息
1、当一条新的通知来临的时候,会触发NotificationListener的onNotificationPosted方法,该方法会调用状态栏条目管理者的addNotification方法,关键代码如下:
public class NotificationListener extends NotificationListenerWithPlugins {
...
protected NotificationEntryManager mEntryManager;//状态栏条目管理者
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
// 在主线程中进行更新
mPresenter.getHandler().post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
String key = sbn.getKey();
mEntryManager.removeKeyKeptForRemoteInput(key);
//我们第一次创建通知的时候getNotificationData返回的一定是null,因为还没有放进去,所以isUpdate = false,走添加通知的流程
boolean isUpdate = mEntryManager.getNotificationData().get(key) != null;
...
//判断到来的通知是需要更新还是添加
if (isUpdate) {
// 更新通知操作
mEntryManager.updateNotification(sbn, rankingMap);
} else {
// 添加新通知操作
mEntryManager.addNotification(sbn, rankingMap);
}
});
}
}
...
}
2、NotificationEntryManager的addNotification方法又进一步调用addNotificationInternal方法
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
VisualStabilityManager.Callback {
...
@Override
public void addNotification(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) {
...
addNotificationInternal(notification, ranking);
...
}
...
private void addNotificationInternal(StatusBarNotification notification,
NotificationListenerService.RankingMap ranking) throws InflationException {
//获取唯一标识key,key打印出来是这样的:0|com.example.app3|110|null|10161
String key = notification.getKey();
mNotificationData.updateRanking(ranking);
//创建Entry实例对象
NotificationData.Entry shadeEntry = createNotificationViews(notification);
...
}
}
3、addNotificationInternal方法进一步调用createNotificationViews方法,该方法会继续调用inflateViews来构建视图对象
public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
VisualStabilityManager.Callback {
...
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn)
throws InflationException {
NotificationData.Entry entry = new NotificationData.Entry(sbn);
Dependency.get(LeakDetector.class).trackInstance(entry);
entry.createIcons(mContext, sbn);
//构建视图
inflateViews(entry, mListContainer.getViewParentForNotification(entry));
return entry;
}
...
}
4、inflateViews方法会创建RowInflaterTask对象并调用inflate方法:
public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
VisualStabilityManager.Callback {
...
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) {
entry.reset();
updateNotification(entry, pmUser, sbn, entry.row);
} else {
//创建构建视图的任务对象
new RowInflaterTask().inflate(mContext, parent, entry,
// 加载完成的回调,这里的加载指的仅仅是一个空视图
row -> {
// 绑定监听事件和回调
bindRow(entry, pmUser, sbn, row);
// 在视图上更新通知信息
updateNotification(entry, pmUser, sbn, row);
});
}
}
}
5、RowInflaterTask的inflate方法会创建异步构建视图任务对象AsyncLayoutInflater并调用他的inflate方法:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/RowInflaterTask.java
public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener {
/**
* 构建一个新的通知栏视图
*/
public void inflate(Context context, ViewGroup parent, NotificationData.Entry entry,
RowInflationFinishedListener listener) {
...
//使用异步方式加载视图的Inflater
AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
...
inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}
}
6、AsyncLayoutInflater对象调用inflate将status_bar_notification_row.xml布局文件转化为视图对象,该布局文件的具体内容如下所示:
frameworks/base/packages/SystemUI/res/layout/status_bar_notification_row.xml
<?xml version="1.0" encoding="utf-8"?>
<com.android.systemui.statusbar.ExpandableNotificationRow
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="true"
android:clickable="true"
>
<com.android.systemui.statusbar.NotificationBackgroundView
android:id="@+id/backgroundNormal"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.systemui.statusbar.NotificationBackgroundView
android:id="@+id/backgroundDimmed"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.systemui.statusbar.NotificationContentView
android:id="@+id/expanded"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.android.systemui.statusbar.NotificationContentView
android:id="@+id/expandedPublic"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/veto"
android:layout_width="48dp"
android:layout_height="0dp"
android:gravity="end"
android:layout_marginEnd="-80dp"
android:background="@null"
android:paddingEnd="8dp"
android:paddingStart="8dp"/>
<ViewStub
android:layout="@layout/notification_children_container"
android:id="@+id/child_container_stub"
android:inflatedId="@+id/notification_children_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<ViewStub
android:layout="@layout/notification_guts"
android:id="@+id/notification_guts_stub"
android:inflatedId="@+id/notification_guts"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<com.android.systemui.statusbar.notification.FakeShadowView
android:id="@+id/fake_shadow"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.android.systemui.statusbar.ExpandableNotificationRow>
7、从视图树可以看到这整个布局文件status_bar_notification_row.xml就是一条通知ExpandableNotificationRow:
8、AsyncLayoutInflater是采用异步方式来加载布局文件的,待布局文件加载完之后会调用之前在RowInflaterTask中设置的onInflateFinished回调方法:
frameworks/support/asynclayoutinflater/src/main/java/androidx/asynclayoutinflater/view/AsyncLayoutInflater.java
public final class AsyncLayoutInflater {
...
private Callback mHandlerCallback = new Callback() {
@Override
public boolean handleMessage(Message msg) {
InflateRequest request = (InflateRequest) msg.obj;
if (request.view == null) {
request.view = mInflater.inflate(request.resid, request.parent, false);
}
//布局加载完毕,调用之前在RowInflaterTask中设置的回调方法
request.callback.onInflateFinished(request.view, request.resid, request.parent);
mInflateThread.releaseRequest(request);
return true;
}
};
...
}
9、RowInflaterTask的onInflateFinished方法会继续回调之前在NotificationEntryManager的inflateViews方法中使用lamb表达式设置的回调方法:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationEntryManager.java
public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
VisualStabilityManager.Callback {
...
private void inflateViews(NotificationData.Entry entry, ViewGroup parent) {
PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
entry.notification.getUser().getIdentifier());
...
new RowInflaterTask().inflate(mContext, parent, entry,
//回调方法
row -> {
//填充数据,绑定监听事件和回调
bindRow(entry, pmUser, sbn, row);
// 在视图上更新通知信息
updateNotification(entry, pmUser, sbn, row);
});
...
}
...
}
10、bindRow方法会填充ExpandableNotificationRow的数据,并且再将ExpandableNotificationRow数据绑定到StatusBar:
public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
VisualStabilityManager.Callback {
...
//填充数据并绑定到StatusBarNotificationPresenter中
private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setExpansionLogger(this, entry.notification.getKey());
row.setGroupManager(mGroupManager);
row.setHeadsUpManager(mHeadsUpManager);
row.setOnExpandClickListener(mPresenter);
row.setInflationCallback(this);//这个回调很重要
row.setLongPressListener(getNotificationLongClicker());
mListContainer.bindRow(row);
mRemoteInputManager.bindRow(row);
final String pkg = sbn.getPackageName();
String appname = pkg;
try {
final ApplicationInfo info = pmUser.getApplicationInfo(pkg,
PackageManager.MATCH_UNINSTALLED_PACKAGES
| PackageManager.MATCH_DISABLED_COMPONENTS);
if (info != null) {
appname = String.valueOf(pmUser.getApplicationLabel(info));
}
} catch (PackageManager.NameNotFoundException e) {
// Do nothing
}
row.setAppName(appname);
row.setOnDismissRunnable(() ->
performRemoveNotification(row.getStatusBarNotification()));
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (ENABLE_REMOTE_INPUT) {
row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}
row.setAppOpsOnClickListener(mOnAppOpsClickListener);
//StatusBar实现了mCallback
mCallback.onBindRow(entry, pmUser, sbn, row);
}
...
}
11、updateNotification方法来更新ExpandableNotificationRow的视图:
public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
VisualStabilityManager.Callback {
...
protected void updateNotification(NotificationData.Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setNeedsRedaction(mLockscreenUserManager.needsRedaction(entry));
boolean isLowPriority = mNotificationData.isAmbient(sbn.getKey());
boolean isUpdate = mNotificationData.get(entry.key) != null;
boolean wasLowPriority = row.isLowPriority();
row.setIsLowPriority(isLowPriority);
row.setLowPriorityStateUpdated(isUpdate && (wasLowPriority != isLowPriority));
// bind the click event to the content area
mNotificationClicker.register(row, sbn);
// 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);
entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
entry.autoRedacted = entry.notification.getNotification().publicVersion == null;
entry.row = row;
entry.row.setOnActivatedListener(mPresenter);
boolean useIncreasedCollapsedHeight = mMessagingUtil.isImportantMessaging(sbn,
mNotificationData.getImportance(sbn.getKey()));
boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
&& !mPresenter.isPresenterFullyCollapsed();
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
row.updateNotification(entry);//更新通知视图
}
...
}
12 ExpandableNotificationRow的updateNotification方法如下所示:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
public class ExpandableNotificationRow extends ActivatableNotificationView
implements PluginListener<NotificationMenuRowPlugin> {
...
private final NotificationInflater mNotificationInflater;
...
public void updateNotification(NotificationData.Entry entry) {
mEntry = entry;
mStatusBarNotification = entry.notification;
//调用NotificationInflater的inflateNotificationViews方法
mNotificationInflater.inflateNotificationViews();
cacheIsSystemNotification();
}
...
}
13、继续来看下NotificationInflater的inflateNotificationViews方法:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
public class NotificationInflater {
...
public void inflateNotificationViews() {
inflateNotificationViews(FLAG_REINFLATE_ALL);
}
void inflateNotificationViews(int reInflateFlags) {
//如果通知已经被删除,直接返回
if (mRow.isRemoved()) {
// We don't want to reinflate anything for removed notifications. Otherwise views might
// be readded to the stack, leading to leaks. This may happen with low-priority groups
// where the removal of already removed children can lead to a reinflation.
return;
}
StatusBarNotification sbn = mRow.getEntry().notification;
AsyncInflationTask task = new AsyncInflationTask(sbn, reInflateFlags, mRow,
mIsLowPriority,
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
mCallback, mRemoteViewClickHandler);
if (mCallback != null && mCallback.doInflateSynchronous()) {
task.onPostExecute(task.doInBackground());
} else {
task.execute();
}
}
...
}
14、通过上面的代码可以知道,通知视图是通过异步进行加载的,来看这个AsyncInflationTask,首先看下doInBackground方法:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInflater.java
public class NotificationInflater {
...
public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
implements InflationCallback, InflationTask {
...
@Override
protected InflationProgress doInBackground(Void... params) {
try {
final Notification.Builder recoveredBuilder
= Notification.Builder.recoverBuilder(mContext,
mSbn.getNotification());
Context packageContext = mSbn.getPackageContext(mContext);
Notification notification = mSbn.getNotification();
//如果是多媒体类型的通知
if (notification.isMediaNotification()) {
MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
packageContext);
processor.processNotification(notification, recoveredBuilder);
}
//构造通知View
return createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mIsChildInGroup,
mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
packageContext);
} catch (Exception e) {
mError = e;
return null;
}
}
...
//InflationProgress是NotificationContentInflater的静态内部类,用于构造通知View
static class InflationProgress {
//RemoteViews类型的视图主要用于跨进程显示视图
private RemoteViews newContentView;
private RemoteViews newHeadsUpView;
private RemoteViews newExpandedView;
private RemoteViews newAmbientView;
private RemoteViews newPublicView;
@VisibleForTesting
Context packageContext;
private View inflatedContentView;
private View inflatedHeadsUpView;
private View inflatedExpandedView;
private View inflatedAmbientView;
private View inflatedPublicView;
private CharSequence headsUpStatusBarText;
private CharSequence headsUpStatusBarTextPublic;
}
...
}
}
15、doInBackground方法会继续调用createRemoteViews方法,此方法是构造状态栏通知View的核心方法,会返回通知布局文:
public class NotificationInflater {
...
private static InflationProgress createRemoteViews(int reInflateFlags,
Notification.Builder builder, boolean isLowPriority, boolean isChildInGroup,
boolean usesIncreasedHeight, boolean usesIncreasedHeadsUpHeight, boolean redactAmbient,
Context packageContext) {
InflationProgress result = new InflationProgress();
isLowPriority = isLowPriority && !isChildInGroup;
if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {//默认布局
result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
}
if ((reInflateFlags & FLAG_REINFLATE_EXPANDED_VIEW) != 0) {
result.newExpandedView = createExpandedView(builder, isLowPriority);
}
if ((reInflateFlags & FLAG_REINFLATE_HEADS_UP_VIEW) != 0) {
result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
}
if ((reInflateFlags & FLAG_REINFLATE_PUBLIC_VIEW) != 0) {
result.newPublicView = builder.makePublicContentView();
}
if ((reInflateFlags & FLAG_REINFLATE_AMBIENT_VIEW) != 0) {
result.newAmbientView = redactAmbient ? builder.makePublicAmbientNotification()
: builder.makeAmbientNotification();
}
result.packageContext = packageContext;
result.headsUpStatusBarText = builder.getHeadsUpStatusBarText(false /* showingPublic */);
result.headsUpStatusBarTextPublic = builder.getHeadsUpStatusBarText(
true /* showingPublic */);
return result;
}
...
}
16、通过上面的代码可以看出,系统会根据不同的flag(不同通知类型)来加载不同的通知布局,我们这里以最简单的默认布局(createContentView)来分析,可以看到直接通过Notification.Builder来创建通知View:
public class NotificationInflater {
...
private static RemoteViews createContentView(Notification.Builder builder,
boolean isLowPriority, boolean useLarge) {
if (isLowPriority) {
return builder.makeLowPriorityContentView(false /* useRegularSubtext */);
}
return builder.createContentView(useLarge);
}
...
}
17、继续来看Notification#Builder的createContentView方法:
frameworks/base/core/java/android/app/Notification.java
public class Notification implements Parcelable
{
...
//构造最终的通知UI布局
public RemoteViews createContentView() {
return createContentView(false);
}
public RemoteViews createContentView(boolean increasedHeight) {
//mN.contentView其实就是我们需要自定义通知View时调用setContent方法设置RemoteView,如果没有设置就加载系统默认布局
if (mN.contentView != null && useExistingRemoteView()) {
return mN.contentView;
} else if (mStyle != null) {
final RemoteViews styleView = mStyle.makeContentView(increasedHeight);
if (styleView != null) {
return styleView;
}
}
//加载标准的默认模板
return applyStandardTemplate(getBaseLayoutResource(), null /* result */);
}
//获取布局文件,默认加载布局就是R.layout.notification_template_material_base
private int getBaseLayoutResource() {
return R.layout.notification_template_material_base;
}
...
}
18、applyStandardTemplate方法会给布局设置寬高颜色字体等属性:
public class Notification implements Parcelable
{
...
private RemoteViews applyStandardTemplate(int resId, TemplateBindResult result) {
return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this), result);
}
private RemoteViews applyStandardTemplate(int resId, boolean hasProgress,
TemplateBindResult result) {
return applyStandardTemplate(resId, mParams.reset().hasProgress(hasProgress)
.fillTextsFrom(this), result);
}
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p,
TemplateBindResult result) {
RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
resetStandardTemplate(contentView);
final Bundle ex = mN.extras;
updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient, p.headerTextSecondary);
bindLargeIconAndReply(contentView, p, result);
boolean showProgress = handleProgressBar(p.hasProgress, contentView, ex);
if (p.title != null) {
contentView.setViewVisibility(R.id.title, View.VISIBLE);
contentView.setTextViewText(R.id.title, processTextSpans(p.title));
if (!p.ambient) {
setTextViewColorPrimary(contentView, R.id.title);
}
contentView.setViewLayoutWidth(R.id.title, showProgress
? ViewGroup.LayoutParams.WRAP_CONTENT
: ViewGroup.LayoutParams.MATCH_PARENT);
}
if (p.text != null) {
int textId = showProgress ? com.android.internal.R.id.text_line_1
: com.android.internal.R.id.text;
contentView.setTextViewText(textId, processTextSpans(p.text));
if (!p.ambient) {
setTextViewColorSecondary(contentView, textId);
}
contentView.setViewVisibility(textId, View.VISIBLE);
}
setContentMinHeight(contentView, showProgress || mN.hasLargeIcon());
return contentView;
}
...
}
19、经过15-18步的分析,我们已经知道了通知栏视图的创建流程,现在让我们重新回到第14步,在AsyncInflationTask的doInBackground方法执行完毕,接着就会执行onPostExecute方法:
public class NotificationInflater {
...
public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
implements InflationCallback, InflationTask {
...
@Override
protected InflationProgress doInBackground(Void... params) {
...
}
@Override
protected void onPostExecute(InflationProgress result) {
if (mError == null) {
mCancellationSignal = apply(result, mReInflateFlags, mRow, mRedactAmbient,
mRemoteViewClickHandler, this);
} else {
handleError(mError);
}
}
...
}
}
20、AsyncInflationTask的onPostExecute方法会进一步调用NotificationInflater的apply方法,该方法会根据不同的通知类型做不同的事,这里选一个最普通的类型FLAG_REINFLATE_CONTENT_VIEW来看一下这个方法:
public class NotificationInflater {
...
public static CancellationSignal apply(InflationProgress result, int reInflateFlags,
ExpandableNotificationRow row, boolean redactAmbient,
RemoteViews.OnClickHandler remoteViewClickHandler,
@Nullable InflationCallback callback) {
NotificationData.Entry entry = row.getEntry();
NotificationContentView privateLayout = row.getPrivateLayout();//获取id为R.id.expanded的视图View
NotificationContentView publicLayout = row.getPublicLayout();//获取id为R.id.expandedPublic的视图View
final HashMap<Integer, CancellationSignal> runningInflations = new HashMap<>();
int flag = FLAG_REINFLATE_CONTENT_VIEW;
if ((reInflateFlags & flag) != 0) {//通知栏默认布局
//是否是新增加的View
boolean isNewView = !canReapplyRemoteView(result.newContentView, entry.cachedContentView);
//创建回调对象
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
result.inflatedContentView = v;
}
@Override
public RemoteViews getRemoteView() {
return result.newContentView;
}
};
applyRemoteView(result, reInflateFlags, flag, row, redactAmbient,
isNewView, remoteViewClickHandler, callback, entry, privateLayout,
privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
NotificationContentView.VISIBLE_TYPE_CONTRACTED),
runningInflations, applyCallback);
}
...
}
...
}
21、继续看applyRemoteView方法:
public class NotificationInflater {
...
static void applyRemoteView(final InflationProgress result,
final int reInflateFlags, int inflationId,
final ExpandableNotificationRow row,
final boolean redactAmbient, boolean isNewView,
RemoteViews.OnClickHandler remoteViewClickHandler,
@Nullable final InflationCallback callback, NotificationData.Entry entry,
NotificationContentView parentLayout, View existingView,
NotificationViewWrapper existingWrapper,
final HashMap<Integer, CancellationSignal> runningInflations,
ApplyCallback applyCallback) {
RemoteViews newContentView = applyCallback.getRemoteView();
if (callback != null && callback.doInflateSynchronous()) {
try {
//新增通知的情况
if (isNewView) {
View v = newContentView.apply(
result.packageContext,
parentLayout,
remoteViewClickHandler);
v.setIsRootNamespace(true);
applyCallback.setResultView(v);
} else {
newContentView.reapply(
result.packageContext,
existingView,
remoteViewClickHandler);
existingWrapper.onReinflated();
}
}
...
}
...
}
}
22、重新回到第20步的apply方法,继续向下看,会继续调用一个关键方法finishIfDone,
public class NotificationInflater {
...
public static CancellationSignal apply(InflationProgress result, int reInflateFlags,
ExpandableNotificationRow row, boolean redactAmbient,
RemoteViews.OnClickHandler remoteViewClickHandler,
@Nullable InflationCallback callback) {
...
finishIfDone(result, reInflateFlags, runningInflations, callback, row,
redactAmbient);
CancellationSignal cancellationSignal = new CancellationSignal();
cancellationSignal.setOnCancelListener(
() -> runningInflations.values().forEach(CancellationSignal::cancel));
return cancellationSignal;
}
...
}
23、finishIfDone方法中比较关键的代码点在于,会通过调用privateLayout的setContractedChild方法将remouteViews添加到privateLayout视图中:
public class NotificationInflater {
...
private static boolean finishIfDone(InflationProgress result, int reInflateFlags,
HashMap<Integer, CancellationSignal> runningInflations,
@Nullable InflationCallback endListener, ExpandableNotificationRow row,
boolean redactAmbient) {
Assert.isMainThread();
NotificationData.Entry entry = row.getEntry();
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
if (runningInflations.isEmpty()) {
if ((reInflateFlags & FLAG_REINFLATE_CONTENT_VIEW) != 0) {
if (result.inflatedContentView != null) {
//result.inflatedContentView就是RemoteViews
//这里将result.inflatedContentView添加到privateLayout(R.id.expanded)
privateLayout.setContractedChild(result.inflatedContentView);
}
entry.cachedContentView = result.newContentView;
}
...
if (endListener != null) {
//RemoteView加载到NotificationContentView之后会回调endListener的onAsyncInflationFinished方法
endListener.onAsyncInflationFinished(row.getEntry());
}
return true;
}
return false;
}
...
}
23、NotificationContentView的setContractedChild方法代码如下:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
public class NotificationContentView extends FrameLayout {
...
public void setContractedChild(View child) {
if (mContractedChild != null) {
mContractedChild.animate().cancel();
removeView(mContractedChild);
}
addView(child);
mContractedChild = child;
mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
mContainingNotification);
}
...
}
上面23个步骤具体讲述了系统通知管理服务端,从接收到通知消息到创建对应通知消息视图View,并将RemoteView加载到NotificationContentView的过程。
五、显示新的通知图标
1、接下来我们继续来看一下NotificationContentView创建完毕之后,是如何更新到系统状态栏视图上的。
继续看上面提到过的NotificationInflater的finishIfDone方法,该方法的最后会调用endListener的onAsyncInflationFinished方法:
public class NotificationInflater {
...
private static boolean finishIfDone(InflationProgress result, int reInflateFlags,
HashMap<Integer, CancellationSignal> runningInflations,
@Nullable InflationCallback endListener, ExpandableNotificationRow row,
boolean redactAmbient) {
...
if (endListener != null) {
//RemoteView加载到NotificationContentView之后会回调endListener的onAsyncInflationFinished方法
endListener.onAsyncInflationFinished(row.getEntry());
}
return true;
}
return false;
}
...
}
//回调接口
public interface InflationCallback {
void handleInflationException(StatusBarNotification notification, Exception e);
void onAsyncInflationFinished(NotificationData.Entry entry);
default boolean doInflateSynchronous() {
return false;
}
}
2、endListener的回调很关键,该回调最早是在NotificationEntryManager的bindRow方法中设置的,也因此这里会层层回调,最终回调回NotificationEntryManager的onAsyncInflationFinished方法,该方法首先会判断是否是 新的通知,如果是则调用addEntry方法,该方法继续调用addNotificationViews方法,addNotificationViews方法在添加新的通知栏视图数据之后,会继续调用updateNotifications方法,updateNotifications方法先是对通知栏视图数据进行过滤和排序,然后再用mPresenter层的updateNotificationViews方法来刷新通知栏视图:
public class NotificationEntryManager implements Dumpable, NotificationInflater.InflationCallback,
ExpandableNotificationRow.ExpansionLogger, NotificationUpdateHandler,
VisualStabilityManager.Callback {
...
//填充数据并绑定到StatusBarNotificationPresenter中
private void bindRow(NotificationData.Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
row.setExpansionLogger(this, entry.notification.getKey());
row.setGroupManager(mGroupManager);
row.setHeadsUpManager(mHeadsUpManager);
row.setOnExpandClickListener(mPresenter);
row.setInflationCallback(this);//这里会设置回调方法,这个回调很关键
...
}
...
//最终的回调方法
@Override
public void onAsyncInflationFinished(NotificationData.Entry entry) {
mPendingNotifications.remove(entry.key);
boolean isNew = mNotificationData.get(entry.key) == null;
if (isNew && !entry.row.isRemoved()) {
//1如果是新的通知,调用addEntry方法
addEntry(entry);
} else if (!isNew && entry.row.hasLowPriorityStateUpdated()) {
mVisualStabilityManager.onLowPriorityUpdated(entry);
mPresenter.updateNotificationViews();
}
entry.row.setLowPriorityStateUpdated(false);
}
private void addEntry(NotificationData.Entry shadeEntry) {
...
//2继续调用addNotificationViews方法来增加通知栏视图
addNotificationViews(shadeEntry);
...
}
protected void addNotificationViews(NotificationData.Entry entry) {
if (entry == null) {
return;
}
//添加通知视图数据对象
mNotificationData.add(entry);
tagForeground(entry.notification);
//3继续调用updateNotifications方法
updateNotifications();
}
public void updateNotifications() {
//对通知进行过滤和排序
mNotificationData.filterAndSort();
//4继续调用P层的updateNotificationViews方法刷新通知栏视图
mPresenter.updateNotificationViews();
}
}
3、NotificationEntryManager的mPresenter的实现者是在StatusBar,来看下状态栏对象的updateNotificationViews方法,在该方法中,首先是将通知视图添加到通知栏视图中,然后再更新通知栏视图和和视图图标:
public class StatusBar extends SystemUI implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
...
@Override
public void updateNotificationViews() {
...
// 将通知视图添加到通知栏视图中
mViewHierarchyManager.updateNotificationViews();
updateSpeedBumpIndex();
updateFooter();
updateEmptyShadeView();
updateQsExpansionEnabled();
// 这里不仅仅更新了通知面版的通知视图,也更新了状态栏的通知图标
mNotificationIconAreaController.updateNotificationIcons();
}
...
}
4、将通知视图添加到通知栏视图中的具体方法在NotificationViewHierarchyManager的updateNotificationViews方法中:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
public class NotificationViewHierarchyManager {
...
public void updateNotificationViews() {
//拿到所有的通知条目,条目中封装了整个通知的信息
ArrayList<NotificationData.Entry> activeNotifications = mEntryManager.getNotificationData()
.getActiveNotifications();
//创建ExpandableNotificationRow,用来承载每条通知RemoteView
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
final int N = activeNotifications.size();
//遍历全部通知
for (int i = 0; i < N; i++) {
NotificationData.Entry ent = activeNotifications.get(i);
...
//经过过滤之后全部添加到toShow
toShow.add(ent.row);
...
}
}
...
//移除通知子条目
removeNotificationChildren();
//遍历需要显示的通知,添加到NotificationStackScrollLayout
for (int i = 0; i < toShow.size(); i++) {
View v = toShow.get(i);
//如果是新增通知则需要添加,addContainerView
if (v.getParent() == null) {
mVisualStabilityManager.notifyViewAddition(v);
//将通知条目视图添加到通知栏视图NotificationStackScrollLayout中
mListContainer.addContainerView(v);
}
}
//后面会对通知视图进行排序等操作
}
...
}
5、NotificationStackScrollLayout的addContainerView方法如下所示:
frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
public class NotificationStackScrollLayout extends ViewGroup
implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider,
NotificationListContainer {
...
@Override
public void addContainerView(View v) {
addView(v);
}
...
}
6、分析完通知视图添加到通知栏视图的过程,接下来我们继续来看下是如何更新通知栏和状态栏视图的,该过程主要是在NotificationIconAreaController的updateNotificationIcons方法中实现的:
public class NotificationIconAreaController implements DarkReceiver {
...
public void updateNotificationIcons() {
//更新状态栏图标
updateStatusBarIcons();
//向状态栏添加通知图标
updateIconsForLayout(entry -> entry.expandedIcon, mShelfIcons,
NotificationShelf.SHOW_AMBIENT_ICONS, false /* hideDismissed */,
false /* hideRepliedMessages */);
applyNotificationIconsTint();
}
public void updateStatusBarIcons() {
updateIconsForLayout(entry -> entry.icon, mNotificationIcons,
false /* showAmbient */, true /* hideDismissed */, true /* hideRepliedMessages */);
}
...
}
7、可以发现updateIconsForLayout方法:
public class NotificationIconAreaController implements DarkReceiver {
...
private void updateIconsForLayout(Function<NotificationData.Entry, StatusBarIconView> function,
NotificationIconContainer hostLayout, boolean showAmbient, boolean hideDismissed,
boolean hideRepliedMessages) {
// toShow保存即将显示的图标
ArrayList<StatusBarIconView> toShow = new ArrayList<>(
mNotificationScrollLayout.getChildCount());
// 过滤通知,并保存需要显示的通知图标
for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
// 获取一个通知视图
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
if (shouldShowNotificationIcon(ent, showAmbient, hideDismissed,hideRepliedMessages)) {
//添加图标
toShow.add(function.apply(ent));
}
}
}
ArrayMap<String, ArrayList<StatusBarIcon>> replacingIcons = new ArrayMap<>();
ArrayList<View> toRemove = new ArrayList<>();
for (int i = 0; i < hostLayout.getChildCount(); i++) {
View child = hostLayout.getChildAt(i);
if (!(child instanceof StatusBarIconView)) {
continue;
}
if (!toShow.contains(child)) {
boolean iconWasReplaced = false;
StatusBarIconView removedIcon = (StatusBarIconView) child;
String removedGroupKey = removedIcon.getNotification().getGroupKey();
for (int j = 0; j < toShow.size(); j++) {
StatusBarIconView candidate = toShow.get(j);
if (candidate.getSourceIcon().sameAs((removedIcon.getSourceIcon()))
&& candidate.getNotification().getGroupKey().equals(removedGroupKey)) {
if (!iconWasReplaced) {
iconWasReplaced = true;
} else {
iconWasReplaced = false;
break;
}
}
}
if (iconWasReplaced) {
ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(removedGroupKey);
if (statusBarIcons == null) {
statusBarIcons = new ArrayList<>();
replacingIcons.put(removedGroupKey, statusBarIcons);
}
statusBarIcons.add(removedIcon.getStatusBarIcon());
}
toRemove.add(removedIcon);
}
}
// removing all duplicates
ArrayList<String> duplicates = new ArrayList<>();
for (String key : replacingIcons.keySet()) {
ArrayList<StatusBarIcon> statusBarIcons = replacingIcons.get(key);
if (statusBarIcons.size() != 1) {
duplicates.add(key);
}
}
replacingIcons.removeAll(duplicates);
hostLayout.setReplacingIcons(replacingIcons);
final int toRemoveCount = toRemove.size();
for (int i = 0; i < toRemoveCount; i++) {
hostLayout.removeView(toRemove.get(i));
}
final FrameLayout.LayoutParams params = generateIconLayoutParams();
for (int i = 0; i < toShow.size(); i++) {
StatusBarIconView v = toShow.get(i);
// The view might still be transiently added if it was just removed and added again
hostLayout.removeTransientView(v);
if (v.getParent() == null) {
if (hideDismissed) {
v.setOnDismissListener(mUpdateStatusBarIcons);
}
hostLayout.addView(v, i, params);
}
}
hostLayout.setChangingViewPositions(true);
// Re-sort notification icons
final int childCount = hostLayout.getChildCount();
for (int i = 0; i < childCount; i++) {
View actual = hostLayout.getChildAt(i);
StatusBarIconView expected = toShow.get(i);
if (actual == expected) {
continue;
}
hostLayout.removeView(expected);
hostLayout.addView(expected, i);
}
hostLayout.setChangingViewPositions(false);
hostLayout.setReplacingIcons(null);
}
...
}
到此通知已经全部的创建,加载以及添加完成。
五、总结
本篇文章大概梳理了通知布局的加载,数据填充以及添加和显示,整个流程大概就是从framework拿到StatusBarNotification,封装成NotificationEntry,之后再根据不同通知类型加载不同布局,如果没有自定义布局则加载默认布局,创建RemoteView,添加到ExpandableNotificationRow的中的id为R.id.expanded的NotificationContentView,之后再将ExpandableNotificationRow添加到NotificationStackScrollLayout,整个通知布局如下图: