RemoteViews的作用及原理

一、RemoteViews是什么?

        RemoteViews表示远程View,用于跨进程更新UI,主要用于系统通知栏(Notification)和桌面小部件(App Widget)中。RemoteViews没有继承View, 却实现了parcelable这个接口。

        在通知栏上显示通知是通过NotificationManager的notify()方法实现的,如果通知栏需要自定义布局,就需要使用到RemoteViews。

    /**
     * Post a notification to be shown in the status bar. If a notification with
     * the same id has already been posted by your application and has not yet been canceled, it
     * will be replaced by the updated information.
     *
     * @param id An identifier for this notification unique within your
     *        application.
     * @param notification A {@link Notification} object describing what to show the user. Must not
     *        be null.
     */
    public void notify(int id, Notification notification)
    {
        notify(null, id, notification);
    }

    /**
     * Post a notification to be shown in the status bar. If a notification with
     * the same tag and id has already been posted by your application and has not yet been
     * canceled, it will be replaced by the updated information.
     *
     * All {@link android.service.notification.NotificationListenerService listener services} will
     * be granted {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} access to any {@link Uri uris}
     * provided on this notification or the
     * {@link NotificationChannel} this notification is posted to using
     * {@link Context#grantUriPermission(String, Uri, int)}. Permission will be revoked when the
     * notification is canceled, or you can revoke permissions with
     * {@link Context#revokeUriPermission(Uri, int)}.
     *
     * @param tag A string identifier for this notification.  May be {@code null}.
     * @param id An identifier for this notification.  The pair (tag, id) must be unique
     *        within your application.
     * @param notification A {@link Notification} object describing what to
     *        show the user. Must not be null.
     */
    public void notify(String tag, int id, Notification notification)
    {
        notifyAsUser(tag, id, notification, mContext.getUser());
    }

        桌面小部件是通过AppWidgetProvider实现的,AppWidgetProvider本质上是一个广播,不过小部件的界面需要使用RemoteViews实现。

public class AppWidgetProvider extends BroadcastReceiver {
    /**
     * Constructor to initialize AppWidgetProvider.
     */
    public AppWidgetProvider() {
    }

    /**
     * Implements {@link BroadcastReceiver#onReceive} to dispatch calls to the various
     * other methods on AppWidgetProvider.  
     *
     * @param context The Context in which the receiver is running.
     * @param intent The Intent being received.
     */
    // BEGIN_INCLUDE(onReceive)
    public void onReceive(Context context, Intent intent) {
        // Protect against rogue update broadcasts (not really a security issue,
        // just filter bad broacasts out so subclasses are less likely to crash).
        String action = intent.getAction();
        if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
                if (appWidgetIds != null && appWidgetIds.length > 0) {
                    this.onUpdate(context, AppWidgetManager.getInstance(context), appWidgetIds);
                }
            }
        } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
                final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                this.onDeleted(context, new int[] { appWidgetId });
            }
        } else if (AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)
                    && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS)) {
                int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
                Bundle widgetExtras = extras.getBundle(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS);
                this.onAppWidgetOptionsChanged(context, AppWidgetManager.getInstance(context),
                        appWidgetId, widgetExtras);
            }
        } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) {
            this.onEnabled(context);
        } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) {
            this.onDisabled(context);
        } else if (AppWidgetManager.ACTION_APPWIDGET_RESTORED.equals(action)) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                int[] oldIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_OLD_IDS);
                int[] newIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS);
                if (oldIds != null && oldIds.length > 0) {
                    this.onRestored(context, oldIds, newIds);
                    this.onUpdate(context, AppWidgetManager.getInstance(context), newIds);
                }
            }
        }
    }
    // END_INCLUDE(onReceive)

    //此处省略其他代码
}

        之所以要在通知栏和小部件中使用RemoteViews展示界面,是因为它们的界面运行在其它进程中,即系统的SystemServer进程。

二、如何使用RemoteViews?

        RemoteViews在通知栏和小部件中的使用方法可以参考:官方文档

        以及我的另一篇博客:RemoteViews布局和类型限制源码分析

三、RemoteViews的原理

        RemoteViews的作用是在其它进程中显示并更新UI,不过它只支持一些常用的Layout和View。因为RemoteViews是跨进程的,没有提供findViewById()方法,所以无法直接访问它的View元素。不过RemoteViews提供了一系列set方法去访问其View元素,比如设置资源、添加点击事件等。

  

        RemoteViews主要用于通知栏和小部件中,通知栏和小部件分别由NotificationManager和AppWidgetManager管理,而NotificationManager和AppWidgetManager是通过Binder分别和SystemServer进程中的NotificationManagerService以及AppWidgetService进行通信的。因此,通知栏和小部件中的界面实际上是由NotificationManagerService以及AppWidgetService加载的,它们运行在系统SystemServer进程中,APP进程要更新RemoteViews,就需要使用Binder进行跨进程通信。

        RemoteViews中的IPC过程:

        1. RemoteViews通过Binder传递到SystemServer进程,系统会根据RemoteViews的包名等信息得到相关的资源;

        2. 通过LayoutInflater加载RemoteViews的布局文件,在SystemServer进程中,这个布局文件其实是一个普通的View,不过相对于APP进程,它是一个RemoteViews;

        3. 系统对View执行界面初始化任务,这些操作是通过RemoteViews提供的一系列set方法提交的,不过这些set方法对View的操作不是立即执行的,在RemoteViews内部会记录这些操作,具体执行要等到RemoteViews被加载后执行;

        4. 当APP进程需要更新RemoteViews时,需要调用相关的set方法,通过NotificationManager和AppWidgetManager来提交更新任务给SystemServer进程,具体更新操作需要在SystemServer进程中完成。

        RemoteViews中set方法的实现:

        1. 系统并没有通过Binder去支持View的跨进程访问。RemoteViews提供了一种Action的概念,Action实现了Parcelable接口。

        2. 系统将RemoteViews的一系列操作封装到Action对象中,并将Action跨进程传输到SystemServer进程,最后在远程进程中执行Action对象中的所有操作。每调用一次set方法,RemoteViews中就会添加对应的Action对象,最终会传到远程进程中。

        3. 远程进程通过RemoteViews的apply方法进行View的更新操作(遍历所有Action对象,并调用其apply方法)。

        4. 这样就不需要定义大量的Binder接口,通过在远程进程中的批量操作,避免了大量的IPC操作,提高了性能。

    /**
     * Base class for all actions that can be performed on an
     * inflated view.
     *
     *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
     */
    private abstract static class Action implements Parcelable {
        public abstract void apply(View root, ViewGroup rootParent,
                OnClickHandler handler) throws ActionException;

        public static final int MERGE_REPLACE = 0;
        public static final int MERGE_APPEND = 1;
        public static final int MERGE_IGNORE = 2;

        public int describeContents() {
            return 0;
        }

        public void setBitmapCache(BitmapCache bitmapCache) {
            // Do nothing
        }

        public int mergeBehavior() {
            return MERGE_REPLACE;
        }

        public abstract int getActionTag();

        public String getUniqueKey() {
            return (getActionTag() + "_" + viewId);
        }

        /**
         * This is called on the background thread. It should perform any non-ui computations
         * and return the final action which will run on the UI thread.
         * Override this if some of the tasks can be performed async.
         */
        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
            return this;
        }

        public boolean prefersAsyncApply() {
            return false;
        }

        /**
         * Overridden by subclasses which have (or inherit) an ApplicationInfo instance
         * as member variable
         */
        public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
            return true;
        }

        public void visitUris(@NonNull Consumer<Uri> visitor) {
            // Nothing to visit by default
        }

        int viewId;
    }

        RemoteViews中apply和reapply的区别:

        1. apply: 加载布局,并且更新UI。

        2. reApply:只更新UI。

        3. 通知栏和桌面小部件初始化时,会调用apply方法,后续的更新操作都调用reapply方法。

    /**
     * Inflates the view hierarchy represented by this object and applies
     * all of the actions.
     *
     * <p><strong>Caller beware: this may throw</strong>
     *
     * @param context Default context to use
     * @param parent Parent that the resulting view hierarchy will be attached to. This method
     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
     * @return The inflated view hierarchy
     */
    public View apply(Context context, ViewGroup parent) {
        return apply(context, parent, null);
    }

    /** @hide */
    public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
        RemoteViews rvToApply = getRemoteViewsToApply(context);

        View result = inflateView(context, rvToApply, parent);
        loadTransitionOverride(context, handler);

        rvToApply.performApply(result, parent, handler);

        return result;
    }
    /**
     * Applies all of the actions to the provided view.
     *
     * <p><strong>Caller beware: this may throw</strong>
     *
     * @param v The view to apply the actions to.  This should be the result of
     * the {@link #apply(Context,ViewGroup)} call.
     */
    public void reapply(Context context, View v) {
        reapply(context, v, null);
    }

    /** @hide */
    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 ((Integer) v.getTag(R.id.widget_frame) != 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的应用及原理

        Android深入理解RemoteViews

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值