RemoteViews
在 Android
中有两种使用场景:通知栏和桌面小控件。正是通过 RemoteViews
,通知栏和桌面小控件才可以在其他进程中显示。
本文基于 Android5.0
源码,先介绍 RemoteViews
在通知栏的使用方式,接着会分析 RemoteViews
的内部原理。分析内部原理是通过几个问题进行的:
先看代码:
public class BlogActivity extends Activity implements View.OnClickListener {
private NotificationManager mNotificationManager;
private static final int NOTIFICATION_ID = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blog_activity);
Button btnShowNotification = (Button) findViewById(R.id.btn_show_notification);
Button btnUpdateNotification = (Button) findViewById(R.id.btn_update_notification);
Button btnCancelNotification = (Button) findViewById(R.id.btn_cancel_notification);
btnShowNotification.setOnClickListener(this);
btnUpdateNotification.setOnClickListener(this);
btnCancelNotification.setOnClickListener(this);
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_show_notification) {
showNotification("This is a title");
} else if (id == R.id.btn_update_notification) {
showNotification("This is a updated title");
} else if (id == R.id.btn_cancel_notification) {
mNotificationManager.cancel(NOTIFICATION_ID);
}
}
private void showNotification(String title) {
Notification notification = new Notification();
notification.icon = R.drawable.icon1;
notification.tickerText = "notification is coming";
notification.flags = Notification.FLAG_AUTO_CANCEL;
notification.when = System.currentTimeMillis();
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_custom_notification);
remoteViews.setTextViewText(R.id.tv_title, title);
remoteViews.setImageViewResource(R.id.image_view, R.drawable.icon1);
notification.contentView = remoteViews;
PendingIntent pendingIntent = PendingIntent.getActivity(BlogActivity.this, 0,
new Intent(BlogActivity.this, DemoAActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
notification.contentIntent = pendingIntent;
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
}
通过三个按钮分别进行通知的显示,更新和取消。在 showNotification(String title)
方法中,通过 RemoteViews
对象来设置通知栏中的 title
;当点击更新时,会更新通知栏中的 title
;当点击取消按钮时,会取消掉已显示的通知栏。
显示通知最终调用的是:
mNotificationManager.notify(NOTIFICATION_ID, notification);
我们从这里开始看,NotificationManager
类是一个通知的管理类,真正执行操作的却不在这个类中。会经由这个类,发需要执行的操作交给其他类去执行。
进入 NotificationManager
类的 notify
方法,这个方法的作用是发送一个 Notification
显示在状态栏中:
public void notify(int id, Notification notification)
{
notify(null, id, notification);
}
public void notify(String tag, int id, Notification notification)
{
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
Notification stripped = notification.clone();
Builder.stripForDelivery(stripped);
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
stripped, idOut, UserHandle.myUserId());
if (id != idOut[0]) {
Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
} catch (RemoteException e) {
}
}
在这个方法中,会调用 getService()
方法:
static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
// 获取一个 BinderProxy 对象,这个对象是由服务端返回的。
IBinder b = ServiceManager.getService("notification");
// 这个方法的作用是把服务端的 Binder 对象转化为客户端所需要的 INotificationManager 接口类型对象。
// 这里会返回一个 android.app.INotificationManager.Proxy 对象,原因是客户端发起了跨进程请求。
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
回到 notify 方法中,INotificationManager service
会调用 enqueueNotificationWithTag
方法。那么真正执行这个方法的地方在哪里呢?是在 NotificationManagerService
类中,可以看到
private final IBinder mService = new INotificationManager.Stub() {
这是一个匿名内部类,真正的操作就是在它内部的方法实现中,即:
@Override
public void enqueueNotificationWithTag(String pkg, String opPkg, String tag, int id,
Notification notification, int[] idOut, int userId) throws RemoteException {
enqueueNotificationInternal(pkg, opPkg, Binder.getCallingUid(),
Binder.getCallingPid(), tag, id, notification, idOut, userId);
}
接着调用了 NotificationManagerService
类的 enqueueNotificationInternal
方法:
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int[] idOut, int incomingUserId) {
if (DBG) {
Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id
+ " notification=" + notification);
}
checkCallerIsSystemOrSameApp(pkg);
// 是否是系统通知
final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg));
// 是否是监听的包,当通过 bindServiceAsUser 方法绑定时,这个方法才会返回 true。而 bindServiceAsUser
// 是有 @SystemApi 注解的方法。
final boolean isNotificationFromListener = mListeners.isListenerPackage(pkg);
final int userId = ActivityManager.handleIncomingUser(callingPid,
callingUid, incomingUserId, true, false, "enqueueNotification", pkg);
final UserHandle user = new UserHandle(userId);
// Limit the number of notifications that any given package except the android
// package or a registered listener can enqueue. Prevents DOS attacks and deals with leaks.
if (!isSystemNotification && !isNotificationFromListener) {
// 普通应用会限制通知的数量,不能超过 50 个。
synchronized (mNotificationList) {
int count = 0;
final int N = mNotificationList.size();
for (int i=0; ifinal NotificationRecord r = mNotificationList.get(i);
if (r.sbn.getPackageName().equals(pkg) && r.sbn.getUserId() == userId) {
count++;
if (count >= MAX_PACKAGE_NOTIFICATIONS) {
Slog.e(TAG, "Package has already posted " + count
+ " notifications. Not showing more. package=" + pkg);
return;
}
}
}
}
}
// 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)) {
EventLogTags.writeNotificationEnqueue(callingUid, callingPid,
pkg, id, tag, userId, notification.toString());
}
// 校验包名和 Notification 对象
if (pkg == null || notification == null) {
throw new IllegalArgumentException("null not allowed: pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
if (notification.icon != 0) {
if (!notification.isValid()) {
throw new IllegalArgumentException("Invalid notification (): pkg=" + pkg
+ " id=" + id + " notification=" + notification);
}
}
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (mNotificationList) {
// === Scoring === 进行评分,目的是为了后面对通知的显示进行排序
// 0. Sanitize inputs 整理一下 priority 属性的值
notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN,
Notification.PRIORITY_MAX);
// Migrate notification flags to scores
if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) {
if (notification.priority < Notification.PRIORITY_MAX) {
notification.priority = Notification.PRIORITY_MAX;
}
} else if (SCORE_ONGOING_HIGHER &&
0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) {
if (notification.priority < Notification.PRIORITY_HIGH) {
notification.priority = Notification.PRIORITY_HIGH;
}
}
// 1. initial score: buckets of 10, around the app [-20..20]
final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER;
// 2. extract ranking signals from the notification data
// 创建一个 StatusBarNotification 对象,用于封装 Notification。
final StatusBarNotification n = new StatusBarNotification(
pkg, opPkg, id, tag, callingUid, callingPid, score, notification,
user);
NotificationRecord r = new NotificationRecord(n, score);
NotificationRecord old = mNotificationsByKey.get(n.getKey());
if (old != null) {
// Retain ranking information from previous record
r.copyRankingInformation(old);
}
mRankingHelper.extractSignals(r); // 提取对排序有用的信息
// 3. Apply local rules
// blocked apps 用户设置不显示通知
if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) {
if (!isSystemNotification) {
r.score = JUNK_SCORE;
Slog.e(TAG, "Suppressing notification from package " + pkg
+ " by user request.");
}
}
// 分值低于阈值下限
if (r.score < SCORE_DISPLAY_THRESHOLD) {
// Notification will be blocked because the score is too low.
return;
}
// Clear out group children of the old notification if the update causes the
// group summary to go away. This happens when the old notification was a
// summary and the new one isn't, or when the old notification was a summary
// and its group key changed.
if (old != null && old.getNotification().isGroupSummary() &&
(!notification.isGroupSummary() ||
!old.getGroupKey().equals(r.getGroupKey()))) {
cancelGroupChildrenLocked(old, callingUid, callingPid, null);
}
// 找出 key 对应的 index 值,如果找不到,就返回 -1。
int index = indexOfNotificationLocked(n.getKey());
if (index < 0) {
// 新增通知
mNotificationList.add(r);
mUsageStats.registerPostedByApp(r);
} else {
// 更新通知
old = mNotificationList.get(index);
mNotificationList.set(index, r);
mUsageStats.registerUpdatedByApp(r, old);
// Make sure we don't lose the foreground service state.
notification.flags |=
old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE;
r.isUpdate = true;
}
mNotificationsByKey.put(n.getKey(), r);
// Ensure if this is a foreground service that the proper additional
// flags are set.
if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
notification.flags |= Notification.FLAG_ONGOING_EVENT
| Notification.FLAG_NO_CLEAR;
}
applyZenModeLocked(r);
mRankingHelper.sort(mNotificationList); // 对通知进行排序
if (notification.icon != 0) {
// Notification 对象的 icon 属性不等于 0 时,就发送通知添加
// 从这里看以看出, 要显示一个通知, icon 属性一定要有值
StatusBarNotification oldSbn = (old != null) ? old.sbn : null;
mListeners.notifyPostedLocked(n, oldSbn);
} else {
Slog.e(TAG, "Not posting notification with icon==0: " + notification);
if (old != null && !old.isCanceled) {
mListeners.notifyRemovedLocked(n);
}
// ATTENTION: in a future release we will bail out here
// so that we do not play sounds, show lights, etc. for invalid
// notifications
Slog.e(TAG, "WARNING: In a future release this will crash the app: "
+ n.getPackageName());
}
// 通知的音效, 震动效果
buzzBeepBlinkLocked(r);
}
}
});
idOut[0] = id;
}
接着进入 notifyPostedLocked
方法:
/**
* asynchronously notify all listeners about a new notification
*
*
* Also takes care of removing a notification that has been visible to a listener before,
* but isn't anymore.
*/
public void notifyPostedLocked(StatusBarNotification sbn, StatusBarNotification oldSbn) {
for (final ManagedServiceInfo info : mServices) {
...
mHandler.post(new Runnable() {
@Override
public void run() {
// 调用到这里
notifyPosted(info, sbnToPost, update);
}
});
}
}
仍然是在 NotificationManagerService
类中:
private void notifyPosted(final ManagedServiceInfo info,
final StatusBarNotification sbn, NotificationRankingUpdate rankingUpdate) {
// 把 IInterface info.service 对象强制转化为 INotificationListener 对象。
final INotificationListener listener = (INotificationListener)info.service;
// 构造一个 StatusBarNotificationHolder 对象,与 sbn 相比,新的对象具有跨进程通信的能力。
// 原因是这个类实现了 IInterface 接口。
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
// 发送通知
listener.onNotificationPosted(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (posted): " + listener, ex);
}
}
在这个方法中,关键是要找到真正执行 onNotificationPosted(sbnHolder, rankingUpdate)
操作的地方。看到 INotificationListener
,根据 google 的命名规范,自然想到真正执行操作的地方应该是在一个叫做 ‘NotificationListenerService
’ 的类中,这只是猜测。下面去找没有这么一个类呢?双击 Shift,开始查找,还真有这个类。
接着看一下这个类的结构,可以看到它有一个内部类 INotificationListenerWrapper
:
private class INotificationListenerWrapper extends INotificationListener.Stub {
这个类继承了 INotificationListener.Stub
抽象类,是真正执行 INotificationListener
规定的接口方法的地方。
下面在 INotificationListenerWrapper
中看到 onNotificationPosted
方法的真正实现:
@Override
public void onNotificationPosted(IStatusBarNotificationHolder sbnHolder,
NotificationRankingUpdate update) {
StatusBarNotification sbn;
try {
// 取出 StatusBarNotification 对象
sbn = sbnHolder.get();
} catch (RemoteException e) {
Log.w(TAG, "onNotificationPosted: Error receiving StatusBarNotification", e);
return;
}
Notification.Builder.rebuild(getContext(), sbn.getNotification());
// protect subclass from concurrent modifications of (@link mNotificationKeys}.
synchronized (mWrapper) {
// 这个方法用于构造一个 RankingMap 对象,也就是 mRankingMap。
applyUpdate(update);
try {
// 发送通知
NotificationListenerService.this.onNotificationPosted(sbn, mRankingMap);
} catch (Throwable t) {
Log.w(TAG, "Error running onNotificationPosted", t);
}
}
}
接着去定位 onNotificationPosted
方法,自然是在 NotificationListenerService
类中,但定位到的结果是空方法:
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
onNotificationPosted(sbn);
}
public void onNotificationPosted(StatusBarNotification sbn) {
// optional
}
这是因为 NotificationListenerService
是一个抽象类,这里并没有完全实现这个方法,只是默认进行了空实现。换句话说,应该找到真正实现的地方。这里为了不影响文章的分析流程,先直接给出结果:
在 BaseStatusBar
类中,有一个成员变量 private final NotificationListenerService mNotificationListener
指向了 NotificationListenerService
的匿名内部类,这里就是真正实现了 onNotificationPosted
方法的地方:
@Override
public void onNotificationPosted(final StatusBarNotification sbn,
final RankingMap rankingMap) {
mHandler.post(new Runnable() {
@Override
public void run() {
Notification n = sbn.getNotification();
// mNotificationData 就是当前显示通知的列表对象,如果从这里可以映射到对应的值,
// 那么,isUpdate 的值就是 true。
boolean isUpdate = mNotificationData.get(sbn.getKey()) != null
|| isHeadsUp(sbn.getKey());
// Ignore children of notifications that have a summary, since we're not
// going to show them anyway. This is true also when the summary is canceled,
// because children are automatically canceled by NoMan in that case.
if (n.isGroupChild() &&
mNotificationData.isGroupWithSummary(sbn.getGroupKey())) {
if (DEBUG) {
Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
}
// Remove existing notification to avoid stale data.
if (isUpdate) {
removeNotification(sbn.getKey(), rankingMap);
} else {
mNotificationData.updateRanking(rankingMap);
}
return;
}
if (isUpdate) {
// 更新通知
updateNotification(sbn, rankingMap);
} else {
// 添加通知
addNotification(sbn, rankingMap);
}
}
});
}
进入 addNotification
方法:
public abstract void addNotification(StatusBarNotification notification,
RankingMap ranking);
这是一个抽象方法,它的具体实现是在 PhoneStatusBar
类中:
public class PhoneStatusBar extends BaseStatusBar implements DemoMode,
DragDownHelper.DragDownCallback, ActivityStarter {
@Override
public void addNotification(StatusBarNotification notification, RankingMap ranking) {
if (DEBUG) Log.d(TAG, "addNotification key=" + notification.getKey());
if (mUseHeadsUp && shouldInterrupt(notification)) {
if (DEBUG) Log.d(TAG, "launching notification in heads up mode");
Entry interruptionCandidate = new Entry(notification, null);
ViewGroup holder = mHeadsUpNotificationView.getHolder();
if (inflateViewsForHeadsUp(interruptionCandidate, holder)) {
// 1. Populate mHeadsUpNotificationView
mHeadsUpNotificationView.showNotification(interruptionCandidate);
// do not show the notification in the shade, yet.
return;
}
}
// 创建通知的 View,封装在一个 Entry 对象里面。这个方法是在父类 BaseStatusBar 里面的。
Entry shadeEntry = createNotificationViews(notification);
if (shadeEntry == null) {
return;
}
if (notification.getNotification().fullScreenIntent != null) {
// Stop screensaver if the notification has a full-screen intent.
// (like an incoming phone call)
awakenDreams();
// not immersive & a full-screen alert should be shown
if (DEBUG) Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
try {
notification.getNotification().fullScreenIntent.send();
} catch (PendingIntent.CanceledException e) {
}
} else {
// usual case: status bar visible & not immersive
// show the ticker if there isn't already a heads up
if (mHeadsUpNotificationView.getEntry() == null) {
tick(notification, true);
}
}
// 把创建好的 Entry shadeEntry 对象添加到通知栏中。这个方法也是在父类 BaseStatusBar 里面的。
addNotificationViews(shadeEntry, ranking);
// Recalculate the position of the sliding windows and the titles.
setAreThereNotifications();
updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
}
先看一下 createNotificationViews
方法:
protected NotificationData.Entry createNotificationViews(StatusBarNotification sbn) {
if (DEBUG) {
Log.d(TAG, "createNotificationViews(notification=" + sbn);
}
// Construct the icon. 创建 icon
Notification n = sbn.getNotification(); // 获取到 Notification 对象
final StatusBarIconView iconView = new StatusBarIconView(mContext,
sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
final StatusBarIcon ic = new StatusBarIcon(sbn.getPackageName(),
sbn.getUser(),
n.icon,
n.iconLevel,
n.number,
n.tickerText);
if (!iconView.set(ic)) {
handleNotificationError(sbn, "Couldn't create icon: " + ic);
return null;
}
// Construct the expanded view.
NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
// inflateViews 方法是填充布局的
if (!inflateViews(entry, mStackScroller)) {
handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
return null;
}
return entry;
}
下面看一下 inflateViews
方法:
private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent) {
return inflateViews(entry, parent, false);
}
private boolean inflateViews(NotificationData.Entry entry, ViewGroup parent, boolean isHeadsUp) {
PackageManager pmUser = getPackageManagerForUser(
entry.notification.getUser().getIdentifier());
int maxHeight = mRowMaxHeight;
final StatusBarNotification sbn = entry.notification;
// 获取到我们构造的 remoteView
RemoteViews contentView = sbn.getNotification().contentView;
RemoteViews bigContentView = sbn.getNotification().bigContentView;
...
// set up the adaptive layout
View contentViewLocal = null;
View bigContentViewLocal = null;
try {
// contentView 调用 apply 方法后,转化成为一个 View 对象
contentViewLocal = contentView.apply(mContext, expanded,
mOnClickHandler);
if (bigContentView != null) {
bigContentViewLocal = bigContentView.apply(mContext, expanded,
mOnClickHandler);
}
}
catch (RuntimeException e) {
final String ident = sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId());
Log.e(TAG, "couldn't inflate view for notification " + ident, e);
return false;
}
// 把 View 对象设置给通知栏
if (contentViewLocal != null) {
contentViewLocal.setIsRootNamespace(true);
expanded.setContractedChild(contentViewLocal);
}
...
}
接着看一下 RemoteViews
的 apply
方法:
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result;
...
LayoutInflater inflater = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
// Clone inflater so we load resources from correct context and
// we don't add a filter to the static version returned by getSystemService.
inflater = inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
// 加载 RemoteViews 中的布局文件
result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
rvToApply.performApply(result, parent, handler);
return result;
}
private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
if (mActions != null) {
handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
final int count = mActions.size();
// 遍历所有的 Action 对象并调用它们的 apply 方法。
for (int i = 0; i < count; i++) {
Action a = mActions.get(i);
a.apply(v, parent, handler);
}
}
}
依然是 NotificationManager
的 notify
方法开始,到 BaseStatusBar
的 onNotificationPosted
方法,和添加通知的分析是一样的。所以,直接从 BaseStatusBar
的 onNotificationPosted
方法往下进行。更新通知会调用 updateNotification
方法:
public void updateNotification(StatusBarNotification notification, RankingMap ranking) {
...
if (contentsUnchanged && bigContentsUnchanged && headsUpContentsUnchanged
&& publicUnchanged) {
if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
oldEntry.notification = notification;
...
if (wasHeadsUp) {
if (shouldInterrupt) {
updateHeadsUpViews(oldEntry, notification);
if (alertAgain) {
resetHeadsUpDecayTimer();
}
} else {
// we updated the notification above, so release to build a new shade entry
mHeadsUpNotificationView.releaseAndClose();
return;
}
} else {
if (shouldInterrupt && alertAgain) {
removeNotificationViews(key, ranking);
addNotification(notification, ranking); //this will pop the headsup
} else {
// 更新通知的 View
updateNotificationViews(oldEntry, notification);
}
}
}
}
...
}
看一下 updateNotificationViews
方法:
private void updateNotificationViews(NotificationData.Entry entry,
StatusBarNotification notification) {
updateNotificationViews(entry, notification, false);
}
private void updateNotificationViews(NotificationData.Entry entry,
StatusBarNotification notification, boolean isHeadsUp) {
final RemoteViews contentView = notification.getNotification().contentView;
final RemoteViews bigContentView = isHeadsUp
? notification.getNotification().headsUpContentView
: notification.getNotification().bigContentView;
final Notification publicVersion = notification.getNotification().publicVersion;
final RemoteViews publicContentView = publicVersion != null ? publicVersion.contentView
: null;
// Reapply the RemoteViews 调用 remoteViews 的 reapply 方法更新界面
contentView.reapply(mContext, entry.expanded, mOnClickHandler);
if (bigContentView != null && entry.getBigContentView() != null) {
bigContentView.reapply(mContext, entry.getBigContentView(),
mOnClickHandler);
}
if (publicContentView != null && entry.getPublicContentView() != null) {
publicContentView.reapply(mContext, entry.getPublicContentView(), mOnClickHandler);
}
...
}
看一下 reApply
方法:
public void reapply(Context context, View v, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
// In the case that a view has this RemoteViews applied in one orientation, is persisted
// across orientation change, and has the RemoteViews re-applied in the new orientation,
// we throw an exception, since the layouts may be completely unrelated.
if (hasLandscapeAndPortraitLayouts()) {
if (v.getId() != rvToApply.getLayoutId()) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
}
}
rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
}
从这里可以看到 RemoteViews
的 apply
方法和 reApply
方法的区别:apply
方法会加载布局并更新界面,而 reApply
方法则只会更新界面。
从 NotificationManager
的 cancel
方法开始分析,
public void cancel(int id)
{
cancel(null, id);
}
public void cancel(String tag, int id)
{
INotificationManager service = getService();
String pkg = mContext.getPackageName();
if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")");
try {
// 这里同样是一次 Binder 调用
service.cancelNotificationWithTag(pkg, tag, id, UserHandle.myUserId());
} catch (RemoteException e) {
}
}
根据通知时怎么添加部分的分析,我们知道,需要到 NotificationManagerService
中去找 cancelNotificationWithTag
方法的真正操作。
@Override
public void cancelNotificationWithTag(String pkg, String tag, int id, int userId) {
checkCallerIsSystemOrSameApp(pkg);
userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, true, false, "cancelNotificationWithTag", pkg);
// Don't allow client applications to cancel foreground service notis.
cancelNotification(Binder.getCallingUid(), Binder.getCallingPid(), pkg, tag, id, 0,
Binder.getCallingUid() == Process.SYSTEM_UID
? 0 : Notification.FLAG_FOREGROUND_SERVICE, false, userId, REASON_NOMAN_CANCEL,
null);
}
void cancelNotification(final int callingUid, final int callingPid,
final String pkg, final String tag, final int id,
final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete,
final int userId, final int reason, final ManagedServiceInfo listener) {
// In enqueueNotificationInternal notifications are added by scheduling the
// work on the worker handler. Hence, we also schedule the cancel on this
// handler to avoid a scenario where an add notification call followed by a
// remove notification call ends up in not removing the notification.
mHandler.post(new Runnable() {
@Override
public void run() {
String listenerName = listener == null ? null : listener.component.toShortString();
EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, id, tag, userId,
mustHaveFlags, mustNotHaveFlags, reason, listenerName);
synchronized (mNotificationList) {
int index = indexOfNotificationLocked(pkg, tag, id, userId);
if (index >= 0) {
// 取出通知记录对象
NotificationRecord r = mNotificationList.get(index);
// Ideally we'd do this in the caller of this method. However, that would
// require the caller to also find the notification.
if (reason == REASON_DELEGATE_CLICK) {
mUsageStats.registerClickedByUser(r);
}
if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) {
return;
}
if ((r.getNotification().flags & mustNotHaveFlags) != 0) {
return;
}
// 从通知记录集合中把这个通知删掉
mNotificationList.remove(index);
// 删除通知
cancelNotificationLocked(r, sendDelete, reason);
cancelGroupChildrenLocked(r, callingUid, callingPid, listenerName);
updateLightsLocked();
}
}
}
});
}
看一下 cancelNotificationLocked
方法:
private void cancelNotificationLocked(NotificationRecord r, boolean sendDelete, int reason) {
...
// status bar
if (r.getNotification().icon != 0) {
r.isCanceled = true;
// 通知删除一个通知
mListeners.notifyRemovedLocked(r.sbn);
}
...
}
看一下 notifyRemovedLocked
方法:
/**
* asynchronously notify all listeners about a removed notification
*/
public void notifyRemovedLocked(StatusBarNotification sbn) {
// make a copy in case changes are made to the underlying Notification object
// NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
// notification
final StatusBarNotification sbnLight = sbn.cloneLight();
for (final ManagedServiceInfo info : mServices) {
if (!isVisibleToListener(sbn, info)) {
continue;
}
final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
mHandler.post(new Runnable() {
@Override
public void run() {
// 通知删除
notifyRemoved(info, sbnLight, update);
}
});
}
}
private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
NotificationRankingUpdate rankingUpdate) {
if (!info.enabledAndUserMatches(sbn.getUserId())) {
return;
}
final INotificationListener listener = (INotificationListener) info.service;
StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
try {
listener.onNotificationRemoved(sbnHolder, rankingUpdate);
} catch (RemoteException ex) {
Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
}
}
有了添加通知部分的分析,直接去 BaseStatusBar
中找 onNotificationRemoved
方法:
@Override
public void onNotificationRemoved(final StatusBarNotification sbn,
final RankingMap rankingMap) {
if (DEBUG) Log.d(TAG, "onNotificationRemoved: " + sbn);
mHandler.post(new Runnable() {
@Override
public void run() {
removeNotification(sbn.getKey(), rankingMap);
}
});
}
接着看 removeNotification
方法:
@Override
public void removeNotification(String key, RankingMap ranking) {
if (ENABLE_HEADS_UP && mHeadsUpNotificationView.getEntry() != null
&& key.equals(mHeadsUpNotificationView.getEntry().notification.getKey())) {
mHeadsUpNotificationView.clear();
}
// 移除通知的 View
StatusBarNotification old = removeNotificationViews(key, ranking);
if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
...
}
在 BaseStatusBar
类中的 removeNotificationViews
方法:
protected StatusBarNotification removeNotificationViews(String key, RankingMap ranking) {
// 从当前通知集合中移除通知的 Entry 对象
NotificationData.Entry entry = mNotificationData.remove(key, ranking);
if (entry == null) {
Log.w(TAG, "removeNotification for unknown key: " + key);
return null;
}
updateNotifications();
return entry.notification;
}
通过分析通知的添加和更新,学习到 RemoteViews
在其中发挥的重要作用。
Android开发艺术探索