一、通知栏的布局的添加
status_bar.xml中的左边区域增加了通知图标显示的区域
<LinearLayout
android:id="@+id/status_bar_left_side"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:clipChildren="false"
>
.......................
//notification icon显示区域
<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>
StatusBar添加fragment时初始化NotificationIconArea
protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
..................
FragmentHostManager.get(mStatusBarWindow)
.addTagListener(CollapsedStatusBarFragment.TAG, (tag, fragment) -> {
CollapsedStatusBarFragment statusBarFragment =
(CollapsedStatusBarFragment) fragment;
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController);//初始化通知栏区域
PhoneStatusBarView oldStatusBarView = mStatusBarView;
..................
CollapsedStatusBarFragment.initNotifcationIconArea
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为notification_icon_area.xml,先判断是否存在view,通过view的父布局将其自身remove,然后再addview。即把notification_icon_area.xml的view添加到notification_icon_area的view里作为子视图
当下拉状态栏时,状态栏的图标ico会慢慢变为透明。
//隐藏系统icon区域
public void hideSystemIconArea(boolean animate) {
animateHide(mSystemIconArea, animate);
}
//隐藏通知icon区域
public void hideNotificationIconArea(boolean animate) {
animateHide(mNotificationIconAreaInner, animate);
animateHide(mCenteredIconArea, animate);
}
二、通知的监听加载流程
StatusBar.start添加Notification的注册监听,最终是在NotificationListenerWithPlugins注册的
com.android.systemui.statusbar.phone.NotificationListenerWithPlugins.java
public void registerAsSystemService(Context context, ComponentName componentName,
int currentUser) throws RemoteException {
super.registerAsSystemService(context, componentName, currentUser);
Dependency.get(PluginManager.class).addPluginListener(this,
NotificationListenerController.class);
}
在NotificationListener处理通知监听的回调
com.android.systemui.statusbar.NotificationListener.java
//监听连接
public void onListenerConnected() {
if (DEBUG) Log.d(TAG, "onListenerConnected");
onPluginConnected();
final StatusBarNotification[] notifications = getActiveNotifications();//所有激活的通知,即没有被移除的通知
if (notifications == null) {
Log.w(TAG, "onListenerConnected unable to get active notifications.");
return;
}
final RankingMap currentRanking = getCurrentRanking();
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
for (StatusBarNotification sbn : notifications) {
mEntryManager.addNotification(sbn, currentRanking);//添加到通知实体类进行管理
}
});
NotificationManager noMan = mContext.getSystemService(NotificationManager.class);
onSilentStatusBarIconsVisibilityChanged(noMan.shouldHideSilentStatusBarIcons());
}
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
.....................
//收到更新或添加的通知
if (isUpdate) {
mEntryManager.removeNotification(key, rankingMap, UNDEFINED_DISMISS_REASON);
} else {
mEntryManager.getNotificationData()
.updateRanking(rankingMap);
}
return;
}
if (isUpdate) {
mEntryManager.updateNotification(sbn, rankingMap);
} else {
mEntryManager.addNotification(sbn, rankingMap);
}
});
}
}
//删除通知
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
int reason) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
final String key = sbn.getKey();
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
mEntryManager.removeNotification(key, rankingMap, reason);
});
}
}
//按通知优先级,重新排列通知位置
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onRankingUpdate");
if (rankingMap != null) {
RankingMap r = onPluginRankingUpdate(rankingMap);
Dependency.get(Dependency.MAIN_HANDLER).post(() -> {
mEntryManager.updateNotificationRanking(r);
});
}
}
我们重点看看是如何添加新通知的
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 rankingMap) throws InflationException {
String key = notification.getKey();
mNotificationData.updateRanking(rankingMap);
NotificationListenerService.Ranking ranking = new NotificationListenerService.Ranking();
rankingMap.getRanking(key, ranking);
NotificationEntry entry = new NotificationEntry(notification, ranking);
Dependency.get(LeakDetector.class).trackInstance(entry);
// Construct the expanded view.
requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
REASON_CANCEL));
.............
}
先对通知按优先级进行排列,然后创建出NotificationEntry的实体,最后通过inflate添加到布局中。requireBinder()中返回的是NotificationRowBinder,而NotficationRowBinder是由setRowBinder传入的,setRowBinder由StatusBar.setUpPresenter调用,参数是rowBinder。
private void setUpPresenter() {
....................
final NotificationRowBinderImpl rowBinder =
new NotificationRowBinderImpl(
mContext,
SystemUIFactory.getInstance().provideAllowNotificationLongPress());
........................
mEntryManager.setRowBinder(rowBinder);
.....................
}
public void setRowBinder(NotificationRowBinder notificationRowBinder) {
mNotificationRowBinder = notificationRowBinder;
}
private NotificationRowBinder requireBinder() {
..............
return mNotificationRowBinder;
}
最终是进入到了NotificationRowBinderImpl.inflateViews函数里
com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl.java
public void inflateViews(
NotificationEntry entry,
Runnable onDismissRunnable)
throws InflationException {
ViewGroup parent = mListContainer.getViewParentForNotification(entry);
PackageManager pmUser = StatusBar.getPackageManagerForUser(mContext,
entry.notification.getUser().getIdentifier());
final StatusBarNotification sbn = entry.notification;
//存在就更新通知,不存在就装载布局
if (entry.rowExists()) {
entry.updateIcons(mContext, sbn);
entry.reset();
updateNotification(entry, pmUser, sbn, entry.getRow());
} else {
entry.createIcons(mContext, sbn);
new RowInflaterTask().inflate(mContext, parent, entry,
row -> {
bindRow(entry, pmUser, sbn, row, onDismissRunnable);
updateNotification(entry, pmUser, sbn, row);
});
}
}
我们先看是如何装载布局的
public void inflate(Context context, ViewGroup parent, NotificationEntry entry,
RowInflationFinishedListener listener) {
if (TRACE_ORIGIN) {
mInflateOrigin = new Throwable("inflate requested here");
}
mListener = listener;
AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
mEntry = entry;
entry.setInflationTask(this);
inflater.inflate(R.layout.status_bar_notification_row, parent, this);
}
最后是把status_bar_notification_row.xml的布局添加上了
我们接着看是如何更新通知的
private void updateNotification(
NotificationEntry entry,
PackageManager pmUser,
StatusBarNotification sbn,
ExpandableNotificationRow row) {
row.setIsLowPriority(entry.ambient);
// Extract target SDK version.
try {
ApplicationInfo info = pmUser.getApplicationInfo(sbn.getPackageName(), 0);
entry.targetSdk = info.targetSdkVersion;
} catch (PackageManager.NameNotFoundException ex) {
Log.e(TAG, "Failed looking up ApplicationInfo for " + sbn.getPackageName(), ex);
}
row.setLegacy(entry.targetSdk >= Build.VERSION_CODES.GINGERBREAD
&& entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
entry.setRow(row);
row.setOnActivatedListener(mPresenter);
boolean useIncreasedCollapsedHeight =
mMessagingUtil.isImportantMessaging(sbn, entry.importance);
boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
&& !mPresenter.isPresenterFullyCollapsed();
row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
row.setEntry(entry);
if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */);
}
if (mNotificationInterruptionStateProvider.shouldPulse(entry)) {
row.updateInflationFlag(FLAG_CONTENT_VIEW_AMBIENT, true /* shouldInflate */);
}
row.setNeedsRedaction(
Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
row.inflateViews();//装入view
..............
}
rom.inflateViews最后是调用到了inflateNotificationViews
com.android.systemui.statusbar.notification.row.NotificationContentInflater.java
private void inflateNotificationViews(@InflationFlag int reInflateFlags) {
............
AsyncInflationTask task = new AsyncInflationTask(
sbn,
mInflateSynchronously,
reInflateFlags,
mCachedContentViews,
mRow,
mIsLowPriority,
mIsChildInGroup,
mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight,
mRedactAmbient,
mCallback,
mRemoteViewClickHandler);
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
task.executeOnExecutor(Executors.newCachedThreadPool()); // UNISOC: Modify for bug1340757
}
}
AsyncInflationTask继承了asynctask,所以我们看它的继承方法
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
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority,
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight,
mRedactAmbient, packageContext);
return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
mRow.getContext(), mRow.getHeadsUpManager(),
mRow.getExistingSmartRepliesAndActions());
} catch (Exception e) {
mError = e;
return null;
}
}
@Override
protected void onPostExecute(InflationProgress result) {
if (mError == null) {
mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags,
mCachedContentViews, mRow, mRedactAmbient, mRemoteViewClickHandler, this);
} else {
handleError(mError);
}
}
首先判断是否是媒体通知,进行对应的主题背景颜色绘制。继续看方法createRemoteViews是如何创建远程view的
private static InflationProgress createRemoteViews(@InflationFlag 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_CONTENT_VIEW_CONTRACTED) != 0) {
result.newContentView = createContentView(builder, isLowPriority, usesIncreasedHeight);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
result.newExpandedView = createExpandedView(builder, isLowPriority);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
result.newHeadsUpView = builder.createHeadsUpContentView(usesIncreasedHeadsUpHeight);
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
result.newPublicView = builder.makePublicContentView();
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_AMBIENT) != 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;
}
createRemoteViews就是根据各种flag创建各式各样的view,如CONTENT_VIEW、EXPANDED_VIEW、HEADSUP_VIEW、PUBLIC_VIEW、AMBIENT_VIEW等
三、总结:总的来说通知的布局添加不难,逻辑都比较清晰,但是涉及到布局装载、布局的嵌套还是有点繁琐,此外还涉及到布局的许多细节。