Android 理解RemoteViews

作者: 一个有故事的程序员 | 来源:发表于2018-11-19 15:14 被阅读14次

    导语

    什么是远程view呢?它和远程service一样,RemoteViews可以在其他进程中显示。我们可以跨进程更新它的界面。在Android中,主要有两种场景:通知栏和桌面小部件。

    本章先简单介绍通知栏和桌面小部件应用,接着分析RemoteViews内部机制,最后分析RemoteViews的意义并给出一个实例。

    主要内容

    • RemoteViews的应用
    • RemoteViews的内部机制
    • RemoteViews的意义

    具体内容

    RemoteViews的应用

    通知栏主要是通过NotificationManager的notify方法实现。桌面小部件是通过APPWidgetProvider来实现。APPWidgetProvider本质是一个广播。RemoteViews运行在系统的SystemServer进程。

    RemoteViews在通知栏的应用

    我们用到自定义通知,首先要提供一个布局文件,然后通过RemoteViews来加载,可以自定义通知的样式。更新view时,通过RemoteViews提供的一系列方法。如果给一个控件加点击事件,要使用PendingIntent。

    RemoteViews在桌面小部件的应用

    AppWidgetProvider是实现桌面小部件的类,本质是一个BroadcastReceiver。开发步骤如下:

    1. 定义小部件界面。代码
    2. 定义小部件配置信息。代码
    3. 定义小部件实现类,继承AppWidgetProvider。代码
      上面的例子实现了一个简单地桌面小部件,在小部件上显示一张图片,点击后会旋转一周。
    4. 在AndroidManifest.mxl中声明小部件。
     receiver android:name=".MyAppWidgetProvider" >
         <meta-data
             android:name="android.appwidget.provider"
             android:resource="@xml/appwidget_provider_info" >
         </meta-data>
     
         <intent-filter>
             <action android:name="com.ryg.chapter_5.action.CLICK" />
             <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
         </intent-filter>
     </receiver>
    

    第一个action用于识别小部件的单击,第二个action作为小部件的标识必须存在。

    AppWidgetProvider除了onUpdate方法,还有一系列方法。这些方法会自动被onReceive方法调用。当广播到来以后,AppWidgetProvider会自动根据广播的action通过onReceive方法分发广播。

    • onEnable:该小部件第一次添加到桌面时调用,添加多次只在第一次调用。
    • onUpdate:小部件被添加或者每次小部件更新时调用,更新时机由updatePeriodMillis指定,每个周期小部件都会自动更新一次。
    • onDeleted:每删除一次桌面小部件都会调用一次。
    • onDisabled:最后一个该类型的桌面小部件被删除时调用。
    • onReceive:内置方法,用于分发具体事件给以上方法。
    PendingIntent概述

    PendingIntent表示一种处于待定的状态的intent。典型场景是RemoteViews添加单击事件。通过send和cancel方法来发送和取消待定intent。

    PendingIntent支持三种待定意图:

    • static PendingIntent:getActivity(Context context, int requestCode, Intent intent, int flags)获得一个PendingIntent,效果相当于Context.startActivity(Intent)。
    • static PendingIntent:getService(Context context, int requestCode, Intent intent, int flags)获得一个PendingIntent,效果相当于Context.startService(Intent)。
    • static PendingIntent:getBroadcast(Context context, int requestCode, Intent intent, int flags)获得一个PendingIntent,效果相当于Context.sendBroadcast(Intent)。

    其中requestCode多数情况下设为0即可,requestCode会影响flags的效果。

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

    Intent的匹配规则:
    如果两个intent的ComponentName和intent-filter相同,那么这两个intent相同。Extras不参与匹配过程。

    flags参数的含义:

    • FLAG_ONE_SHOT
      当前的PendingIntent只能被使用一次,然后就会被自动cancel,如果后续还有相同的PendingIntent,它们的send方法会调用失败。对于通知栏来说,同类的通知只能使用一次,后续的通知将无法打开。
    • FLAG_NO_CREATE
      当前的PendingIntent不会主动创建,如果当前PendingIntent之前不存在(匹配的PendingIntent),那么获取PendingIntent失败。这个flag很少使用。
    • FLAG_CANCEL_CURRENT
      当前的PendingIntent如果存在(匹配的PendingIntent),那么它们都会被cancel,然后系统创建一个新的PendingIntent。对于通知栏来说,那些被cancel的消息单击后将无法打开。
    • FLAG_UPDATE_CURRENT
      当前PendingIntent如果已经存在(匹配的PendingIntent),那么它们都会被更新。即intent中的extras会被替换成最新的。

    举例:
    在manager.notify(id,notification)中,如果id是常量,那么多次调用notify只能弹出一个通知,后续的通知会把前面的通知完全替代。而如果每次id都不同,那么会弹出多个通知。
    如果id每次都不同且PendingIntent不匹配,那么flags不会对通知之间造成干扰。
    如果id不同且PendingIntent匹配:

    1. 如果采用了FLAG_ONE_SHOT标记位,那么后续通知中的PendingIntent会和第一条通知完全一致,包括extras,单击任何一条通知后,剩下的通知均无法再打开,当所有的通知被清除后,会再次重复这一过程。
    2. 如果采用FLAG_CANCEL_CURRENT,那么只有最新的通知可以打开。
    3. 如果采用FLAG_UPDATE_CURRENT,那么之前弹出的通知中的PendingIntent会被更新,与最新一条的通知完全一致,包括extras,并且这些通知都可以打开。

    RemoteViews的内部机制

    构造方法
    public RemoteViews(String packageName, int layoutId)
    

    第一个参数是当前应用的包名,第二个参数是待加载的布局文件。
    RemoteViews并不支持所有的view类型,支持类型如下:

    • Layout:FrameLayout、LinearLayout、RelativeLayout、GridLayout。
    • View:AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper,ListView,GridView、StackView、AdapterViewFlipper、ViewStub。
    • RemoteViews不支持以上view的子类。

    访问RemoteViews的view元素,必须通过一系列set方法完成:

    方法名 作用
    setTextViewText(int viewId,CharSequence text) 设置TextView的文本内容 第一个参数是TextView的id 第二个参数是设置的内容。
    setTextViewTextSize(int viewId, int units, float size) 设置TextView的字体大小 第二个参数是字体的单位。
    setTextColor(int viewId, int color) 设置TextView字体颜色。
    setImageViewResource(int viewId, int srcId) 设置ImageView的图片
    setInt(int viewId,String methodName, int value) 反射调用View对象的参数类型为Int的方法 比如上述的setImageViewResource的方法内部就是这个方法实现 因为srcId为int型参数。
    setLong setBoolean 类似于setInt。
    setOnClickPendingIntent(int viewId,PendingIntent pendingIntent) 添加点击事件的方法

    大部分set方法是通过反射来完成的。

    RemoteViews内部机制

    通知栏和小组件分别由NotificationManager(NM)和AppWidgetManager(AWM)管理,而NM和AWM通过Binder分别和SystemService进程中的NotificationManagerService以及AppWidgetService中加载的,而它们运行在系统的SystemService中,这就和我们进程构成了跨进程通讯。

    首先RemoteViews会通过Binder传递到SystemService进程,因为RemoteViews实现了Parcelable接口,因此它可以跨进程传输,系统会根据RemoteViews的包名等信息拿到该应用的资源;然后通过LayoutInflater去加载RemoteViews中的布局文件。接着系统会对View进行一系列界面更新任务,这些任务就是之前我们通过set来提交的。set方法对View的更新并不会立即执行,会记录下来,等到RemoteViews被加载以后才会执行。

    为了提高效率,系统没有直接通过Binder去支持所有的View和View操作。而是提供一个Action概念,Action同样实现Parcelable接口。系统首先将View操作封装到Action对象并将这些对象跨进程传输到SystemService进程,接着SystemService进程执行Action对象的具体操作。远程进程通过RemoteViews的apply方法来进行View的更新操作,RemoteViews的apply方法会去遍历所有的Action对象并调用他们的apply方法。这样避免了定义大量的Binder接口,也避免了大量IPC操作。

    apply和reApply的区别在于:apply会加载布局并更新界面,而reApply则只会更新界面。RemoteViews在初始化界面时会调用apply方法,后续更新界面调用reApply方法。

    关于单击事件,RemoteViews中只支持发起PendingIntent,不支持onClickListener那种模式。setOnClickPendingIntent用于给普通的View设置单击事件,不能给集合(ListView/StackView)中的View设置单击事件(开销大,系统禁止了这种方式)。如果要给ListView/StackView中的item设置单击事件,必须将setPendingIntentTemplate和setOnClickFillInIntent组合使用才可以。

    RemoteViews的意义

    当一个应用需要更新另一个应用的某个界面,我们可以选择用AIDL来实现,但如果更新比较频繁,效率会有问题,同时AIDL接口就可能变得很复杂。如果采用RemoteViews就没有这个问题,但RemoteViews仅支持一些常用的View,如果界面的View都是RemoteViews所支持的,那么就可以考虑采用RemoteViews。

    Demo AB

    更多内容戳这里(整理好的各种文集)

    相关文章

      网友评论

        本文标题:Android 理解RemoteViews

        本文链接:https://www.haomeiwen.com/subject/rsfafqtx.html