美文网首页
Android开发五《理解RemoteViews》

Android开发五《理解RemoteViews》

作者: 独自闯天涯的码农 | 来源:发表于2022-03-23 16:17 被阅读0次

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


    1.png

    支持的布局:

    • AdapterViewFlipper
    • FrameLayout
    • GridLayout
    • GridView
    • LinearLayout
    • ListView
    • RelativeLayout
    • StackView
    • ViewFlipper
      支持的控件:
    • AnalogClock
    • Button
    • Chronometer
    • ImageButton
    • ImageView
    • ProgressBar
    • TextClock
    • TextView

    一、RemoteViews的应用

    使用场景:
    1、通知栏:通过NotiicationManager的notify方法实现

     Intent intent = new Intent(this, NotiActivity.class);
     PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    
     String id = "my_channel_01";
     CharSequence name = "channel";
     String description = "description";
     int importance = NotificationManager.IMPORTANCE_DEFAULT;
     NotificationChannel mChannel = new NotificationChannel(id, name, importance);
    
     mChannel.setDescription(description);
     mChannel.enableLights(true);
     mChannel.setLightColor(Color.RED);
     mChannel.enableVibration(true);
     mChannel.setVibrationPattern(new long[]{100, 200, 300, 400, 500, 400, 300, 200, 400});
    
     NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
     manager.createNotificationChannel(mChannel);
    
    
     RemoteViews remoteView = new RemoteViews(getPackageName(), R.layout.layout_notification);
     remoteView.setTextColor(R.id.re_text, Color.RED);
     remoteView.setTextViewText(R.id.re_text, "remote view demo");
     remoteView.setImageViewResource(R.id.re_image, R.drawable.btn_me_share);
     remoteView.setOnClickPendingIntent(R.id.notification, pendingIntent);
    
     Notification notification = new Notification.Builder(this, id)
     .setAutoCancel(false)
     .setContentTitle("title")
     .setContentText("describe")
     .setContentIntent(pendingIntent)
     .setSmallIcon(R.drawable.btn_me_share)
     .setOngoing(true)
     .setCustomContentView(remoteView)
     .setWhen(System.currentTimeMillis())
     .build();
     manager.notify(1, notification);
    

    从上面代码发现,RemoteViews的方法使用起来很简单。利用构造函数new RemoteViews(packagename, layoutId) 来关联一个view的布局,并通过一些set 方法更新布局,最后利用notification.Builder().setCustomContentView(RemoteViews) 来设置通知栏的view

    2、桌面小部件:通过AppWidgetProvider(本质是一个广播)来实现;
    桌面小部件主要是利用RemoteViews和AppWidgetProvider结合使用,而AppWidgetProvider又是extends BroadcastReceiver, 所以再使用的时候,多了一些关于广播的知识。

    public class NewAppWidget extends AppWidgetProvider {
      private static final String CLICK_ACTION = "com.taohuahua.action.click";
    
      static void updateAppWidget(final Context context, final AppWidgetManager appWidgetManager, final int appWidgetId) {
        final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_widget);
    
        Intent anIntent = new Intent();
        anIntent.setAction(CLICK_ACTION);
        PendingIntent anPendingIntent = PendingIntent.getBroadcast(context, 0, anIntent, 0);
        views.setOnClickPendingIntent(R.id.appwidget_text, anPendingIntent);
        appWidgetManager.updateAppWidget(appWidgetId, views);
      }
    
      @Override
     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int appWidgetId : appWidgetIds) {
          updateAppWidget(context, appWidgetManager, appWidgetId);
        }
    }
    
      @Override
      public void onEnabled(Context context) {
      // Enter relevant functionality for when the first widget is created
      }
    
      @Override
      public void onDisabled(Context context) {
      // Enter relevant functionality for when the last widget is disabled
      }
    
      @Override
      public void onReceive(Context context, Intent intent) {
        super.onReceive(context, intent);
        final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.layout_widget);
    
        if (Objects.equals(intent.getAction(), CLICK_ACTION)) {
        Toast.makeText(context, "hello world", Toast.LENGTH_SHORT).show();
    
        //获得appwidget管理实例,用于管理appwidget以便进行更新操作
        AppWidgetManager manger = AppWidgetManager.getInstance(context);
        // 相当于获得所有本程序创建的appwidget
        ComponentName thisName = new ComponentName(context, NewAppWidget.class);
        //更新widget
        manger.updateAppWidget(thisName, views);
      }
    }
    

    从代码中可以看出,里面有几个重要的方法:

    1. onUpdate: 小部件被添加时或者每次更新时调用。更新时间由第二步配置中updatePeriodMills来决定,单位为毫秒。
    2. onReceive: 广播内置方法,用于分发接收到的事件。
    3. onEnable: 当该窗口小部件第一次添加时调用。
    4. onDelete:每删除一次调用一次。
    5. onDisabled:最后一个该桌面小部件被删除时调用。
      所以在onUpdate方法中利用RemoteViews来显示了新的布局,并利用pendingIntent来实现点击小部件控件跳转的方法。

    二、RemoteViews的内部机制

    RemoteView的作用是在其他进程中显示并更新View的界面,最常用的构造方法:

    public RemoteViews(String packageName,int layoutId)
    

    RemoteViews没有提供findViewById方法,必须通过RemoteViews所提供的一系列set方法来完成更新;这些set方法通过反射来完成的.


    RemoteViews的部分set方法

    通知栏以及小部件分别由NotificationManager和AppWidgetManager管理,而NotificationManager以及AppWidgetManager通过Binder分别和SystemServer进行中的NotificationManagerService以及AppWidgetService进行通信,因此,通知栏以及桌面小部件中的布局文件是在NotificationManagerService以及AppWidgetService中被加载的,而它们运行在系统的systemServer中,这就和我们的进行构成了跨进程通信的场景。
    这里没有使用Binder进行进程通信,由于View的方法太多大量的IPC操作会影响效率,这里提供了Action的概念,Action代表一个View的操作,系统将Action操作封装到Action对象并将这些对象跨进程传输到远程进程中,接着直接Action对象中的Action操作。我们使用RemoteViews时,每调用一个set方法,就会添加一个Action对象,当我们通过NotificationManager和AppWidgetManager提交更新时,这些Action对象就会传输到远程进程中并依次执行。


    内部机制

    在RemoteViews的源码中,可以看到定义了一个Action对象的列表

     /**
     * An array of actions to perform on the view tree once it has been
     * inflated
     */
     private ArrayList<Action> mActions;</pre>
    

    而Action的是对远程视图进行的操作的一个封装。因为我们无法通过RemoteViews的findViewById方法来操作视图,所以RemoteViews每次视图的操作都会创建一个action对象添加到列表中。

    /**
     * Base class for all actions that can be performed on an
     * inflated view.
     *
     *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
     */
     private abstract static class Action implements Parcelable {
       public abstract void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException;
    
       public static final int MERGE_REPLACE = 0;
       public static final int MERGE_APPEND = 1;
       public static final int MERGE_IGNORE = 2;
    
       public int describeContents() {
         return 0;
       }
    
       /**
       * Overridden by each class to report on it's own memory usage
       */
       public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
        // We currently only calculate Bitmap memory usage, so by default,
        // don't do anything here
       }
    
       public void setBitmapCache(BitmapCache bitmapCache) {
       // Do nothing
       }
    
       public int mergeBehavior() {
        return MERGE_REPLACE;
       }
    
       public abstract String getActionName();
    
       public String getUniqueKey() {
         return (getActionName() + viewId);
       }
    
       /**
         * This is called on the background thread. It should perform any non-ui computations
       * and return the final action which will run on the UI thread.
       * Override this if some of the tasks can be performed async.
       */
       public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
         return this;
       }
    
       public boolean prefersAsyncApply() {
         return false;
       }
    
       /**
       * Overridden by subclasses which have (or inherit) an ApplicationInfo instance
       * as member variable
       */
       public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
         return true;
       }
      
       int viewId;
     }
    

    从源码中可以看出,action提供了一个抽象的方法

     public abstract void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException;
    

    在RemoteViews.java中发现了有很多Action的子类, 这里重点讲解一个类

     /**
     * Base class for the reflection actions.
     */
     private final class ReflectionAction extends Action {
     ...
     }
    

    因为很多更新视图的方法最后都走到

    addAction(new ReflectionAction(viewId, methodName, type, value));
    

    可以发现,当RemoteViews通过set方法来更新一个视图时,并没有立即更新,而是添加到action列表中。这样可以大大提高跨进程通信的性能,至于什么时候更新,对于自定义通知栏,需要NotificationManager调用notify()之后;而对于桌面小部件,则需要AppWidgetManager调用updateAppWidget()之后。
    最后进入ReflectionAction类中的apply方法看一下,发现内部就是利用反射机制设置相关视图的。

     @Override
     public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
      final View view = root.findViewById(viewId);
      if (view == null) return;
     
      Class<?> param = getParameterType();
      if (param == null) {
       throw new ActionException("bad type: " + this.type);
      }
    
     try {
      getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
     } catch (ActionException e) {
       throw e;
     } catch (Exception ex) {
       throw new ActionException(ex);
     }
    }
    

    注意:当我们调用RemoteViews的set方法时,并不会立刻更新它们的界面,必须通过NotificationManager的notify方法以及AppWidgetManager的updateAppWidget方法才会更新他们的界面。

    三、PendingIntent概述

    peddingIntent表示一种处于pending状态的意图,pending状态表示一种待定、等待、即将发生。就是说接下来有一个Intent将要在某个待定的时刻发生。PendingIntent和Intent的区别在于,PendingIntent是在将来某个时刻发生,而Intent是立刻发生。PendingIntent典型的使用场景是给RemoteViews添加单机事件。由于RemoteViews运行在远程进程中,无法直接调用setOnClickListener方法来设置单击事件,就需要使用pendingIntent,PendingIntent通过send和cancel来发送和取消待定的Intent。

    PendingIntent支持的三种待定意图:启动Activity、启动Service以及发送广播


    PendingIntent的主要方法

    这三个方法的参数意义是相同的,第一个和第三个参数比较好理解,主要是第二个和第四个;
    第二个参数requestCode,requestCode表示PendingIntent方的请求码,多数情况设为0即可。requestCode会影响到flags的效果.
    第四个参数flags有四种类型:

    1. FLAG_ONE_SHOT
      PendingIntent只被使用一次,然后被自动cancel,后续如果还有相同的PendIntent,那么它们的send方法调用失败。对通知栏消息来说,同类的通知只会使用一次,后续的通知单击后将无法打开。
    2. FLAG_NO_CREATE
      当前描述的PendIntent不会主动创建,如果当前PendIntent之前不存在,那么getActivity,getService,getBroadcast方法会直接返回null,即获取PendingIntent失败。
    3. FLAG_CANCEL_CURRENT
      当前描述的PendIntent如果已经存在,那么它们会被cancel,然后系统创建一个新的PendingIntent。对通知栏消息来说,那些被cancel的消息单击后将无法打开。
    4. FLAG_UPDATE_CURRENT
      当前描述的PendIntent如果已经存在,那么它们都会被更新。即它们的Intent中的Extras会被替换成最新的。

    PendingIntent的匹配规则为:
    如果两个PendingIntent它们内部的Intent相同并且 requestCode也相同则相同。
    Intent的匹配规则为:
    如果两个Intent的ComponentName和intent-filter都相同,则它们两个就是相同的。

    通过通知栏消息理解这四个标记,分两种情况
    manager.notify(1,notification)
    1、通知ID是常量
    多次调用notify只能弹出一个通知,后续的通知会把前面的通知完全替代掉;
    2、通知ID是变量
    多次调用notify会弹出多个通知;这也分两种情况

      1. PendingIntent不匹配
        不管采用何种标记位,这些通知之间不会相互干扰;
      1. PendingIntent匹配状态,标记位为:
        FLAG_ONE_SHOT:后续通知中的PendingIntent会和第一条通知保持完全一致,包括其中的Extras,单击任何一条通知后,剩下的通知均无法再打开,当所有的通知都被清除后,会再次重复这个过程
        FLAG_CANCEL_CURRENT:只有最新的通知可以打开,之前弹出的所有通知均无法打开;
        FLAG_UPDATE_CURRENT:之前弹出的通知中的PendingIntent会被更新,最终它们和最新一条通知保持完全一致,包括其中的Extras,并且这些通知都是可以打开的.

    相关文章

      网友评论

          本文标题:Android开发五《理解RemoteViews》

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