第五章 理解 RemoteViews

RemoteViews 表示的是一个 View 结构,可以在其他进程中显示,RemoteViews 提供了一组基础的操作用于跨进程更新它的界面。应用:通知栏和桌面小部件。

5.1 RemoteViews 的应用

通知栏主要是通过 NotificationManager 的 notify() 来实现的;
桌面小部件则是通过 AppWidgetProvider 来实现的,AppWidgetProvider 本质上是一个广播。
通知栏和桌面小部件的开发过程中都会用到 RemoveViews,因为他们运行在系统的SystemServer进程中,在更新界面时无法像在 Activity 里面那样去直接更新 View。不过RemoveViews提供了一系列的 set 方法。
1. RemoteViews 在通知栏上的应用

        Intent intent = new Intent(mContext, SecondActivity.class);
        /**
         * 单击通知后会打开SecondActivity,同时会清除自身(通知)
         */
        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
        NotificationManager notificationManager
                = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
        Notification notification = builder
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentText("hello world")
                .setWhen(System.currentTimeMillis())
                .setContentIntent(pendingIntent)
                .build();
        notificationManager.notify(1, notification);

提供一个布局文件,通过 RemoteViews 来加载这个布局文件即可改变通知样式:

        RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
        remoteViews.setTextViewText(R.id.remote_textview, "remote_textview");
        remoteViews.setImageViewBitmap(R.id.remote_imageview, BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
        PendingIntent openActivity2PendingIntent
                = PendingIntent.getActivity(this, 0, new Intent(this, ThirdActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
        remoteViews.setOnClickPendingIntent(R.id.remote_imageview, openActivity2PendingIntent);
        notification.contentView = remoteViews;
        notificationManager.notify(2, notification);

(1). 创建 RemoteViews 对象:提供当前的包名和布局文件的资源 id。
(2). 更新 View:使用 RemoteViews 的一系列 set 方法。
(3). RemoteViews 的单击事件:使用 RemoteViews 的 setOnClickPendingIntent()实现。
(4). PendingIntent:需要通过 PendingIntent.getActivity() 获取。
2. RemoteViews 在桌面小部件上的应用
AppWidgetProvider 是 Android 中用于实现桌面小部件的类,本质是一个广播,即 BroadcastReceiver。是 BroadcastReceiver的子类。
桌面小部件的开发步骤:
(1). 定义小部件界面 在 res/layout/ 下新建一个XML文件,命名随意,这里是 widget.xml。小部件做成什么样子,就在这里布局。
(2). 定义小部件的配置信息 在 res/xml/ 下新建一个xml文件,命名随意,这里是 appwidget_provider_info.xml,并在里面加载 widget.xml。
(3). 定义小部件的实现类 该类继承 AppWidgetProvider。
(4). 在 AndroidManifest.xml 中声明小部件 在 中的 resource 属性中声明。
onReceive():这是广播内置的方法,用于分发具体的事件给其他方法。源码:会根据不同的 action 分别调用 onEnabled()、onDisabled()、onUpdate()、onDeleted()
onUpdate():小部件被添加时或者每次小部件更新时都会调用一次,小部件的更新时机由 updatePeriodMillis (在上面第二步的 xml 文件中) 来指定,每隔周期会自动更新一次。
onDeleted():小部件每删除一次就调用一次。
onDisabled():最后一个该类型的小部件被删除时就调用该方法,注意是最后一个
onEnabled():当该窗口小部件第一次添加到桌面时就调用该方法,可添加多次但只在第一次调用。
3. PendingIntent 概述
PendingIntent 表示一种处于 Pending 的状态的意图,意思是接下来有一个 Intent 将在某个特定的时刻发生。典型使用场景,给远程的 RemoteViews 添加点击事件,PendingIntent 通过 send() 和 cancel() 来发送和取消特定的待定 Intent。
Intent 是立刻发生。
三种待定意图
参数:Context context, int requestCode, Intent intent, int flags
PendingIntent.getActivity()获得一个 PendingIntent,该待定意图发生时,效果相当于context.startActivity(Intent)
PendingIntent.getBroadcast() 获得一个 PendingIntent,该待定意图发生时,效果相当于context.sendBroadcast(Intent)
PendingIntent.getService() 获得一个 PendingIntent,该待定意图发生时,效果相当于context.startService(Intent)
参数 requestCode: PendingIntent 发送方的请求码,多数情况下设为0即可,会影响到 flags 的效果
PandingIntent 的匹配规则
如果两个 PendingIntent 它们内部的 Intent 相同且 requestCode 相同,则两个 PendingIntent 就是相同的。
Intent 的匹配规则:两个 Intent 的ComponentName 和 intent-filter 都相同。
参数 flags
FLAG_ONE_SHOT:当前描述的 PendingIntent 只能被使用一次,然后就会被自动 cancel,如果后续还有相同的 PendingIntent,则 他们的 send 方法就会调用失败。对于通知栏来说,如果采用此标记位,同类通知只能使用一次,后续的通知单击后将无法打开。
FLAG_NO_CREATE: 当前描述的 PendIntent 不会主动创建,如果当前 PendingIntent 之前不存在,那么getActivity()、getBroadcast()、getService() 会直接返回 null,即获取 PendingIntent失败。日常开发很少用,没有太大使用意义。
FLAG_CANCEL_CURRENT: 当前描述的 PendingIntent 如果已经存在,他们都会被 cancel,然后系统会创建一个新的 PendingIntent。
FLAG_UPDATE_CURRENT: 当前描述的 PendingIntent 已经存在,那么他们就会被更新,即它们的 Intent 中的 Extras 会被替换成新的。
注意:notificationManager.notify(int id, Notification notification)
(1): 如果 notify 的 id 是常亮,不管 PendIntent 是否匹配,后面通知会直接替换前面的通知。
(2): 如果 id 每次都不同,当 PendingIntent 不匹配时,此时不管采用什么标记位,这些同志之间都不会相互干扰。
(3): 如果 id 每次都不同,当 PendingIntent 处于匹配状态下,分情况:
FLAG_ONE_SHOT:后续通知中的 PendingIntent 会和第一条通知保持完全一致,包括其中的 Extras,单击任何一条通知后,剩下的通知均无法打开,所有的通知都被清除后,会再次重复这个过程。
FLAG_CANCEL_CURRENT:只有最新的通知可以打开,之前弹出的所有通知均无法打开。
FLAG_UPDATE_CURRENT:之前弹出的通知中的 PendingIntent 都会被更新,最终他们会和最新的一条通知完全一致,包括其中的 Extras,并且这些通知都是可以打开的。

5.2 RemoteViews 内部机制

RemoteViews 的作用是在其他进程中显示并更新 View 界面。
常用的构造函数:

/**
 * packageName 表示应用的包名   layoutId 表示待加载的布局文件
 */
public RemoteViews(String packageName, int layoutId)

支持的类型:
layout:FrameLayout、LinearLayout、Relativelayout、GridLayout
View:Button、ImageButton、ImageView、TextView、ListView、GridView、ViewStub…..
由于 RemoteViews 在远程进程中显示 View,所以没有 findViewById(),而是提供一系列的 set方法(绝大多数通过反射完成)。
1. 通知栏和桌面小部件分别由 Notificationmanager 和 AppWidgetmanager 管理,而 Notificationmanager 和 AppWidgetmanager 通过 Binder 分别和 SystemServer 进程中 NotificationManagerService 和 AppWidgetManagerService 进行通信。通知栏和桌面小部件中的布局文件实际上是在 NotificationManagerService 以及AppWidgetManagerService 中被加载的,这样就构成了跨进程通信。
2. 首先,RemoteViews 通过 Binder 传递到 SystemServer 进程,系统根据 RemoteViews 中的包名等信息获得该应用的资源。
3. 其次, SystemServer 进程通过 layoutInflater 去加载 RemoteViews 的布局文件,接着对 View 做一系列的界面更新任务。
理论上,系统可以通过 Binder 支持所有的 View 和 View 操作,但是大量的 IPC 操作会影响效率。为解决该问题,系统并没有直接通过 Binder 支持 View 的跨进程访问,而是提供了一个 Action概念,一个 Action 代表了一个操作。
**1. **Action 代表一个 View 操作,将 View 操作封装到 Action 对象并将这些对象跨进程传输到远程进程,接着再远程进程中执行 Action 对象的具体操作。应用中每次执行一次 set方法,RemoteViews中就会添加一个对应的 Action 对象。当通过 Notificationmanager 和 AppWidgetmanager 来提交更新时,这些 Action 对象就传输到远程进程并在远程进程中依次执行。
2. 远程进程通过 RemoteViews 的 apply 方法进行 View的更新操作, apply 方法内部会遍历每一个 Action 对象并调用 Action 对象的 Apply 方法,具体的操作是在 Action 对象的 apply 方法来完成的。
好处:首先不需要定义大量的Binder接口,其次通过在远程进程中批量执行 RemoteViews 的修改操作从而避免了大量的 IPC 操作,这就提高了性能。
当调用 RemoteView 的 set 方法时(本地进程),并不会立刻更新界面,必须调用 NotificationManager 的 notify 以及 AppWidgetManager 的 updateAppWidget 通过 apply 和 reApply 才能更新它们的界面(远程进程)。apply 和 reApply 的区别在于:apply 会加载布局并更新界面, reApply 只会更新界面。通知栏和小部件只在初始化界面时调用 apply 方法,在后续的更新界面时则会调用 reApply 方法。
关于单击事件, RemoteViews 只支持发起 PendingIntent,不支持 onClickListener。
**1. **setOnClickPendingIntent 用于给普通 View 设置单击事件,不能给 ListView等集合中的itemView 设置单击事件。因为开销较大,系统禁止。
2. 要给ListView、StackView 中的 item 设置单击事件,必须将 setPendingIntentTemplate() 和 setOnClickFillInIntent() 组合使用才可以。

5.3 RemoteViews 的意义

一个应用更新另一个应用的界面,选择 AIDL 可以实现,但是如果更新比较频繁,就会存在效率问题,AIDL 接口也会变得复杂。此时如果采用 RemoteViews 就没有问题,不过缺点是:它仅仅支持常见的 View,对于自定义 View 是不支持的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值