美文网首页艺术探索
Android读书笔记(5)—— 理解RemoteViews

Android读书笔记(5)—— 理解RemoteViews

作者: AndroidMaster | 来源:发表于2018-01-21 21:07 被阅读124次

    一、RemoteViews基础认知

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

    1、Notification

    1.1、分类

    1)普通的notification

    1.内容标题
    2.大图标
    3.内容
    4.内容附加信息
    5.小图标
    6.时间

    2)大布局Notification

    图1

    大布局notification是在android4.1以后才增加的,大布局notification与小布局notification只在7部分有区别,其它部分都一致。大布局notification只有在所有notification的最上面时才会显示大布局,其它情况下显示小布局。你也可以用手指将其扩展为大布局(前提是它是大布局)。
    大布局notification有三种类型:图1为NotificationCompat.InboxStyle类型。图2左部为NotificationCompat.BigTextStyle。图2右部 为:NotificationCompat.BigPictureStyle

    图2

    3)自定义布局notification

    1.2、基本操作

    Notification的基本操作主要有创建、更新、取消这三种。一个Notification的必要属性有三项,如果不设置则在运行时会抛出异常:

    • 小图标,通过 setSmallIcon() 方法设置
    • 标题,通过 setContentTitle 方法设置
    • 内容,通过 setContentText() 方法设置

    1)创建Notification

    Notification.Builer: 使用建造者模式构建Notification对象。由于Notification.Builder 仅支持 Android 4.1及之后的版本,为了解决兼容性问题, Google 在 Android Support v4 中加入了 NotificationCompat.Builder 类。对于某些在 Android 4.1 之后才特性,即使 NotificationCompat.Builder 支持该方法,在之前的版本中也不能运行。
    Notification : 通知对应类,保存通知相关的数据。NotificationManager 向系统发送通知时会用到。
    NotificationManager : NotificationManager是通知管理类,它是一个系统服务。调用notify(int id, Notification notification)方法可以向系统发送通知(取消通知:cancel(int id),id就是这么用的)。

    //获取PendingIntent
    Intent mainIntent = new Intent(this, MainActivity.class);
    PendingIntent mainPendingIntent = PendingIntent.getActivity(this, 0, mainIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    //创建 Notification.Builder 对象
    NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
        .setSmallIcon(R.mipmap.ic_launcher)
        //点击通知后自动清除
        .setAutoCancel(true)
        .setContentTitle("我是带Action的Notification")
        .setContentText("点我会打开MainActivity")
        .setContentIntent(mainPendingIntent);
    //发送通知
    mNotifyManager.notify(3, builder.build());
    

    2)更新Notification

    更新通知很简单,只需要再次发送相同ID的通知即可,如果之前的通知还未被取消,则会直接更新该通知相关的属性;如果之前的通知已经被取消,则会重新创建一个新通知。更新通知跟发送通知使用相同的方式。

    3)取消Notification

    取消通知有如下 5 种方式:

    • 点击通知栏的清除按钮,会清除所有可清除的通知
    • 设置了 setAutoCancel() 或 FLAG_AUTO_CANCEL 的通知,点击该通知时会清除它
    • 通过 NotificationManager 调用 cancel(int id) 方法清除指定 ID 的通知
    • 通过 NotificationManager 调用 cancel(String tag, int id) 方法清除指定 TAG 和 ID 的通知
    • 通过 NotificationManager 调用 cancelAll() 方法清除所有该应用之前发送的通知

    如果你是通过 NotificationManager.notify(String tag, int id, Notification notify) 方法创建的通知,那么只能通过 NotificationManager.cancel(String tag, int id) 方法才能清除对应的通知,调用NotificationManager.cancel(int id) 无效。

    1.3、设置Notification的通知效果

    Notification 有震动、响铃、呼吸灯三种响铃效果,可以通过 setDefaults(int defualts) 方法来设置。 Default 属性有以下四种,一旦设置了 Default 效果,自定义的效果就会失效

    //设置系统默认提醒效果,一旦设置默认提醒效果,则自定义的提醒效果会全部失效。具体可看源码
    //添加默认震动效果,需要申请震动权限
    //<uses-permission android:name="android.permission.VIBRATE" />
    Notification.DEFAULT_VIBRATE
    //添加系统默认声音效果,设置此值后,调用setSound()设置自定义声音无效
    Notification.DEFAULT_SOUND
    //添加默认呼吸灯效果,使用时须与 Notification.FLAG_SHOW_LIGHTS 结合使用,否则无效
    Notification.DEFAULT_LIGHTS
    //添加上述三种默认提醒效果
    Notification.DEFAULT_ALL
    

    除了以上几种设置 Notification 默认通知效果,还可以通过以下几种 FLAG 设置通知效果。

    //提醒效果常用 Flag
    //三色灯提醒,在使用三色灯提醒时候必须加该标志符
    Notification.FLAG_SHOW_LIGHTS
    //发起正在运行事件(活动中)
    Notification.FLAG_ONGOING_EVENT
    //让声音、振动无限循环,直到用户响应 (取消或者打开)
    Notification.FLAG_INSISTENT
    //发起Notification后,铃声和震动均只执行一次
    Notification.FLAG_ONLY_ALERT_ONCE
    //用户单击通知后自动消失
    Notification.FLAG_AUTO_CANCEL
    //只有调用NotificationManager.cancel()时才会清除
    Notification.FLAG_NO_CLEAR
    //表示正在运行的服务
    Notification.FLAG_FOREGROUND_SERVICE
    
    /**
    * 最普通的通知效果
    */
    private void showNotifyOnlyText() {
       NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setLargeIcon(mLargeIcon)
               .setContentTitle("我是只有文字效果的通知")
               .setContentText("我没有铃声、震动、呼吸灯,但我就是一个通知");
       mManager.notify(1, builder.build());
    }
    
    /**
    * 展示有自定义铃声效果的通知
    * 补充:使用系统自带的铃声效果:Uri.withAppendedPath(Audio.Media.INTERNAL_CONTENT_URI, "6");
    */
    private void showNotifyWithRing() {
       NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("我是伴有铃声效果的通知")
               .setContentText("美妙么?安静听~")
               //调用系统默认响铃,设置此属性后setSound()会无效
               //.setDefaults(Notification.DEFAULT_SOUND)
               //调用系统多媒体裤内的铃声
               //.setSound(Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2"));
               //调用自己提供的铃声,位于 /res/values/raw 目录下
               .setSound(Uri.parse("android.resource://com.littlejie.notification/" + R.raw.sound));
       //另一种设置铃声的方法
       //Notification notify = builder.build();
       //调用系统默认铃声
       //notify.defaults = Notification.DEFAULT_SOUND;
       //调用自己提供的铃声
       //notify.sound = Uri.parse("android.resource://com.littlejie.notification/"+R.raw.sound);
       //调用系统自带的铃声
       //notify.sound = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI,"2");
       //mManager.notify(2,notify);
       mManager.notify(2, builder.build());
    }
    
    /**
    * 展示有震动效果的通知,需要在AndroidManifest.xml中申请震动权限
    * <uses-permission android:name="android.permission.VIBRATE" />
    * 补充:测试震动的时候,手机的模式一定要调成铃声+震动模式,否则你是感受不到震动的
    */
    private void showNotifyWithVibrate() {
       //震动也有两种设置方法,与设置铃声一样,在此不再赘述
       long[] vibrate = new long[]{0, 500, 1000, 1500};
       NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("我是伴有震动效果的通知")
               .setContentText("颤抖吧,凡人~")
               //使用系统默认的震动参数,会与自定义的冲突
               //.setDefaults(Notification.DEFAULT_VIBRATE)
               //自定义震动效果
               .setVibrate(vibrate);
       //另一种设置震动的方法
       //Notification notify = builder.build();
       //调用系统默认震动
       //notify.defaults = Notification.DEFAULT_VIBRATE;
       //调用自己设置的震动
       //notify.vibrate = vibrate;
       //mManager.notify(3,notify);
       mManager.notify(3, builder.build());
    }
    
    /**
    * 显示带有呼吸灯效果的通知,但是不知道为什么,自己这里测试没成功
    */
    private void showNotifyWithLights() {
       final NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("我是带有呼吸灯效果的通知")
               .setContentText("一闪一闪亮晶晶~")
               //ledARGB 表示灯光颜色、 ledOnMS 亮持续时间、ledOffMS 暗的时间
               .setLights(0xFF0000, 3000, 3000);
       Notification notify = builder.build();
       //只有在设置了标志符Flags为Notification.FLAG_SHOW_LIGHTS的时候,才支持呼吸灯提醒。
       notify.flags = Notification.FLAG_SHOW_LIGHTS;
       //设置lights参数的另一种方式
       //notify.ledARGB = 0xFF0000;
       //notify.ledOnMS = 500;
       //notify.ledOffMS = 5000;
       //使用handler延迟发送通知,因为连接usb时,呼吸灯一直会亮着
       Handler handler = new Handler();
       handler.postDelayed(new Runnable() {
           @Override
           public void run() {
               mManager.notify(4, builder.build());
           }
       }, 10000);
    }
    
    /**
    * 显示带有默认铃声、震动、呼吸灯效果的通知
    * 如需实现自定义效果,请参考前面三个例子
    */
    private void showNotifyWithMixed() {
       NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("我是有铃声+震动+呼吸灯效果的通知")
               .setContentText("我是最棒的~")
               //等价于setDefaults(Notification.DEFAULT_SOUND | Notification.DEFAULT_LIGHTS | Notification.DEFAULT_VIBRATE);
               .setDefaults(Notification.DEFAULT_ALL);
       mManager.notify(5, builder.build());
    }
    
    /**
    * 通知无限循环,直到用户取消或者打开通知栏(其实触摸就可以了),效果与FLAG_ONLY_ALERT_ONCE相反
    * 注:这里没有给Notification设置PendingIntent,也就是说该通知无法响应,所以只能手动取消
    */
    private void showInsistentNotify() {
       NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("我是一个死循环,除非你取消或者响应")
               .setContentText("啦啦啦~")
               .setDefaults(Notification.DEFAULT_ALL);
       Notification notify = builder.build();
       notify.flags |= Notification.FLAG_INSISTENT;
       mManager.notify(6, notify);
    }
    
    /**
    * 通知只执行一次,与默认的效果一样
    */
    private void showAlertOnceNotify() {
       NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
               .setSmallIcon(R.mipmap.ic_launcher)
               .setContentTitle("仔细看,我就执行一遍")
               .setContentText("好了,已经一遍了~")
               .setDefaults(Notification.DEFAULT_ALL);
       Notification notify = builder.build();
       notify.flags |= Notification.FLAG_ONLY_ALERT_ONCE;
       mManager.notify(7, notify);
    }
    
    /**
    * 清除所有通知
    */
    private void clearNotify() {
       mManager.cancelAll();
    }
    

    2、PendingIntent

    PendingIntent是一种延迟的,即将发生的Intent,用于在某个特定时刻执行特定的Action。而Intent是立刻发生。
    PendingIntent 是 Android 系统管理并持有,即便创建该PendingIntent对象的进程被杀死了,这个PendingItent对象在其他进程中还是可用的。PendingIntent通过sendcancel方法来发送和取消特定的Intent。

    PendingIntent 主要可以通过以下三种方式获取:

    //获取一个用于启动Activity的PendingIntent 对象
    public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags);
    //获取一个用于启动Service的PendingIntent对象
    public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags);
    //获取一个用于向BroadcastReceiver广播的PendingIntent 对象
    public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags)
    

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

    Intent的匹配规则

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

    PendingIntent的匹配规则

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

    PendingIntent 具有以下几种 flag:

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

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

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

    3、RemoteViews在通知栏上的应用

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

    //获取PendingIntent
    Intent intent = new Intent(this, SecondActivity.class);
    PendingIntent mainPendingIntent = PendingIntent.getActivity(this, 0, intent,
        PendingIntent.FLAG_UPDATE_CURRENT);
    //获取NotificationManager实例
    NotificationManager notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    //创建RemoteViews
    RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
    remoteViews.setTextViewText(R.id.tv_content, "这是新的文本");//更新TextView
    remoteViews.setImageViewResource(R.id.iv_content, R.mipmap.tencent);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, new Intent(this,
        SecondActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
    remoteViews.setOnClickPendingIntent(R.id.btn1, pendingIntent);//设置点击事件
    //实例化NotificationCompat.Builder并设置相关属性
    Notification.Builder builder = new Notification.Builder(this)
        //设置小图标,部分rom可能不生效
        .setSmallIcon(R.mipmap.tencent)
        //点击通知后自动清除
        .setAutoCancel(true)
        //设置通知标题
        .setContentTitle("我是带Action的Notification")
        //设置通知内容
        .setContentText("点我会打开MainActivity")
        .setContentIntent(mainPendingIntent);
        Notification notification = builder.build();
        notification.contentView = remoteViews;
        //设置通知时间,默认为系统发出通知的时间,通常不用设置
        //.setWhen(System.currentTimeMillis());
        notifyManager.notify(1, builder.build());
    

    4、RemoteViews在桌面小部件上的应用

    AppWidgetProvider是实现桌面小部件的类,本质是一个BroadcastReceiver

    • 定义小部件界面
      在res/layout下新建一个xml文件
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/icon1" />
    </LinearLayout>
    
    • 定义小部件配置信息
      在res/xml下新建一个xml文件
    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/widget"
        android:minHeight="84dp"
        android:minWidth="84dp"
        android:updatePeriodMillis="86400000" >//自动更新的周期,单位为ms
    </appwidget-provider>
    
    • 定义小部件实现类,继承AppWidgetProvider
    public class MyAppWidgetProvider extends AppWidgetProvider {
        public static final String TAG = "MyAppWidgetProvider";
        public static final String CLICK_ACTION = "com.ryg.chapter_5.action.CLICK";
    
        public MyAppWidgetProvider() {
            super();
        }
    
        @Override
        public void onReceive(final Context context, Intent intent) {
            super.onReceive(context, intent);
            Log.i(TAG, "onReceive : action = " + intent.getAction());
    
            // 这里判断是自己的action,做自己的事情,比如小工具被点击了要干啥,这里是做一个动画效果
            if (intent.getAction().equals(CLICK_ACTION)) {
                Toast.makeText(context, "clicked it", Toast.LENGTH_SHORT).show();
    
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Bitmap srcbBitmap = BitmapFactory.decodeResource(
                                context.getResources(), R.drawable.icon1);
                        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
                        for (int i = 0; i < 37; i++) {
                            float degree = (i * 10) % 360;
                            RemoteViews remoteViews = new RemoteViews(context
                                    .getPackageName(), R.layout.widget);
                            remoteViews.setImageViewBitmap(R.id.imageView1,
                                    rotateBitmap(context, srcbBitmap, degree));
                            Intent intentClick = new Intent();
                            intentClick.setAction(CLICK_ACTION);
                            PendingIntent pendingIntent = PendingIntent
                                    .getBroadcast(context, 0, intentClick, 0);
                            remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
                            appWidgetManager.updateAppWidget(new ComponentName(
                                    context, MyAppWidgetProvider.class),remoteViews);
                            SystemClock.sleep(30);
                        }
    
                    }
                }).start();
            }
        }
    
        /**
         * 每次窗口小部件被点击更新都调用一次该方法
         */
        @Override
        public void onUpdate(Context context, AppWidgetManager appWidgetManager,
                int[] appWidgetIds) {
            super.onUpdate(context, appWidgetManager, appWidgetIds);
            Log.i(TAG, "onUpdate");
    
            final int counter = appWidgetIds.length;
            Log.i(TAG, "counter = " + counter);
            for (int i = 0; i < counter; i++) {
                int appWidgetId = appWidgetIds[i];
                onWidgetUpdate(context, appWidgetManager, appWidgetId);
            }
    
        }
    
        /**
         * 窗口小部件更新
         * 
         * @param context
         * @param appWidgeManger
         * @param appWidgetId
         */
        private void onWidgetUpdate(Context context,
                AppWidgetManager appWidgeManger, int appWidgetId) {
    
            Log.i(TAG, "appWidgetId = " + appWidgetId);
            RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
                    R.layout.widget);
    
            // "窗口小部件"点击事件发送的Intent广播
            Intent intentClick = new Intent();
            intentClick.setAction(CLICK_ACTION);
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
                    intentClick, 0);
            remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
            appWidgeManger.updateAppWidget(appWidgetId, remoteViews);
        }
    
        private Bitmap rotateBitmap(Context context, Bitmap srcbBitmap, float degree) {
            Matrix matrix = new Matrix();
            matrix.reset();
            matrix.setRotate(degree);
            Bitmap tmpBitmap = Bitmap.createBitmap(srcbBitmap, 0, 0,
                    srcbBitmap.getWidth(), srcbBitmap.getHeight(), matrix, true);
            return tmpBitmap;
        }
    }
    
    • 在AndroidManifest.xml中声明
    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:内置方法,用于分发具体事件给以上方法

    上面的例子实现了一个简单地桌面小部件,在小部件上显示一张图片,点击后会旋转一周。

    二、RemoteViews的内部机制

    1、相关方法

    构造方法:public RemoteViews(String packageName,int layoutId)
    第一个参数是当前应用的包名,第二个参数是待加载的布局文件。

    RemoteViews并不支持所有的view类型

    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)
    • setTextViewTextSize(int viewId,int units,float size)
    • setTextColor(int viewId,int color)
    • setImageViewResource(int viewId,int srcId)
    • setInt(int viewId,String methodName,int value)反射调用View对象的参数类型为Int的方法 比如上述的setImageViewResource的方法内部就是这个方法实现 因为srtId为int型参数
    • setLong setBoolean 类似于setInt
    • setOnClickPendingIntent(int viewId,PendingIntent pendingIntent) 添加点击事件的方法
      大部分set方法是通过反射来完成的。

    2、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。

    参考文献

    Android Notification 详解

    相关文章

      网友评论

        本文标题:Android读书笔记(5)—— 理解RemoteViews

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