探究RemoteViews的作用和原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/willway_wang/article/details/82261706

1 前言

RemoteViewsAndroid 中有两种使用场景:通知栏和桌面小控件。正是通过 RemoteViews,通知栏和桌面小控件才可以在其他进程中显示。
本文基于 Android5.0 源码,先介绍 RemoteViews 在通知栏的使用方式,接着会分析 RemoteViews 的内部原理。分析内部原理是通过几个问题进行的:

  • 通知是如何显示出来的?
  • 通知是如何更新的?
  • 通知是如何取消的?

2 正文

2.1 小例子

先看代码:

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;当点击取消按钮时,会取消掉已显示的通知栏。

2.2 通知是如何显示出来的?

显示通知最终调用的是:

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; i<N; i++) {
                    final 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
 *
 * <p>
 * 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 类中:

扫描二维码关注公众号,回复: 3124086 查看本文章
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);
    }
   ...
}

接着看一下 RemoteViewsapply 方法:

 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);
        }
    }
}

2.3 通知是如何更新的?

依然是 NotificationManagernotify 方法开始,到 BaseStatusBaronNotificationPosted 方法,和添加通知的分析是一样的。所以,直接从 BaseStatusBaronNotificationPosted 方法往下进行。更新通知会调用 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);
}

从这里可以看到 RemoteViewsapply 方法和 reApply 方法的区别:apply 方法会加载布局并更新界面,而 reApply 方法则只会更新界面。

2.4 通知是如何取消的?

NotificationManagercancel 方法开始分析,

 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;
}

3 总结

通过分析通知的添加和更新,学习到 RemoteViews 在其中发挥的重要作用。

参考

Android开发艺术探索

猜你喜欢

转载自blog.csdn.net/willway_wang/article/details/82261706