Android Notification從notify到新增view的處理流程
建立Notification是很容易的,android8.0以後開始加入通知渠道NotificationChannel,然後在構造NotificationCompat.Builder的時候,指定要傳送的渠道,最後呼叫NotificationManager.notify(id,notification)傳送通知。
public void notify(int id, Notification notification)
{
notify(null, id, notification);
}
---------------
public void notify(String tag, int id, Notification notification)
{
notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}
---------------
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
INotificationManager service = getService();
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification, isLowRam);
...
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
-------------------
static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
獲取了INotificationManager 遠端介面物件,把Notification 給加入到佇列中,
INotificationManager的遠端物件是NotificationManagerService裡的mservice,NotificationManagerService是系統服務。在開機啟動的時候,Systemserver裡和其他系統服務一起啟動,最後註冊到ServiceManager裡。
publishBinderService(Context.NOTIFICATION_SERVICE, mService); //把service註冊到Servicemanager裡
----
private final IBinder mService = new INotificationManager.Stub(){
...
}
接著前面的,找到了enqueueNotificationWithTag
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, userId);
}
------------
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) {
....處理notification包含的資訊,通知渠道,優先順序。。
mHandler.post(new EnqueueNotificationRunnable(userId, r));
}
把notification轉換為NotificationRecord,並post給EnqueueNotificationRunnable,
EnqueueNotificationRunnable的run方法裡。
@Override
public void run() {
synchronized (mNotificationLock) {
mEnqueuedNotifications.add(r);
scheduleTimeoutLocked(r);
...
mHandler.post(new PostNotificationRunnable(r.getKey()));
}
------------
public void run() {
synchronized (mNotificationLock) {
try {
NotificationRecord r = null;
int N = mEnqueuedNotifications.size();
NotificationRecord old = mNotificationsByKey.get(key);
final StatusBarNotification n = r.sbn;
final Notification notification = n.getNotification();
...
if (notification.getSmallIcon() != null) {
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
mListeners.notifyPostedLocked(n, oldSbn);
if (oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) {
mHandler.post(new Runnable() {
@Override
public void run() {
mGroupHelper.onNotificationPosted(
n, hasAutoGroupSummaryLocked(n));
}
});
}
}
...
buzzBeepBlinkLocked(r);
}
------------
void buzzBeepBlinkLocked(NotificationRecord record) {
處理notification的 聲音 震動和燈光閃爍
}
------------
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
傳送給狀態列
mHandler.post(new Runnable() {
@Override
public void run() {
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) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
INotificationListener是另一個遠端介面物件
protected class NotificationListenerWrapper extends INotificationListener.Stub
------------
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update) {
SomeArgs args = SomeArgs.obtain();
args.arg1 = sbn;
args.arg2 = mRankingMap;
mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_POSTED,
args).sendToTarget();
}
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);
------------
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationPosted(sbn);
}
public void onNotificationPosted(StatusBarNotification sbn) {
// optional
}
很意外,發現方法是空的,那調這麼多有什麼用? 研究了一下發現,onNotificationPosted的這個方法屬於的是
public abstract class NotificationListenerService extends Service {
1
NotificationListenerService 是個抽象方法,那很自然呼叫的時候會呼叫它的子類,
然後
public class NotificationListenerWithPlugins extends NotificationListenerService
1
但是找了一圈NotificationListenerWithPlugins ,沒有onNotificationPosted,那只有繼續找它的子類了,
後來發現,在Statusbar裡有個匿名內部類實現了NotificationListenerService 的方法。
private final NotificationListenerWithPlugins mNotificationListener =
new NotificationListenerWithPlugins() {
...
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
mHandler.post(new Runnable() {
@Override
public void run() {
...
if (isUpdate) {
updateNotification(sbn, rankingMap);
} else {
addNotification(sbn, rankingMap);
}
...
}
}
...
}
------------
public void addNotification(StatusBarNotification notification, RankingMap ranking)
throws InflationException {
Entry shadeEntry = createNotificationViews(notification);
boolean isHeadsUped = shouldPeek(shadeEntry);.
...
}
------------
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);
// Construct the expanded view.
inflateViews(entry, mStackScroller);
}
------------
protected void inflateViews(Entry entry, ViewGroup parent) {
new RowInflaterTask().inflate(mContext, parent, entry,
row -> {
bindRow(entry, pmUser, sbn, row);
updateNotification(entry, pmUser, sbn, row);
});
}
最後bindRow就是去構造通知欄的通知View,然後updateNotification就是去顯示到狀態列。
private void updateNotification(Entry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row) {
...
row.updateNotification(entry);
}
------------
public void updateNotification(NotificationData.Entry entry) {
mEntry = entry;
mStatusBarNotification = entry.notification;
mNotificationInflater.inflateNotificationViews();
}
------------
public void inflateNotificationViews() {
inflateNotificationViews(FLAG_REINFLATE_ALL);
}
------------
void inflateNotificationViews(int reInflateFlags) {
...
StatusBarNotification sbn = mRow.getEntry().notification;
new AsyncInflationTask(sbn, reInflateFlags, mRow, mIsLowPriority,
mIsChildInGroup, mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
mCallback, mRemoteViewClickHandler).execute();
}
使用了非同步任務AsyncTask去完成佈局
AsyncInflationTask
@Override
protected InflationProgress doInBackground(Void... params) {
return createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mIsChildInGroup,
mUsesIncreasedHeight, mUsesIncreasedHeadsUpHeight, mRedactAmbient,
packageContext);
}
------------
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;
return result;
}
到了這裡,都是建立各種佈局
比如createContentView
public RemoteViews createContentView() {
return createContentView(false /* increasedheight */ );
}
------------
public RemoteViews createContentView(boolean increasedHeight) {
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());
}
這裡會去判斷我們是否有在notification裡新增style, 如果有不同的style,比如音樂播放器那種notification,就是自定義style,如果沒有,那就用預設的layout。
private int getBaseLayoutResource() {
return R.layout.notification_template_material_base;
}
------------
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/status_bar_latest_event_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:tag="base" >
<include layout="@layout/notification_template_header" />
<LinearLayout
android:id="@+id/notification_main_column"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:layout_marginStart="@dimen/notification_content_margin_start"
android:layout_marginEnd="@dimen/notification_content_margin_end"
android:layout_marginTop="@dimen/notification_content_margin_top"
android:layout_marginBottom="@dimen/notification_content_margin_bottom"
android:orientation="vertical" >
<include layout="@layout/notification_template_part_line1" />
<include layout="@layout/notification_template_text" />
<include
android:layout_width="match_parent"
android:layout_height="@dimen/notification_progress_bar_height"
android:layout_marginTop="@dimen/notification_progress_margin_top"
layout="@layout/notification_template_progress" />
</LinearLayout>
<include layout="@layout/notification_template_right_icon" />
</FrameLayout>
原來這就是我們用的notification的佈局內容。
private RemoteViews applyStandardTemplate(int resId) {
return applyStandardTemplate(resId, mParams.reset().fillTextsFrom(this));
}
private RemoteViews applyStandardTemplate(int resId, boolean hasProgress) {
return applyStandardTemplate(resId, mParams.reset().hasProgress(hasProgress)
.fillTextsFrom(this));
}
private RemoteViews applyStandardTemplate(int resId, StandardTemplateParams p) {
updateBackgroundColor(contentView);
bindNotificationHeader(contentView, p.ambient);
bindLargeIcon(contentVi