notification icon加载流程
在SystemUI的加载流程中,我们讲到加载的StatusBarWindowView类对应的视图文件是status_bar.xml,里面有控件id = notification_icon_area
<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"/>
接下来我们看下SystemUI是怎么加载这个AlphaOptimizedFrameLayout
StatusBar.java.makeStatusBarView() ->
statusBarFragment.initNotificationIconArea(mNotificationIconAreaController)->
statusBarFragment即为CollapsedStatusBarFragment.java类 ->
CollapsedStatusBarFragment.java类
public void initNotificationIconArea(NotificationIconAreaController
notificationIconAreaController) {
//状态栏icon左边那一块的加载
ViewGroup notificationIconArea = mStatusBar.findViewById(R.id.notification_icon_area);
//这里getxxx加载过来到视图添加到给ViewGroup进行显示
mNotificationIconAreaInner =
notificationIconAreaController.getNotificationInnerAreaView();
if (mNotificationIconAreaInner.getParent() != null) {
((ViewGroup) mNotificationIconAreaInner.getParent())
.removeView(mNotificationIconAreaInner);
}
notificationIconArea.addView(mNotificationIconAreaInner);
ViewGroup statusBarCenteredIconArea = mStatusBar.findViewById(R.id.centered_icon_area);
mCenteredIconArea = notificationIconAreaController.getCenteredNotificationAreaView();
if (mCenteredIconArea.getParent() != null) {
((ViewGroup) mCenteredIconArea.getParent())
.removeView(mCenteredIconArea);
}
statusBarCenteredIconArea.addView(mCenteredIconArea);
// Default to showing until we know otherwise.
showNotificationIconArea(false);
}
notificationIconAreaController.getNotificationInnerAreaView();
/**
* Returns the view that represents the notification area.
*/
public View getNotificationInnerAreaView() {
return mNotificationIconArea;
}
mNotificationIconArea的在哪里进行了初始化?
在该类的initializeNotificationAreaViews()
/**
* Initializes the views that will represent the notification area.初始化通知信息区域的视图。
*/
protected void initializeNotificationAreaViews(Context context) {
reloadDimens(context);
LayoutInflater layoutInflater = LayoutInflater.from(context);
mNotificationIconArea = inflateIconArea(layoutInflater); //加载状态栏通知信息 区域的视图,注意不是id控件:R.layout.notification_icon_area.xml
mNotificationIcons = mNotificationIconArea.findViewById(R.id.notificationIcons);
mNotificationScrollLayout = mStatusBar.getNotificationScrollLayout();
mCenteredIconArea = layoutInflater.inflate(R.layout.center_icon_area, null);
mCenteredIcon = mCenteredIconArea.findViewById(R.id.centeredIcon);
initAodIcons();//进入这里
}
如上,我们就找到了初始化的地方,主要是通过inflate这个R.layout.notification_icon_area文件,通过addView的方式添加到了AlphaOptimizedFrameLayout,下面就是看下R.layout.notification_icon_area这个文件了
<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" >
<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"/>
</com.android.keyguard.AlphaOptimizedLinearLayout>
NotificationIconContainer标签对应的类是NotificationIconContainer
/**
* A container for notification icons. It handles overflowing icons properly and positions them
* correctly on the screen.
*/
public class NotificationIconContainer extends AlphaOptimizedFrameLayout {}
从备注就能够看出来,这个就是所有notification icons的父控件,所有icons最后都是添加到这里面来的,就是说在java代码中对NotificationIconContainer进行添加视图,就能显示出来了。好了,到这里我们的第一部分初始化流程就讲完了。
Notification发送到显示的整个过程:
不论是自定义的notification、还是开机就自动显示的notification,都是这个入口manager.notify()开始的
Notification 的发送逻辑
一般来说,如果我们自己的 app 想发送一条新的 Notification,大概会执行下列代码:
import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Build;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import androidx.core.app.NotificationCompat;
private NotificationManager mNotificationManager =
(NotificationManager) mContext.getSystemService(
Context.NOTIFICATION_SERVICE);
private Notification notification;
private static final Intent TRAIN_SETTINGS =
new Intent(Settings.ACTION_AIRPLANE_MODE_SETTINGS); //intent到飞行模式的Settings页面
...
NotificationChannel channel = new NotificationChannel("jiaxian","高铁模式",NotificationManager.IMPORTANCE_LOW);
mNotificationManager.createNotificationChannel(channel);
PendingIntent pendingIntent = PendingIntent.getActivity(
mContext,
0,
TRAIN_SETTINGS,
0
);
//使用Builder构造器创建Notification对象,通知设置,这个jiaxian字符串要和上面NotificationChannel的一致
notification = new NotificationCompat.Builder(mContext,"jiaxian")
.setContentTitle("高铁模式")
.setContentText("已开启高铁模式")
.setSmallIcon(R.drawable.stat_sys_train) //通知信息栏的icon和左上角状态栏的小icon图标
.setLargeIcon(BitmapFactory.decodeResource(mContext.getResources(), R.drawable.ic_train_normal)) //通知信息的右边大图
.setColor(Color.parseColor("#ff0000"))
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.build();
发送调用:
mNotificationManager.notify(1,notification);
取消调用:
mNotificationManager.cancel(1);
可以看到,我们通过 NotificationCompat.Builder 新建了一个 Notification 对象,最后通过 NotificationManager#notify() 方法将 Notification 发送出去。
/**
* @hide
*/
@UnsupportedAppUsage
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
INotificationManager service = getService();
String pkg = mContext.getPackageName();
try {
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
fixNotification(notification), user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
我们可以看到,到最后会调用 service.enqueueNotificationWithTag() 方法,这里的是 service 是 INotificationManager 接口。如果熟悉 AIDL 等系统相关运行机制的话,就可以看出这里是代理类调用了代理接口的方法,实际方法实现是在 NotificationManagerService 当中。
(一般Settings和SystemUI需要用到frameworks层的一些服务,但是是无法直接调用的,属于跨进程通信,所以会用到在frameworks层frameworks\base\services\core\java目录下定义一些aidl接口、并且定义一个aidl服务实现类去实现接口书写特定的功能方法,然后将这些功能方法通过aidl定义的接口暴露出来,供给SystemUI、Settings这些客户端使用!)
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, boolean postSilently) {
if (DBG) {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
}
if (pkg == null || notification == null) {
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
final UserHandle user = UserHandle.of(userId);
// Can throw a SecurityException if the calling uid doesn't have permission to post
// as "pkg"
final int notificationUid = resolveNotificationUid(opPkg, pkg, callingUid, userId);
if (notificationUid == INVALID_UID) {
throw new SecurityException("Caller " + opPkg + ":" + callingUid
+ " trying to post for invalid pkg " + pkg + " in user " + incomingUserId);
}
checkRestrictedCategories(notification);
// Fix the notification as best we can.
try {
fixNotification(notification, pkg, tag, id, userId);
} catch (Exception e) {
Slog.e(TAG, "Cannot fix notification", e);
return;
}
mUsageStats.registerEnqueuedByApp(pkg);
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, notificationUid, callingPid, notification,
user, null, System.currentTimeMillis());
// setup local book-keeping
String channelId = notification.getChannelId();
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
String shortcutId = n.getShortcutId();
final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
pkg, notificationUid, channelId, shortcutId,
true /* parent ok */, 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;
Slog.e(TAG, noChannelStr);
boolean appNotificationsOff = mPreferencesHelper.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;
}
final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
r.setIsAppImportanceLocked(mPreferencesHelper.getIsAppImportanceLocked(pkg, callingUid));
r.setPostSilently(postSilently);
r.setFlagBubbleRemoved(false);
r.setPkgAllowedAsConvo(mMsgPkgsAllowedAsConvos.contains(pkg));
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
final boolean fgServiceShown = channel.isFgServiceShown();
if (((channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
|| !fgServiceShown)
&& (r.getImportance() == IMPORTANCE_MIN
|| r.getImportance() == IMPORTANCE_NONE)) {
// Increase the importance of foreground service notifications unless the user had
// an opinion otherwise (and the channel hasn't yet shown a fg service).
if (TextUtils.isEmpty(channelId)
|| NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
r.setSystemImportance(IMPORTANCE_LOW);
} else {
channel.setImportance(IMPORTANCE_LOW);
r.setSystemImportance(IMPORTANCE_LOW);
if (!fgServiceShown) {
channel.unlockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
channel.setFgServiceShown(true);
}
mPreferencesHelper.updateNotificationChannel(
pkg, notificationUid, channel, false);
r.updateNotificationChannel(channel);
}
} else if (!fgServiceShown && !TextUtils.isEmpty(channelId)
&& !NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
channel.setFgServiceShown(true);
r.updateNotificationChannel(channel);
}
}
ShortcutInfo info = mShortcutHelper != null
? mShortcutHelper.getValidShortcutInfo(notification.getShortcutId(), pkg, user)
: null;
if (notification.getShortcutId() != null && info == null) {
Slog.w(TAG, "notification " + r.getKey() + " added an invalid shortcut");
}
r.setShortcutInfo(info);
r.setHasSentValidMsg(mPreferencesHelper.hasSentValidMsg(pkg, notificationUid));
r.userDemotedAppFromConvoSpace(
mPreferencesHelper.hasUserDemotedInvalidMsgApp(pkg, notificationUid));
if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
r.getSbn().getOverrideGroupKey() != null)) {
return;
}
if (info != null) {
// Cache the shortcut synchronously after the associated notification is posted in case
// the app unpublishes this shortcut immediately after posting the notification. If the
// user does not modify the notification settings on this conversation, the shortcut
// will be uncached by People Service when all the associated notifications are removed.
mShortcutHelper.cacheShortcut(info, user);
}
// Whitelist pending intents.
if (notification.allPendingIntents != null) {
final int intentCount = notification.allPendingIntents.size();
if (intentCount > 0) {
final ActivityManagerInternal am = LocalServices
.getService(ActivityManagerInternal.class);
final long duration = LocalServices.getService(
DeviceIdleInternal.class).getNotificationWhitelistDuration();
for (int i = 0; i < intentCount; i++) {
PendingIntent pendingIntent = notification.allPendingIntents.valueAt(i);
if (pendingIntent != null) {
am.setPendingIntentWhitelistDuration(pendingIntent.getTarget(),
WHITELIST_TOKEN, duration);
am.setPendingIntentAllowBgActivityStarts(pendingIntent.getTarget(),
WHITELIST_TOKEN, (FLAG_ACTIVITY_SENDER | FLAG_BROADCAST_SENDER
| FLAG_SERVICE_SENDER));
}
}
}
}
// Need escalated privileges to get package importance
final long token = Binder.clearCallingIdentity();
boolean isAppForeground;
try {
isAppForeground = mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
} finally {
Binder.restoreCallingIdentity(token);
}
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground));
}
enqueueNotificationInternal方法做的事情大概如下:
首先检查通知发起者是系统进程或者是查看发起者发送的是否是同个 app 的通知信息,否则抛出异常;
除了系统的通知和已注册的监听器允许入队列外,其他 app 的通知都会限制通知数上限和通知频率上限;
将 notification 的 PendingIntent 加入到白名单;
将之前的 notification 进一步封装为 StatusBarNotification 和 NotificationRecord,最后封装到一个异步线程 EnqueueNotificationRunnable 中,这里有一个点,就是 mHandler,涉及到切换线程,我们先跟踪一下 mHandler 是在哪个线程被创建。mHandler 是 WorkerHandler 类的一个实例,在 NotificationManagerService#onStart() 方法中被创建,而 NotificationManagerService 是系统 Service,所以 EnqueueNotificationRunnable 的 run 方法会运行在 system_server 的主线程。
NotificationManagerService.EnqueueNotificationRunnable#run()
protected class EnqueueNotificationRunnable implements Runnable {
private final NotificationRecord r;
private final int userId;
private final boolean isAppForeground;
EnqueueNotificationRunnable(int userId, NotificationRecord r, boolean foreground) {
this.userId = userId;
this.r = r;
this.isAppForeground = foreground;
}
@Override
public void run() {
synchronized (mNotificationLock) {
...
// Handle grouped notifications and bail out early if we
// can to avoid extracting signals.
handleGroupedNotificationLocked(r, old, callingUid, callingPid);
// if this is a group child, unsnooze parent summary
if (n.isGroup() && notification.isGroupChild()) {
mSnoozeHelper.repostGroupSummary(pkg, r.getUserId(), n.getGroupKey());
}
// This conditional is a dirty hack to limit the logging done on
// behalf of the download manager without affecting other apps.
if (!pkg.equals("com.android.providers.downloads")
|| Log.isLoggable("DownloadManager", Log.VERBOSE)) {
int enqueueStatus = EVENTLOG_ENQUEUE_STATUS_NEW;
if (old != null) {
enqueueStatus = EVENTLOG_ENQUEUE_STATUS_UPDATE;
}
EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
pkg, id, tag, userId, notification.toString(),
enqueueStatus);
}
postPostNotificationRunnableMaybeDelayedLocked(
r, new PostNotificationRunnable(r.getKey()));
}
}
}
省略的代码主要的工作是提取 notification 相关的属性,同时通知 notification ranking service,有新的 notification 进来,然后对所有 notification 进行重新排序;
最后会调用 postPostNotificationRunnableMaybeDelayedLocked(r, new PostNotificationRunnable(r.getKey()));
@GuardedBy("mNotificationLock")
protected void postPostNotificationRunnableMaybeDelayedLocked(
NotificationRecord r,
PostNotificationRunnable runnable) {
// tell the assistant service about the notification
if (mAssistants.isEnabled()) {
mAssistants.onNotificationEnqueuedLocked(r);
mHandler.postDelayed(runnable, DELAY_FOR_ASSISTANT_TIME);
} else {
mHandler.post(runnable);
}
}
mHandler.post(runnable),其中runnable是PostNotificationRunnable
protected class PostNotificationRunnable implements Runnable {
private final String key;
...
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
mListeners.notifyPostedLocked(r, old);
...
}
}
}
会调用 mListeners.notifyPostedLocked() 方法。这里 mListeners 是 NotificationListeners 类的一个实例。
NotificationListeners是NotificationManagerService的内部类
NotificationManagerService.NotificationListeners#notifyPostedLocked() ->
NotificationManagerService.NotificationListeners#notifyPosted()
/** {@hide} */
public class NotificationManagerService extends SystemService {
...
public class NotificationListeners extends ManagedServices {
static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
...
//异步通知所有监听者(监听者就是代表要对这个notification进行解析操作的一方),侦听有关新通知的信息
@GuardedBy("mNotificationLock")
public void notifyPostedLocked(NotificationRecord r, NotificationRecord old) {
notifyPostedLocked(r, old, true);
}
/**
* @param notifyAllListeners notifies all listeners if true,
else only notifies listeners
* targetting <= O_MR1
*/
@GuardedBy("mNotificationLock")
private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
boolean notifyAllListeners) {
try {
// 延迟初始化通知
StatusBarNotification sbn = r.getSbn();
StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
TrimCache trimCache = new TrimCache(sbn);
for (final ManagedServiceInfo info : getServices()) {
boolean sbnVisible = isVisibleToListener(sbn, info);
boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info)
: false;
// 如果此通知是不可见的,则跳过
if (!oldSbnVisible && !sbnVisible) {
continue;
}
// If the notification is hidden, don't notifyPosted listeners targeting < P.
// Instead, those listeners will receive notifyPosted when the notification is
// unhidden.
if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
continue;
}
// If we shouldn't notify all listeners, this means the hidden state of
// a notification was changed. Don't notifyPosted listeners targeting >= P.
// Instead, those listeners will receive notifyRankingUpdate.
if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
continue;
}
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
// This notification became invisible -> remove the old one.
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;
}
// 在通知侦听之前授予访问权限
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);
}
});
}
} catch (Exception e) {
Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
}
}
//notifyPostedLocked里面又调用notifyPosted(info, sbnToPost, update); 如下:
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
final INotificationListener listener = (INotificationListener) info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
//最后调用这里
listener.onNotificationPosted(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Slog.e(TAG, "unable to notify listener (posted): " + info, ex);
}
}
...
前面先做该通知是否显示的各种判断,如果显示的话最后会执行 listener.onNotificationPosted() 方法。
通过全局搜索得知listener 类型(INotificationListener listener)是 NotificationListenerService.NotificationListenerWrapper 的代理对象(跨进程通信IPC)。
代理对象,意思是INotificationListener是aidl接口,本身没有任何功能。功能写在被代理对象NotificationListenerWrapper中,它继承了INotificationListener 并且重写了功能方法。
现在看NotificationListenerService.NotificationListenerWrapper的onNotificationPosted方法做什么:
public abstract class NotificationListenerService extends Service {
...
/** @hide */
protected class NotificationListenerWrapper extends INotificationListener.Stub {
/** @hide */
@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;
}
if (sbn == null) {
Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification");
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;
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();
}
}
}
}
}
这里在一开始会从 sbnHolder 中获取到 sbn 对象,sbn 隶属于 StatusBarNotificationHolder 类,继承于 IStatusBarNotificationHolder.Stub 对象。因为涉及到跨进程调用,所以会捕获了一个RemoteException。最后将获取到的sbn赋值给args,发送给hanlder处理
看mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
args).sendToTarget();
private final class MyHandler extends Handler {
public static final int MSG_ON_NOTIFICATION_POSTED = 1;
public static final int MSG_ON_NOTIFICATION_REMOVED = 2;
public static final int MSG_ON_LISTENER_CONNECTED = 3;
public static final int MSG_ON_NOTIFICATION_RANKING_UPDATE = 4;
public static final int MSG_ON_LISTENER_HINTS_CHANGED = 5;
public static final int MSG_ON_INTERRUPTION_FILTER_CHANGED = 6;
public static final int MSG_ON_NOTIFICATION_CHANNEL_MODIFIED = 7;
public static final int MSG_ON_NOTIFICATION_CHANNEL_GROUP_MODIFIED = 8;
public static final int MSG_ON_STATUS_BAR_ICON_BEHAVIOR_CHANGED = 9;
public MyHandler(Looper looper) {
super(looper, null, false);
}
@Override
public void handleMessage(Message msg) {
if (!isConnected) {
return;
}
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;
case里面调用了onNotificationPosted(sbn, rankingMap):走的是NotificationListenerService的方法:
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationPosted(sbn);
}
最后是看到一个空方法,因为NotificationListenerService是一个抽象类,
具体的执行肯定是某个类重写了这个方法的:
public void onNotificationPosted(StatusBarNotification sbn) {
// optional
}
那么真正执行的是那个类呢?是所有new或者重写 NotificationListenerService、NotificationListenerWithPlugins的实现类所重写的onNotificationPosted()
推测是NotificationListener:
NotificationListener这个类,因为NotificationListener继承NotificationListenerWithPlugins,NotificationListenerWithPlugins是继承了NotificationListenerService的,然后自身又重写了onNotificationPosted等方法。
/**
* This class handles listening to notification updates and passing them along to
* NotificationPresenter to be displayed to the user.
* 此类处理监听通知更新并将它们传递给 NotificationPresenter 以显示给用户
*
*/
@SuppressLint("OverrideAbstract")
public class NotificationListener extends NotificationListenerWithPlugins {
NotificationListener继承NotificationListenerWithPlugins后,重写了它的各种方法:
/**
* This class handles listening to notification updates and passing them along to
* NotificationPresenter to be displayed to the user. 此类处理监听通知更新并将它们传递给 NotificationPresenter 以显示给用户
* 而NotificationPresenter的实现类是public class StatusBarNotificationPresenter implements NotificationPresenter,
*/
@SuppressLint("OverrideAbstract")
public class NotificationListener extends NotificationListenerWithPlugins {
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
//收到通知消息,执行添加或者修改
if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
mMainHandler.post(() -> {
processForRemoteInput(sbn.getNotification(), mContext);
for (NotificationHandler handler : mNotificationHandlers) {
handler.onNotificationPosted(sbn, rankingMap);
}
});
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap,
int reason) {
//执行移除、取消通知消息
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn + " reason: " + reason);
if (sbn != null && !onPluginNotificationRemoved(sbn, rankingMap)) {
mMainHandler.post(() -> {
for (NotificationHandler handler : mNotificationHandlers) {
handler.onNotificationRemoved(sbn, rankingMap, reason);
}
});
}
}
@Override
public void onNotificationRankingUpdate(final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onRankingUpdate");
//通知的排序优先级改变,就是修改通知显示顺序、位置这些
if (rankingMap != null) {
RankingMap r = onPluginRankingUpdate(rankingMap);
mMainHandler.post(() -> {
for (NotificationHandler handler : mNotificationHandlers) {
handler.onNotificationRankingUpdate(r);
}
});
}
}
}
NotificationListener 的onNotificationPosted、onNotificationRemoved、onNotificationRankingUpdate最终走的是NotificationListener 的内部接口NotificationHandler的这些方法:
...
/** Interface for listening to add/remove events that we receive from NotificationManager. */
public interface NotificationHandler {
//最终都链接到这里,查看实现NotificationHandler的类是那个,是谁在具体执行 -> 是NotificationEntryManager,其内部private final NotificationHandler mNotifListener = new NotificationHandler(){}
void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap);
void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap);
void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, int reason);
void onNotificationRankingUpdate(RankingMap rankingMap);
/**
* Called after the listener has connected to NoMan and posted any current notifications.
*/
void onNotificationsInitialized();
}
NotificationHandler具体实现类是:NotificationEntryManager,所以NotificationListener类鉴听到notification后执行添加修改删除最后走的是下面这些方法:
...
private final NotificationHandler mNotifListener = new NotificationHandler() {
@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
final boolean isUpdateToInflatedNotif = mActiveNotifications.containsKey(sbn.getKey());
if (isUpdateToInflatedNotif) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
}
}
@Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
removeNotification(sbn.getKey(), rankingMap, UNDEFINED_DISMISS_REASON);
}
@Override
public void onNotificationRemoved(
StatusBarNotification sbn,
RankingMap rankingMap,
int reason) {
removeNotification(sbn.getKey(), rankingMap, reason);
}
@Override
public void onNotificationRankingUpdate(RankingMap rankingMap) {
updateNotificationRanking(rankingMap);
}
@Override
public void onNotificationsInitialized() {
}
};
...
而这些addNotification、removeNotification即是添加和删除通知的入口了。具体逻辑需要从addNotification点进去研究。
addNotification:
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
try {
addNotificationInternal(notification, ranking);
} catch (InflationException e) {
handleInflationException(notification, e);
}
}
------------------------》
private void addNotificationInternal(
StatusBarNotification notification,
RankingMap rankingMap) throws InflationException {
//StatusBarNotification是封装通知的类。由 NotificationManagerService 发送给客户端
String key = notification.getKey();
if (DEBUG) {
Log.d(TAG, "addNotification key=" + key);
}
updateRankingAndSort(rankingMap, "addNotificationInternal");
//Ranking是NotificationListenterService的内部类:在当前活动的通知上存储排名相关信息。 <p> 排名对象不会随着通知事件的发生而自动更新。相反,必须通过当前的 {@link RankingMap} 再次检索排名信息。
//推测是排名通知顺序用的
Ranking ranking = new Ranking();
rankingMap.getRanking(key, ranking);
NotificationEntry entry = mPendingNotifications.get(key);
if (entry != null) {
entry.setSbn(notification);
} else {
entry = new NotificationEntry(
notification,
ranking,
mFgsFeatureController.isForegroundServiceDismissalEnabled(),
SystemClock.uptimeMillis());
mAllNotifications.add(entry);
mLeakDetector.trackInstance(entry);
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryInit(entry);
}
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryBind(entry, notification);
}
// Construct the expanded view.构建展开的视图。
if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
mNotificationRowBinderLazy.get()
.inflateViews(
entry,
() -> performRemoveNotification(notification, REASON_CANCEL),
mInflationCallback);
}
mPendingNotifications.put(key, entry);
mLogger.logNotifAdded(entry.getKey());
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onPendingEntryAdded(entry);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onEntryAdded(entry);
}
for (NotifCollectionListener listener : mNotifCollectionListeners) {
listener.onRankingApplied();
}
}
侧重点看mNotificationRowBinderLazy.get().inflateViews()
构建状态信息栏:inflateViews()。执行者是NotificationRowBinder类的实现类 NotificationRowBinderImpl
public interface NotificationRowBinder {
/**
* 添加或更新通知时调用。绑定器必须异步绑定与通知关联的视图。
*
* TODO: The caller is notified when the inflation completes, but this is currently a very
* roundabout business. Add an explicit completion/failure callback to this method.
*/
void inflateViews(
NotificationEntry entry,
Runnable onDismissRunnable,
NotificationRowContentBinder.InflationCallback callback)
throws InflationException;
/**
* Called when the ranking has been updated (but not add or remove has been done). The binder
* should inspect the old and new adjustments and re-inflate the entry's views if necessary
* (e.g. if something important changed).
*/
void onNotificationRankingUpdated(
NotificationEntry entry,
@Nullable Integer oldImportance,
NotificationUiAdjustment oldAdjustment,
NotificationUiAdjustment newAdjustment,
NotificationRowContentBinder.InflationCallback callback);
}
加载然后生成ExpandableNotificationRow等的信息。
@Singleton
public class NotificationRowBinderImpl implements NotificationRowBinder {
/**
* Inflates the views for the given entry (possibly asynchronously).
*/
@Override
public void inflateViews(
NotificationEntry entry,
Runnable onDismissRunnable,
NotificationRowContentBinder.InflationCallback callback)
throws InflationException {
ViewGroup parent = mListContainer.getViewParentForNotification(entry);
if (entry.rowExists()) {
mIconManager.updateIcons(entry);
ExpandableNotificationRow row = entry.getRow();
row.reset();
updateRow(entry, row);
inflateContentViews(entry, row, callback);
entry.getRowController().setOnDismissRunnable(onDismissRunnable);
} else {
mIconManager.createIcons(entry);
mRowInflaterTaskProvider.get().inflate(mContext, parent, entry,
row -> {
// Setup the controller for the view.
ExpandableNotificationRowComponent component =
mExpandableNotificationRowComponentBuilder
.expandableNotificationRow(row)
.notificationEntry(entry)
.onDismissRunnable(onDismissRunnable)
.rowContentBindStage(mRowContentBindStage)
.onExpandClickListener(mPresenter)
.build();
ExpandableNotificationRowController rowController =
component.getExpandableNotificationRowController();
rowController.init();
entry.setRowController(rowController);
bindRow(entry, row);
updateRow(entry, row);
inflateContentViews(entry, row, callback);
});
}
}
无论走那个分支,最后进入到inflateContentViews(entry, row, callback);是一个回调
/**
* Inflate the row's basic content views.加载该行的基本内容视图
*/
private void inflateContentViews(
NotificationEntry entry,
ExpandableNotificationRow row,
@Nullable NotificationRowContentBinder.InflationCallback inflationCallback) {
final boolean useIncreasedCollapsedHeight =
mMessagingUtil.isImportantMessaging(entry.getSbn(), entry.getImportance());
// If this is our first time inflating, we don't actually know the groupings for real
// yet, so we might actually inflate a low priority content view incorrectly here and have
// to correct it later in the pipeline. On subsequent inflations (i.e. updates), this
// should inflate the correct view.
final boolean isLowPriority = mLowPriorityInflationHelper.shouldUseLowPriorityView(entry);
RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
params.setUseLowPriority(isLowPriority);
// TODO: Replace this API with RowContentBindParams directly. Also move to a separate
// redaction controller.
row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
params.rebindAllContentViews();
mRowContentBindStage.requestRebind(entry, en -> {
row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
row.setIsLowPriority(isLowPriority);
if (inflationCallback != null) {
inflationCallback.onAsyncInflationFinished(en);
}
});
}
看NotificationEntryManager:inflationCallback.onAsyncInflationFinished(en);
InflationCallback是NotifInflater的内部接口类,谁去实现呢?是NotificationEntryManager
/**
* InflationCallback是NotifInflater的内部接口类
* */
private final InflationCallback mInflationCallback = new InflationCallback() {
@Override
public void handleInflationException(NotificationEntry entry, Exception e) {
NotificationEntryManager.this.handleInflationException(entry.getSbn(), e);
}
//添加一个notification会走到这里这个回调,包括一开机就显示在信息栏的那些notification
@Override
public void onAsyncInflationFinished(NotificationEntry entry) {
mPendingNotifications.remove(entry.getKey());
// If there was an async task started after the removal, we don't want to add it back to
// the list, otherwise we might get leaks.
if (!entry.isRowRemoved()) {
boolean isNew = getActiveNotificationUnfiltered(entry.getKey()) == null;
mLogger.logNotifInflated(entry.getKey(), isNew);
if (isNew) {
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryInflated(entry);
}
//添加一个notification会走到这里、包括一开机就显示出来的那些notification
addActiveNotification(entry);
updateNotifications("onAsyncInflationFinished");
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onNotificationAdded(entry);
}
} else {
for (NotificationEntryListener listener : mNotificationEntryListeners) {
listener.onEntryReinflated(entry);
}
}
}
}
};
看到addActiveNotification(entry); 和 updateNotifications(“onAsyncInflationFinished”);
/**
* Equivalent to the old NotificationData#add
* @param entry - an entry which is prepared for display
*/
private void addActiveNotification(NotificationEntry entry) {
Assert.isMainThread();
mActiveNotifications.put(entry.getKey(), entry);
mGroupManager.onEntryAdded(entry);
updateRankingAndSort(mRankingManager.getRankingMap(), "addEntryInternalInternal");
}
/**
* Update the notifications
* @param reason why the notifications are updating
*/
public void updateNotifications(String reason) {
reapplyFilterAndSort(reason);
if (mPresenter != null && !mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
mPresenter.updateNotificationViews(reason);
}
}
updateNotifications -》mPresenter.updateNotificationViews(reason);
mPresenter的实现类是StatusBarNotificationPresenter.java
@Override
public void updateNotificationViews(final String reason) {
// The function updateRowStates depends on both of these being non-null, so check them here.
// We may be called before they are set from DeviceProvisionedController's callback.
if (mScrimController == null) return;
// Do not modify the notifications during collapse.
if (isCollapsing()) {
mShadeController.addPostCollapseAction(() -> updateNotificationViews(reason));
return;
}
mViewHierarchyManager.updateNotificationViews();
mNotificationPanel.updateNotificationViews(reason);
}
mNotificationPanel.updateNotificationViews(reason);
//A layout which handles a dynamic amount of notifications and presents them in a scrollable stack.
public class NotificationStackScrollLayout extends ViewGroup implements ScrollAdapter,
NotificationListContainer, ConfigurationListener, Dumpable,
DynamicPrivacyController.Listener {
...
private NotificationStackScrollLayout mNotificationStackScroller;
...
void updateNotificationViews(String reason) {
//更新NotificationStackScrollLayout这个视图类的各种信息
mNotificationStackScroller.updateSectionBoundaries(reason);
mNotificationStackScroller.updateSpeedBumpIndex();
mNotificationStackScroller.updateFooter();
updateShowEmptyShadeView();
mNotificationStackScroller.updateIconAreaViews();
}
进入mNotificationStackScroller查看(NotificationStackScrollLayout是"状态信息栏"那块的视图类)对应的标签是
<com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
android:id="@+id/notification_stack_scroller"
android:layout_marginTop="@dimen/notification_panel_margin_top"
android:layout_width="@dimen/notification_panel_width"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
android:layout_marginBottom="@dimen/close_handle_underlap" />
进入 NotificationStackScroller的updateIconAreaViews();方法,mIconAreaController即是NotificationIconAreaController,左边区域的icon控制器
public void updateIconAreaViews() {
//添加notification icons
mIconAreaController.updateNotificationIcons();
}
这个NotificationIconAreaController控制器做的就是对NotificationIconContainer进行操作,添加icon到这个视图类里面
/**
* Updates the notifications with the given list of notifications to display.使用给定的通知列表更新通知以显示
*/
public void updateNotificationIcons() {
//更新货架图标
updateStatusBarIcons();
//更新货架图标
updateShelfIcons();
//更新中间图标
updateCenterIcon();
//更新 Aod 通知图标
updateAodNotificationIcons();
//应用通知图标色调
applyNotificationIconsTint();
}
接下来我们就看下updateNotificationIcons里面的逻辑了。主要的添加动作就在updateIconsForLayout这个函数中了
public void updateAodNotificationIcons() {
updateIconsForLayout(entry -> entry.getIcons().getAodIcon(), mAodIcons,
false /* showAmbient */,
true /* showLowPriority */,
true /* hideDismissed */,
true /* hideRepliedMessages */,
true /* hideCurrentMedia */,
true /* hide centered icon */,
mBypassController.getBypassEnabled() /* hidePulsing */,
false /* onlyShowCenteredIcon */);
}
updateIconsForLayout:
/**
* Updates the notification icons for a host layout. This will ensure that the notification
* host layout will have the same icons like the ones in here.
* @param function A function to look up an icon view based on an entry
* @param hostLayout which layout should be updated
* @param showAmbient should ambient notification icons be shown
* @param showLowPriority should icons from silent notifications be shown
* @param hideDismissed should dismissed icons be hidden
* @param hideRepliedMessages should messages that have been replied to be hidden
* @param hidePulsing should pulsing notifications be hidden
*/
private void updateIconsForLayout(Function<NotificationEntry, StatusBarIconView> function,
NotificationIconContainer hostLayout, boolean showAmbient, boolean showLowPriority,
boolean hideDismissed, boolean hideRepliedMessages, boolean hideCurrentMedia,
boolean hideCenteredIcon, boolean hidePulsing, boolean onlyShowCenteredIcon) {
ArrayList<StatusBarIconView> toShow = new ArrayList<>(
mNotificationScrollLayout.getChildCount());
// Filter out ambient notifications and notification children.
for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
View view = mNotificationScrollLayout.getChildAt(i);
if (view instanceof ExpandableNotificationRow) {
NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
if (shouldShowNotificationIcon(ent, showAmbient, showLowPriority, hideDismissed,
hideRepliedMessages, hideCurrentMedia, hideCenteredIcon, hidePulsing,
onlyShowCenteredIcon)) {
StatusBarIconView iconView = function.apply(ent);
if (iconView != null) {
toShow.add(iconView);
}
}
}
}
// In case we are changing the suppression of a group, the replacement shouldn't flicker
// and it should just be replaced instead. We therefore look for notifications that were
// just replaced by the child or vice-versa to suppress this.
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);
}
首先从mNotificationScrollLayout取出NotificationEntry,代码是: NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
然后把**NotificationEntry(安卓7.0是叫NotificationData)**存放的StatusBarIconView取出添加到toShow里面,代码是:toShow.add(iconView);
然后是便利这个toShow集合,调用hostLayout.addView(v, i, params);中,hostLayout即是NotificationIconContainer
View view = mNotificationScrollLayout.getChildAt(i);
NotificationEntry ent = ((ExpandableNotificationRow) view).getEntry();
StatusBarIconView iconView = function.apply(ent);
toShow.add(iconView);
for (int i = 0; i < toShow.size(); i++) {
StatusBarIconView v = toShow.get(i);
//执行到最后是 NotificationIconContainer.addView添加视图
//NotificationIconContainer本身没有addView、removeView方法,最终走的是其多层下去的父类ViewGroup的方法
hostLayout.addView(v, i, params);
}
到这里,添加notification icon实现加载并显示的流程已经讲完
总结:
首先,我们在 app 进程创建 Notification 实例,通过跨进程调用(来自frameworks\base\core\java\android\app的调用),传递到 system_server 进程的 NotificationManagerService(在frameworks/base/services/core/java/com/android/server/notification包下) 中进行处理,经过两次异步调用,最后传递给在 NotificationListenerService中已经注册的 NotificationListenerWrapper。而 android 系统在初始化 systemui 进程的时候,会往 NotificationListenerService中注册监听器(这里指的就是 NotificationListener,其监听notification,并作出响应)。这种实现方法就是基于我们熟悉的一种设计模式:监听者模式。
系统开机自动发送的notification从哪里来的?
推测来自厂商自定义的开机向导
可参考:Android10定制Google开机向导
https://blog.csdn.net/zc37093/article/details/117223214