理解 RemoteViews

RemoteViews 表示的是一个 View 结构,它可以在其他进程中显示,并提供了一组基础的操作用于跨进程更新界面。

RemoteViews 的应用

  1. RemoteViews 在实际中,主要用于开发通知栏和桌面小部件。
  2. 通知栏通过 NotificationManager 的 notify 方法来实现。
  3. 桌面小部件通过 AppWidgetProvider 来实现,本质上是一个广播接收器。
  4. RemoteViews 在通知栏上的应用示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    Notification notification = new Notification();
    notification.icon = R.drawable.ic_launcher;
    notification.tickerText = "hello world";
    notification.when = System.currentTimeMillis();
    notification.flags = Notification.FLAG_AUTO_CANCEL;
    Intent intent = new Intent(this, DemoActivity_1.class);
    intent.putExtra("sid", "" + sId);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

    RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
    remoteViews.setTextViewText(R.id.msg, "chapter_5: " + sId);
    remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1);
    PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(this,
    0, new Intent(this, DemoActivity_2.class), PendingIntent.FLAG_UPDATE_CURRENT);
    remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);

    notification.contentView = remoteViews;
    notification.contentIntent = pendingIntent;
    NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(sId, notification);
  5. RemoteViews 在桌面小部件上的应用示例:

    1. 定义小部件布局界面 widget.xml
    2. 在 res/xml 下新建 widget_info.xml 小部件配置信息

      1
      2
      3
      4
      5
      6
      7
      8
      <?xml version="1.0" encoding="utf-8"?>
      <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
      android:initialLayout="@layout/bus_widget" // 指向布局文件
      android:minHeight="250dp" // 尺寸可以通过公式计算出:70dp × n30dpn 代表所占格数
      android:minWidth="250dp" // 同上
      android:previewImage="@drawable/bus_widget_preview" // 添加小部件时的预览图
      android:updatePeriodMillis="86400000"> // 小部件自动更新周期,单位为毫秒
      </appwidget-provider>
    3. 定义小部件的实现类 MyAppWidgetProvider 继承 AppWidgetProvider

    4. 在 AndroidManifest.xml 中声明小部件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <receiver
      android:name=".MyAppWidgetProvider"
      android:label="xxxx 小部件">
      <meta-data
      android:name="android.appwidget.provider"
      android:resource="@xml/widget_info">
      </meta-data>
      <intent-filter>
      <action android:name="com.xxx.action.REFRESH" /> // 自定义 action,用于点击或触发某些操作
      <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> // 必须要加,否则无法正常显示和使用小部件
      </intent-filter>
      </receiver>
    5. AppWidgetProvider 常见方法:

      • onEnable:当小部件第一次添加到桌面时调用该方法,可添加多次,但只在第一次时调用
      • onUpdate:小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件更新时机由 updatePeriodMillis 来决定
      • onDelete:没删除一次小部件就会调用一次
      • onDisabled:当最后一个该类型的小部件被删除时调用该方法
      • onReceive:这是广播接收器的内置方法,用于分发具体的事件给其他方法
  6. PendingIntent 表示一种处于 pending 状态的意图,典型应用场景就是给 RemoteViews 添加点击事件。
  7. PendingIntent 支持三种待定意图:启动 Activity(getActivity)、启动 Service(getService) 和发送广播(getBroadcast)。
  8. getBroadcast(Context context, int requestCode, Intent intent, int flags) 为例说明下 PendingIntent 的匹配规则:
    • 如果两个 PendingIntent 内部 Intent 相同并且 requestCode 相同,那么它们就是相同的。
    • 如果两个 Intent 的 ComponentName 和 intent-filter 都相同,那么它们就是相同的。
  9. PendingIntent flags 参数含义:
    • FLAG_ONE_SHOT:表示当前描述的 PendingIntent 只能被使用一次,对于通知栏消息来说,同类的消息只能打开一次,后续的都无法打开
    • FLAG_NO_CREATE:表示当前描述的 PendingIntent 不会主动创建,直接返回 null
    • FLAG_CANCEL_CURRENT:表示当前描述的 PendingIntent 如果已存在,那么它们都会被 cancel,系统会创建一个新的 PendingIntent,对于通知栏消息来说,被 cancel 的消息都无法打开
    • FLAG_UPDATE_CURRENT:表示当前描述的 PendingIntent 如果已存在,那么它们都会被更新,即它们的 Intent 中的 Extras 都会被替换成最新的。
    • FLAG_IMMUTABLE:表示当前描述的 PendingIntent 是不可变的。

RemoteViews 的内部机制

  1. 常见构造方法:public RemoteViews(String packageName, int layoutId)
  2. RemoteViews 并不支持所有 View 类型,支持的类型如下:

    • Layout

      FrameLayout、LinearLayout、RelativeLayout、GridLayout

    • View

      Button、ImageView、ImageButton、TextView、ProgressBar、ListView、GridView、StackView、ViewStub、AdapterViewFlipper、ViewFlipper、AnalogClock、Chronometer

  3. RemoteViews 部分 set 方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    - setTextViewText(viewId, text)                     设置文本
    - setTextColor(viewId, color) 设置文本颜色
    - setTextViewTextSize(viewId, units, size) 设置文本大小
    - setImageViewBitmap(viewId, bitmap) 设置图片
    - setImageViewResource(viewId, srcId) 根据图片资源设置图片
    - setViewPadding(viewId, left, top, right, bottom) 设置 Padding 间距
    - setOnClickPendingIntent(viewId, pendingIntent) 设置点击事件
    - setInt(viewId, methodName, value) 反射调用参数为 int 的 methodName 方法
    - setLong(viewId, methodName, value) 反射调用参数为 long 的 methodName 方法
    ...
  4. 具体过程:
    首先 RemoteViews 会通过 Binder 传递到 SystemServer 进程,这是因为 RemoteViews 实现了 Parcelable 接口,因此它可以跨进程传输,系统会根据 RemoteViews 中的包名等信息区得到该应用的资源。然后会通过 LayoutInflater 加载 RemoteViews 中的布局文件,在 SystemServer 进程中加载后的布局文件是一个普通的 View,只不过相对本地进程它是一个 RemoteViews 而已。接着系统会对 View 执行一系列的界面更新任务(action),这些任务就是我们通过 set 方法提交的。set 方法对 View 所做的更新并不是立刻执行的,在 RemoteViews 内部会记录所有的更新操作,具体的执行操作要等到 RemoteViews 被加载以后才能执行,这样 RemoteViews 就可以在 SystemServer 进程中显示了。当需要更新 RemoteViews 时,我们需要调用一系列 set 方法并通过 NotificationManager 和 AppWidgetManager 来提交更新任务,具体的更新操作也是在 SystemServer 进程中完成的。

  5. RemoteViews 只支持发起 PendingIntent。
  6. setOnClickPendingIntent 只能用于给 RemoteViews 中的普通 View 设置点击事件,不能用于集合(ListView 和 StackView)中的 Item 点击事件。
  7. ListView 和 StackView 中的 item 添加点击事件,则必须将 setPendingIntentTemplatesetOnClickFillInIntent 组合使用。

本章节配套源码戳我