美文网首页
RemoteViews的认识

RemoteViews的认识

作者: Lucida_star | 来源:发表于2018-05-04 18:17 被阅读10次

微信公众号:Lucidastar
如有问题或建议,请公众号留言
最近更新:2018-05-04

这篇文章主要介绍的RemoteViews的相关知识,我主要从三个方面进行学习和讲解(理解,使用及分析)

RemoteViews的理解

定义:从名字上看是远程View,它和远程Service是一样的,它表示的是一个View结构,它可以在其他进程中显示。
应用:在Android中使用的场景有两种,通知栏和桌面小部件

RemoteViews的使用

一、在通知栏上的应用

1、默认弹出

NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"chaper_5");builder.setSmallIcon(R.mipmap.ic_launcher);builder.setTicker("ticker")        .setContentText("contentText")        .setWhen(System.currentTimeMillis())        .setContentTitle("contentTitle")        .setPriority(NotificationCompat.PRIORITY_HIGH)        .setDefaults( Notification.DEFAULT_VIBRATE | Notification.DEFAULT_ALL | Notification.DEFAULT_SOUND )        .setTicker("悬浮通知");Intent intent = new Intent(this,DemoActivity_1.class);PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);builder.setContentIntent(pendingIntent);NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);manager.notify(1,builder.build());

2、自定义样式弹出
自定义一个xml layout_notification.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:background="@color/light_green"    android:padding="10dp"    android:gravity="center"    android:orientation="horizontal" >    <ImageView        android:id="@+id/icon"        android:background="#000000"        android:layout_width="40dp"        android:scaleType="centerInside"        android:src="@drawable/icon1"        android:layout_height="40dp" />    <LinearLayout        android:id="@+id/layout"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:gravity="right"        android:orientation="vertical" >        <TextView            android:id="@+id/msg"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginBottom="5dp"            android:textColor="@android:color/white"            android:visibility="visible"            android:text="TextView" />        <TextView            android:id="@+id/open_activity2"            android:padding="2dp"            android:background="@drawable/common_bkg"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:textColor="@android:color/white"            android:text="open DemoActivity_2" />    </LinearLayout></LinearLayout>

代码进行设置

NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"chaper_5");        builder                .setSmallIcon(R.mipmap.ic_launcher);//                .setTicker("ticker")//                .setContentText("contentText")//                .setWhen(System.currentTimeMillis())//                .setContentTitle("contentTitle")//                .setPriority(NotificationCompat.PRIORITY_HIGH)//                .setDefaults( Notification.DEFAULT_VIBRATE | Notification.DEFAULT_ALL | Notification.DEFAULT_SOUND )//                .setTicker("悬浮通知");//        Intent intent = new Intent(this,DemoActivity_2.class);//        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,PendingIntent.FLAG_UPDATE_CURRENT);//        builder.setContentIntent(pendingIntent);        RemoteViews remoteViews = new RemoteViews(getPackageName(),R.layout.layout_notification);        remoteViews.setTextViewText(R.id.msg,"chapter_5");        remoteViews.setImageViewResource(R.id.icon,R.drawable.icon1);        PendingIntent openActivity2pendingIntent = PendingIntent.getActivity(this,0,new Intent(this,DemoActivity_2.class),PendingIntent.FLAG_UPDATE_CURRENT);        remoteViews.setOnClickPendingIntent(R.id.open_activity2,openActivity2pendingIntent);        builder.setCustomContentView(remoteViews);        builder.setContentIntent(openActivity2pendingIntent);        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);        manager.notify(2,builder.build());

二、桌面小部件的使用

1、在layout下创建一个xml文件,widget.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">        <ImageView            android:id="@+id/imageView1"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:src="@drawable/icon1"        /></LinearLayout>

2、在res/xml/下新建appwidget_provider_info.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="8640000"// 小部件自动更新周期,毫秒为单位    ></appwidget-provider>

3、创建一个自定义类MyAPPWidgetProvider,继承APPWidgetProvider

public class MyAppWidgetProvider extends AppWidgetProvider{    private static final String TAG = "MyAppWidgetProvider";    public static final String CLICK_ACTION = "com.lucidastar.chapter_5.action.CLICK";    @Override    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {        super.onUpdate(context, appWidgetManager, appWidgetIds);        final int counter = appWidgetIds.length;        for (int i = 0; i < counter; i++) {            int appWidgetId = appWidgetIds[i];            onWidgetUpdate(context,appWidgetManager,appWidgetId);        }    }    private void onWidgetUpdate(Context context, AppWidgetManager appWidgetManager, int appWidgetId) {        RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget);        Intent intentClick = new Intent();        intentClick.setAction(CLICK_ACTION);        PendingIntent pendingIntent = PendingIntent.getBroadcast(context,0,intentClick,0);        remoteViews.setOnClickPendingIntent(R.id.imageView1,pendingIntent);        appWidgetManager.updateAppWidget(appWidgetId,remoteViews);    }    @Override    public void onReceive(final Context context, final Intent intent) {        super.onReceive(context, intent);        if (intent.getAction().equals(CLICK_ACTION)){            Toast.makeText(context,"click_it",Toast.LENGTH_SHORT).show();            new Thread(new Runnable() {                @Override                public void run() {                    Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(),R.drawable.icon1);                    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);                    for (int i = 0; i < 40; i++) {                        float degree = (i * 10) % 360;                        RemoteViews remoteViews = new RemoteViews(context.getPackageName(),R.layout.widget);                        remoteViews.setImageViewBitmap(R.id.imageView1,rotateBitmap(context,bitmap,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();        }    }    private Bitmap rotateBitmap(Context context, Bitmap bitmap, float degree) {        Matrix matrix = new Matrix();        matrix.reset();        matrix.setRotate(degree);        Bitmap bitmap1 = Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);        return bitmap1;    }}

4、在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.lucidastar.chapter_5.action.CLICK"/>//小部件的单击行为        <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>//系统规范    </intent-filter></receiver>
了解一下APPWidgetProvider中的方法介绍
  • onEnable:当该窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用

  • onUpdate:小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机由updatePeriodMillis来指定,每个周期小部件都会自动更新一次。

  • onDeleted:每删除一次桌面小部件就调用一次

  • onDisabled:当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个。

  • onReceive:这是广播的内置方法,用于分发具体的事件给其他方法。


在使用RemoteViews的同时,会用到PendingIntent这个类,简单的介绍一下。
PendingIntent的概述
表示一种处于pending状态的意图,pending状态表示一种待定、等待、即将发生的意思。Intent将某个待定的确定的时刻发生。
Intent是立刻发生。因为RemoteView运行在远程进程中,因此RemoteViews不同于普通的View,所以无法直接向View那样通过setOnClickListener方法来设置单击事件。所以就必须使用PendingIntent,PendingIntent通过send和cancel方法来发送和取消特定的待定Intent。

public static PendingIntent getActivity(Context context, int requestCode,        Intent intent, @Flags int flags) {    return getActivity(context, requestCode, intent, flags, null);}public static PendingIntent getService(Context context, int requestCode,        @NonNull Intent intent, @Flags int flags) {    return buildServicePendingIntent(context, requestCode, intent, flags,            ActivityManager.INTENT_SENDER_SERVICE);}public static PendingIntent getBroadcast(Context context, int requestCode,        Intent intent, @Flags int flags) {    return getBroadcastAsUser(context, requestCode, intent, flags,            new UserHandle(UserHandle.myUserId()));}

requestCode表示PendingIntent发送方的请求码,多数设置为0就可以了。
requestCode会影响到flag的效果。

flag的类型:
  • FLAG_ONE_SHOT:
    当前描述的PendingIntent只能被使用一次,然后它就会被自动cancel,如果后续还有相同的PendingIntent,那么他们的send方法就会调用失败。对于通知栏消息来说,如果采用此标记位,那么同类的通知只能使用一次,后续的通知单击后将无法打开。

  • FLAG_NO_CREATE:
    当前描述的PendingIntent不会主动创建,如果当前PendingIntent之前不存在,那么getActivity、getService和getBroadcast方法会直接返回null,即获取PendingIntent失败,这个标记位很少见,无法单独使用。

  • FLAG_CANCEL_CURRENT:
    当前描述的PendingIntent如果已经存在,那么他们都会被cancel,然后系统会创建一个新的PendingIntent,对于通知栏消息来说,那些被cancel的消息单击后将无法打开

  • FLAG_UPDATE_CURRENT:
    当前描述的PendingIntent如果已经存在,那么它们都会被更新,即他们的Intent中的Extras会被替换成最新的。


RemoteViews的分析

首先看一张图

图注:Lucidastar公众号

支持的View类型

LayoutFrameLayout  、 LinearLayout、Relativelayout、GridLayoutViewAnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewStub。不支持他们的子类及其他View的类型。也无法使用自定义的View。

我先通过提问问题来进行理解和分析

  1. 通过RemoteView实现的通知栏和桌面小部件是跨进程操作吗?

  2. 通过什么进行跨进程操作的?

  3. RemoteView是如何进行更新操作的?

  4. RemoteView为何只支持部分的View和View的操作

  • 是跨进程操作的。
  • NotificationManager和APPWidgetManager通过Binder分别和SystemServer进程中NotificationManagerService以及APPWidgetService进行通信,也就是在两个Service中被加载的,而他们运行在系统的SystemServer中,这就和我们的进程构成了跨进程通信的场景。
  • 首先我们要了解它是如何加载到SystemServer进程中的,RemoteView是通过Binder传递到SystemServer进程中的,因为RemoteView实现了Parcelable接口,系统会根据RemoteView中的包名等信息得到该应用的资源,然后通过LayoutInflater去加载RemoteView中的布局文件,这就在SystemServer中形成了一个普通的View。然后通过我们的set方法对View进行更新,在RemoteView内部会记录所有的更新操作,不会立马进行更新,等到RemoteView被加载以后才执行,这样RemoteViews就可以在SystemServer进程中进行显示了,具体的操作还是在SystemServer进程中进行。
  • 其实系统完全可以通过Binder去支持所有的View和View操作,这样代价太大,并且View的方法太多,大量的IPC操作会影响效率。而且系统并没有通过Binder去直接支持View的跨进程操作,而提供了一个Action概念,Action代表一个View的操作,我们每set一次操作,RemoteViews就会添加一个对应的Action对象,然后通过NotificationManager和AppWidgetManager来提交我们的操作。

然后通过代码来分析是如何进行更新操作的

remoteViews.setTextViewText(R.id.msg,"chapter_5");public void setTextViewText(int viewId, CharSequence text) {    setCharSequence(viewId, "setText", text);}public void setCharSequence(int viewId, String methodName, CharSequence value) {    addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));}private void addAction(Action a) {   ...    if (mActions == null) {        mActions = new ArrayList<Action>();    }    mActions.add(a);    // update the memory usage stats    a.updateMemoryUsageEstimate(mMemoryUsageCounter);}

首先看一下RemoteViews的方法apply以及Action类的实现。

public View apply(Context context, ViewGroup parent, OnClickHandler handler) {    RemoteViews rvToApply = getRemoteViewsToApply(context);    View result = inflateView(context, rvToApply, parent);//inflateView就是通过LayoutInflat而对象获取一个View    loadTransitionOverride(context, handler);    rvToApply.performApply(result, parent, handler);    return result;}

遍历所有的Actions列表,然后执行每个apply方法。

private void performApply(View v, ViewGroup parent, OnClickHandler handler) {    if (mActions != null) {        handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;        final int count = mActions.size();        for (int i = 0; i < count; i++) {            Action a = mActions.get(i);            a.apply(v, parent, handler);        }    }}

我们看一下ReflectionAction的具体实现

private final class ReflectionAction extends Action {  .....    ReflectionAction(int viewId, String methodName, int type, Object value) {        this.viewId = viewId;        this.methodName = methodName;        this.type = type;        this.value = value;    }@Overridepublic 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);    }  }}

ReflectionAction表示的是第一个反射动作,通过它对View的操作会以反射的方式来调用。
另外:
setOnClickPendingIntent、setPendingIntentTemplate以及setOnClickFillInIntent他们之间的区别和联系

setOnClickPendingIntent用于给普通的View设置单击事件,但不能给集合(ListView和StackView)中的View设置单击事件。要给ListView和StackView中的item添加单击事件,必须将setPendingIntentTemplate和setOnClickFillInIntent组合使用才可以。

如果说两个进程间的布局更新 都是些简单的View 最好使用RemoteViews 因为它内部有Action模式比AIDL效率高 如果布局有自定义的View 那就只能使用AIDL 通信 然后更新布局了。

通过理解和分析我们需要掌握的:

  1. RemoteViews的使用
  2. 能够熟练的创建通知栏和桌面小控件
  3. RemoteViews内部是怎么一个过程(可以看上面的问题4的回答)
    我认为掌握这三点就可以了。
image

相关文章

网友评论

      本文标题:RemoteViews的认识

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