什么是RemoteViews
它表示的是一个View结构,可以在其他进程中显示,由于它在其他进程中显示,为了更新它的界面,它提供了一组基础的操作用于跨进程更新它的界面。
RemoteViews的应用
它在实际开发中主要用于通知栏和桌面小部件的开发过程中。通知栏主要是通过NotificationManager的notify方法来实现的,它除了默认效果外,还可以另外定义布局。桌面小部件的开发过程中通过AppWidgetProvider来实现,AppWidgetProvider本质上是一个广播。通知栏和桌面小部件的开发都会用到RemoteViews,它们在更新时无法像在Activity里直接去更新View,因为二者的界面都运行在其他进程中,确切来说是系统的SystemServer进程。RemoteViews提供了一系列set方法来跨进程更新界面,并且这些方法只是View全部方法的子集,另外它支持的View类型也是有限的。
1.RemoteViews在通知栏上的应用
通知渠道
在Android8.0中引入了通知渠道的概念,每一条通知都要属于一个对应的渠道。每个应用程序可以自由地创建当前应用拥有哪些通知渠道,但是这些渠道控制权掌握在用户受伤,用户可以自由选择这些通知的重要程度。
设置图标要用纯alpha层
使用系统默认的样式弹出一个通知(书比较老,有些方法已经废弃,下图使用如今新的方式构建通知)
显示效果
自定义通知
步骤 1.提供一个布局文件 2.通过RemoteViews来加载这个布局文件
显示效果
setAutoCancel方法用于控制通知点击后是否消失,还有setStyle,bigText等用法,更多API可以去查看官方文档。
如何更新RemoteViews
必须要通过RemoteViews所提供的一系列set方法来更新,想要为其添加单击事件,则要使用PendingIntent并通过setOnClickPendingIntent方法来实现。
2.RemoteViews在桌面小部件上的应用
AppWidgetProvider是Android中提供的用于实现桌面小部件的类,其本质是一个广播,即BroadcastReceiver。
步骤
1.定义小部件界面
在res/layout/下新建一个xml文件,内容自定义
2.定义小部件的配置信息
在res/xml下新建appwidget_provider_info.xml,名称随意选择,添加如下内容
initialLayout:小工具所使用的初始化布局 minHeight minWidth定义小工具的最小尺寸 updatePeriodMillis:定义小工具的自动更新周期,毫秒为单位
3.定义小部件的实现类
上面的代码实现了一个简单的桌面小部件,在小部件上面显示一张图片,单击它后这个图片就会旋转一周,当小部件被添加到桌面后,会通过RemoteViews加载布局文件,而当小部件被单及后的旋转效果则是通过不断地更新RemoteViews来实现的,由此可见桌面小部件不管是初始化还是后续的更新界面必须使用RemoteViews来实现。
4.在AndroidManifest.xml声明小部件
因为它本质上是一个广播组件
第一个action用于识别小部件的单击行为,第二个作为小部件的标识必须存在
除了onUpdate方法,还有onEnabled、onDisabled、onDeleted、onReceive。这些方法会自动地被onReceive方法在合适的时间调用。当广播到来的时候AppWidgetProvider会自动根据广播的Action通过onReceive方法来自动分发广播。
调用时机
onEnabled:当该窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用 onUpdate:小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机由opdatePeriodMills来指定,每个周期它都会自动更新一次 onDeleted:每删除一次桌面小部件就调用一次 onDisabled:当最后一个该类型的桌面小部件按被删除时调用该方法 onReceive:广播的内置方法,用于分发事件给其他方法,它会根据不同的Action来分别调用上方的方法
PendingIntent概述
pending表示的是一种待定、等待、即将发生的意思,PendingIntent是在将来某个不确定的时刻发生
三种待定意图
1.启动Activity 2.启动Service 3.发送广播
PendingIntent匹配规则
如果两个PendingIntent内部的Intent也相同并且requestCode也相同,那么这两个PendingIntent就是相同的。
Intent匹配规则
两个Intent的CompnentName和intent-filter都相同,那么这两个Intent就是相同的。Extras不参与Intent的匹配过程,只要Intent之间的CompnentName和intetn-filter相同,即使他们的extras不同,那么这两个Intent也是相同的。
FLAGS参数
FLAG_ONE_SHOT
当前描述的PendingIntent只能被使用一次,然后它就会被自动cancel,如果后续还有相同的PendingIntent,那么它们的send方法就会调用失败。对于通知栏来说,同类的通知只能使用一次,后续通知点击无法打开
FLAG_NO_CREATE
当前描述的PendingIntent不会主动创建,如果当前PengdingIntent之前不存在,那么getActivity、getService、getBroadcast方法会直接返回null,即获取PendingIntent失败。这个标记很少见,无法单独使用
FLAG_CANCEL_CURRENT
当前描述的PendingIntent如果已经存在,那么它们都会被cancel,然后系统会创建一个新的PendingIntent。对于通知栏消息来说,那些被cancel的消息单击后将无法打开
FLAG_UPDATE_CURRENT
当前描述的PendingIntent如果已经存在,那么他们都会被更新,即它们Intent中的Extras都会被替换成新的
manager.notify(1,notification)中,如果第一个参数id是常量,那么多次调用notify都只能弹出一个通知,后续的通知会把前面的通知完全替代掉,而如果每次id都不同,那么多次调用notify会弹出多个通知。
RemoteViews的内部机制
RemoteViews支持的View类型 Layout FrameLayout LinearLayout RelativeLayout GridLayout
View AnalogClock Botton Chronometer ImageButton ImageView ProgressBar TextView ViewFlipper ListView GridView StackView AdapterViewFlipper ViewStub
RemoteViews不会支持它们的子类以及其它View类型
RemoteView的部分set方法
RemoteViews的工作过程
通知栏和桌面小部件分别由NotificationManager和AppWidgetProvider管理,而它们分别通过Binder和SystemServer进程中的NotficationManagerService以及AppWidgetService进行通信。由此可见,通知栏和桌面小部件中的布局文件实际上是在NotificationManagerService和AppWidgetService中被加载的,而它们运行在系统的SystemServer中,这就和我们的进程构成了跨进程通信的场景。
首先RemoteViews会通过Binder传递到SystemServer进程,这是因为RemoteViews实现了Parcelable接口,因此它可以跨进程传输,系统会根据RemoteViews中的包名等信息会得到该应用的资源。然后会通过LayoutInflater去加载RemoteViews中的布局文件。在SystemServer进程中加载后的布局是一个普通的View,只不过相对于我们的进程它是一个RemoteViews而已。接着系统会对View执行一系列界面更新任务,也就是我们之前通过set方法提交的更新操作,更新不是立刻执行的,在RemoteViews内部会记录所有的更新操作,具体的执行时机要等到RemoteViews被加载以后才能执行,这样RemoteViews就可以在SystemServer进程中显示了。当需要更新RemoteViews时,我们需要调用一系列set方法并通过NotificationManager和AppWidgetManager来提交更新任务,具体更新操作也是在SystemServer完成的。
从理论上来说,系统完全可以通过Binder去支持所有的View和View操作,但这样代价太大,因为View方法太多,而且大量IPC操作会影响操作效率。但是系统提供了一个Action的概念。Action代表一个View操作,Action同样实现了Parcelable接口。系统首先将View操作封装到Action对象,当我们通过NotificationManager和AppWidgetManager来提交我们的更新时,这些Action对象就会传输到远程进程并在远程进程中依次执行。远程进程通过RemoteViews的apply方法,具体的View更新操作是由Action对象的apply方法来完成的。上述做法的好处是显而易见的,首先不需要定义大量的Binder接口,其次通过在远程进程中批量执行RemoteViews的修改操作从而避免了大量的IPC操作,提高了程序的性能。
查看set方法源码(取其一)
viewId是被操作的View的id,"setText"是方法名,text是要给textview设置的文本,从setCharSequence的实现可以发现它内部并没有对View进程进行直接操作,而是添加了一个ReflectionAction对象,这应该是一个反射类型的动作。
从上图代码我们可以看到RemoteViews内部有一个mActions成员,它是一个ArrayList,外界每调用一次set方法,RemoteViews就会为其创建一个Acition对象并加入到ArrayList中。这里仅仅是将Action对象保存起来,并未对View进行实际的操作。
RemoteViews的apply方法
从上图可以看出它会调用inflateView方法去加载布局文件,然后通过performApply执行更新操作
performApply方法中它遍历mAction这个列表并执行每个Action对象的apply方法,Action对象的apply方法就是真正操作View的地方。
RemoteViews在通知栏和桌面小部件的工作过程和上面描述一致,当我们调用RemoteViews的set方法时,并不会立刻更新它们的界面,必须要通过NotificationManager的notify方法才能更新它们的界面。实际上在AppWidgetManager的updateAppWidget内部实现中,它们的确是通过RemoteViews的apply以及reapply方法来加载或者更新界面,apply和reapply的区别是apply会加载布局并更新界面,而reapply则只会更新界面。通知栏和小插件在初始化界面时会调用apply发明方法,而在后续的更新界面时则会调用reapply方法。
ReflectionAction中apply源码
ReflectionAction表示一个反射动作,通过他对View的操作会以反射的方式来调用,其中getMethod就是根据方法名来得到反射所需的Method对象。使用ReflctionAction的set方法有setTextViewTect setBoolean setLong setDouble等。除了ReflectionAction还有T而TextViewSizeAction ViewPaddingAction SetOnClickPendingIntent等。
TextViewSizeAction部分实现
之所以不用反射模式因为setTextSize有两个参数,无法复用ReflectionAction,因为ReflectionAction的反射调用只有一个参数。
setOnClickPendingIntent 用于给普通View设置单击事件,但不能给集合中的View(ListView和StackView)设置单击事件,因为开销大,若想为它们提供单击事件,则需要和setPendingIntentTemplate和setOnClickFillInIntent组合使用才可以
RemoteViews用法核心代码
当应用界面中都是一些简单的View,两个应用之间使用RemoteViews会比AIDL效率高。 apply方法若使用资源id去加载布局,在两个应用之间有可能id不一样,但我们可以通过资源名称来加载布局文件,两个应用要约定好RemoteViews中的布局文件的资源名称如"layout_main",然后在根据名称查找到对应的布局文件并加载,然后调用reapply方法把一个应用中的View所做的一系列更新操作全部作用到另一个应用加载的View上面。
|