《Android开发艺术探索》第5章- 理解 RemoteViews 读书笔记

1. RemoteViews 的应用

1.1 说一说对 RemoteViews 的理解

从名字来看,RemoteViews 是一种远程 View,也就是说包含远程和 View 两层意思在里面。具体来说,RemoteViews 表示的是一个 View 结构,它可以在其他进程中显示,由于它在其他进程中显示,RemoteViews 提供了一组基础的操作用于跨进程更新它的界面。

从类结构来看,RemoteViews 并不是 View 的子类,因为它没有继承 View 类,而是实现了 Parcelable 接口和 Filter 接口。

public class RemoteViews implements Parcelable, Filter

文档上的说法:它描述了一个可以展示在另外一个进程里的视图层级。视图层级由一个布局资源文件填充而来,RemoteViews 提供了一些基础的操作用于修改填充的视图层级的内容。

RemoteViews 在 Android 中的应用场景有两种:通知栏和桌面小部件。

1.2 如何创建 RemoteViews?

只要提供当前应用的包名和布局文件的资源 id 就可以创建一个 RemoteViews 对象了。

public RemoteViews(String packageName, int layoutId) {
    this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId);
}

1.3 如何更新 RemoteViews 的视图层级里的 View?

必须通过 RemoteViews 所提供的一系列方法来更新 View,比如:

操作RemoteViews 提供的方法
设置 TextView 的文本setTextViewText(int viewId, CharSequence text) 参数1:TextView 的 id,参数2:要设置的文本
设置 ImageView 的图片setImageViewResource(int viewId, int srcId) 参数1:ImageView 的 id,参数2:要设置的图片资源的 id
给一个控件设置点击事件setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) 参数1:要设置点击事件的 View 的 id,参数2:一个待定的 Intent

1.4 开发桌面小部件的步骤是什么?

  1. 定义小部件的界面
  2. 定义小部件的配置信息
  3. 定义小部件的实现类
  4. 在 AndroidManifest.xml 中声明小部件

1.5 AppWidgetProvider 的回调方法有哪些?作用分别是什么?

方法作用
onEnabled(Context context)当该窗口小部件第一次添加到桌面时会回调该方法,多次添加小部件时只在第一次回调该方法。
onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds)小部件被添加时或者每次小部件被更新时都会回调一次该方法,小部件的更新时机由配置信息里的 updatePeriodMillis 来指定,每个周期小部件都会自动更新一次。
onDeleted(Context context, int[] appWidgetIds)每删除一次桌面小部件,该方法就会被回调。
onDisabled(Context context)最后一个该类型的桌面小部件被删除时,该方法会被回调。
onReceive(Context context, Intent intent)这是广播的内置方法(AppWidgetProviderBroadcastReceiver 的子类),用于分发具体的事件给其他方法,具体来说,onReceive 方法会根据不同的 action 来分别调用 onEnableonDisableonUpdateonDelete 方法。

1.6 PendingIntent 和 Intent 的区别是什么?

从类结构来看,PendingIntent 并非 Intent 的子类:

public final class PendingIntent implements Parcelable

public class Intent implements Parcelable, Cloneable

从发生时机上看,PendingIntent 是在将来的某个不确定的时刻发生,而 Intent 是立刻发生。

从用途上看,要想给 RemoteViews 设置单击事件,就必须使用 PendingIntentPendingIntent 通过 sendcancel 方法来发送和取消特定的待定 Intent

1.7 PendingIntent 支持哪三种待定意图?

待定意图接口方法解释
启动 Activitypublic static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags)获得一个 PendingIntent,该待定意图发生时,效果相当于 Context.startActivity(Intent)
启动 Servicepublic static PendingIntent getService(Context context, int requestCode, Intent intent, int flags)获得一个 PendingIntent,该待定意图发生时,效果相当于 Context.startService(Intent)
发送广播public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags)获得一个 PendingIntent,该待定意图发生时,效果相当于 Context.sendBroadcast(Intent)

1.8 PendingIntent 的匹配规则是什么?

PendingIntent 的匹配规则是:如果两个 PendingIntent 内部的 Intent 相同并且 requestCode 也相同,那么这两个 PendingIntent 就是相同的。

当一个 Intent 调用 filterEquals(Intent other) 方法与另一个 Intent 比较,返回 true,那么两个 Intent 是相同的,否则,不相同。

public boolean filterEquals(Intent other) {
    if (other == null) {
        return false;
    }
    if (!Objects.equals(this.mAction, other.mAction)) return false;
    if (!Objects.equals(this.mData, other.mData)) return false;
    if (!Objects.equals(this.mType, other.mType)) return false;
    if (!Objects.equals(this.mPackage, other.mPackage)) return false;
    if (!Objects.equals(this.mComponent, other.mComponent)) return false;
    if (!Objects.equals(this.mCategories, other.mCategories)) return false;
    return true;
}

可以看到,只要两个 Intent 的 action,data,type,class 和 categories 是相同的,两个 Intent 就是相同的。注意,Intent 的 extra 数据没有参与比较。

参考:PendingIntent 的文档说明。

1.9 FLAG_ONE_SHOTFLAG_NO_CREATEFLAG_CANCEL_CURRENTFLAG_UPDATE_CURRENT 这 4 个标记位的含义是什么?

  • FLAG_ONE_SHOT

    这个标记位表示当前描述的 PendingIntent 仅仅可以被使用一次。如果设置了带有此标记的 PendingIntent,在调用了它的 send() 方法之后,它就会被自动取消;如果后续还有匹配的 PendingIntent 带有此标记,那么它们的 send() 方法就会调用失败。

  • FLAG_NO_CREATE

    这个标记位表示如果当前描述的 PendingIntent 不存在,那么直接返回 null 而不是创建一个 PendingIntent

  • FLAG_CANCEL_CURRENT

    这个标记位表示如果当前描述的 PendingIntent 已经存在,则在产生新的 PendingIntent 之前,会把当前的 PendingIntent 取消。

  • FLAG_UPDATE_CURRENT

    这个标记位表示如果当前描述的 PendingIntent 已经存在,则会把它保留下来并且把它的 extra 数据替换为新的 Intent 里的 extra 数据。

1.10 对于通知栏消息来说,通知 id,PendingIntent 是否匹配,标记位 flags 参数如何影响通知栏消息?

如果 notify 方法的 id 是相同的,那么不管 PendingIntent 是否匹配,后面的通知都会直接替换前面的通知。

如果 notify 方法的 id 不相同,当 PedingIntent 不匹配时,不管采用何种标记位 flags 参数,这些通知之间都不会相互干扰。

如果 notify 方法的 id 不相同,当 PendingIntent 处于匹配状态时,这时采用的标记位 flags 参数就会起作用了:

  • 如果采用 FLAG_ONE_SHOT 标记位,则后续通知中的 PendingIntent 会和第一条通知保持完全一致,包括其中的 extras,单击任何一条通知后,剩下的通知均无法再打开,当所有的通知都被清除后,会再次重复这个过程。
  • 如果采用 FLAG_CANCEL_CURRENT 标记位,则只有最新的通知可以打开,之前弹出的所有通知均无法打开。
  • 如果采用 FLAG_UPDATE_CURRENT 标记位,则之前弹出的通知中的 PendingIntent 都会被更新,最终它们和最新的一条通知保持完全一致,包括其中的 extras,并且这些通知都是可以打开的。

2. RemoteViews 的内部机制

2.1 RemoteViews 是否支持所有的 View 类型?是否支持自定义 View?

RemoteViews 目前并不能支持所有的 View 类型,它所支持的所有类型如下:

分类支持的类型
LayoutAdapterViewFlipperFrameLayoutGridLayoutGridViewLinearLayoutListViewRelativeLayoutStackViewViewFlipperRadioGroup(Android 31)
ViewAnalogClockButtonChronometerImageButtonImageViewProgressBarTextClockTextViewViewStubCheckBox(Android 31)、RadioButton(Android 31)、Switch(Android 31)

RemoteViews 不支持表里所列类型的子类。

RemoteViews 不支持自定义 View

从代码上说,只有标注有 @RemoteView 注解的 View 类型,才会被 RemoteViews 支持。

2.2 说一下 RemoteViews 的内部机制

  1. 创建 RemoteViews 对象,并通过 RemoteViewssetXXX 方法设置一些基础操作,如设置图片,设置文本等,需要注意的是 set 方法对 View 所做的更新并不是立即执行的,只是在 RemoteViews 内部记录所有的更新操作而已;
  2. 通过 BinderRemoteViews 传递到 SystemServer 进程,这是因为 RemoteViews 实现了 Parcelable 接口,可以进行跨进程传输;
  3. SystemServer 进程拿到 RemoteViews 对象,根据该对象中的包名,布局文件通过 LayoutInflater 去加载对应的布局文件,得到对应的 View
  4. SystemServer 进程通过 RemoteViews 对象对 View 执行一系列界面更新任务,具体来说是调用了 RemoteViews 对象的 performApply 方法,在这个方法里逐个执行之前设置的更新操作。

2.3 RemoteViews 里的 Action 的作用是什么?

ActionRemoteViews 里非常重要,可以说 RemoteViews 的重要作用就是对 Action 进行设置和使用。

  1. Action 可以封装对 View 的操作:RemoteViews 每调用一次 set 方法,都会生成一个封装了对应 View 操作的 Action
  2. Action 可以进行跨进程传输,因为 Action 实现了 Parcelable 接口;
  3. Action 可以执行对 View 具体的更新操作:在远程进程调用 RemoteViewsperformApply 方法,内部就是遍历所有的 Action 对象并调用它们的 apply 方法,即执行具体的 View 更新操作;
  4. Action 的使用避免定义大量的 Binder 接口,而且通过在远程进程中批量执行 RemoteViews 的修改操作从而避免了大量的 IPC 操作,提高了系统的性能。

2.4 RemoteViews 的 apply 方法和 reapply 方法的区别是什么?

  • 作用不同:apply 方法会加载布局并更新界面,而 reapply 只会更新界面;

    public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
        RemoteViews rvToApply = getRemoteViewsToApply(context);
        View result;
        ...
        inflater = inflater.cloneInContext(inflationContext);
        inflater.setFilter(this);
        // 加载布局
        result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
        // 更新界面
        rvToApply.performApply(result, parent, handler);
        return result;
    }
    
    public void reapply(Context context, View v, OnClickHandler handler) {
        RemoteViews rvToApply = getRemoteViewsToApply(context);
        // 更新界面
        rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
    }
    
  • 调用时机不同:在初始化界面时会调用 apply 方法,而在更新界面时会调用 reapply 方法。

    对于通知栏来说,

    1. 如果是初始化界面会走 BaseStatusBarcreateNotificationViews 方法,内部调用 RemoteViewsapply 方法;
    2. 如果是更新界面会走 BaseStatusBarupdateNotificationViews 方法,内部调用 RemoteViewsreapply 方法。

    对于桌面小部件来说,初始化界面和更新界面都是在 AppWidgetHostViewupdateAppWidget 方法里。

2.5 RemoteViews 的 setOnClickPendingIntent、setPendingIntentTemplate、setOnClickFillInIntent 的区别和联系是什么?

setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) 用于给普通的 View 设置单击事件,但是不能给集合(如 ListViewStackView)中的 item 设置单击事件,因为比较耗费性能,所以系统不允许这种方式;

如果要给集合(如 ListViewStackView)中的 item 设置单击事件,必须将 setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate)setOnClickFillInIntent(int viewId, Intent fillInIntent) 组合使用才可以。

2.6 为什么对通知栏里的 RemoteViews 调用 setRemoteAdapter 方法没有效果?

这是因为setRemoteAdapter 方法仅仅用于桌面小部件(Can only be used for App Widgets.)。参考:https://stackoverflow.com/questions/42484365/android-listview-in-notification-content-view

参考

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

willwaywang6

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值