Android读书笔记(5)—— 理解RemoteViews
一、RemoteViews基础认知
RemoteViews是一种可以在其他进程中显示的View,提供了一组基础操作用于跨进程更新它的界面。主要两个场景:通知栏和桌面小部件。
1、Notification
1.1、分类
1)普通的notification
1.内容标题
2.大图标
3.内容
4.内容附加信息
5.小图标
6.时间
2)大布局Notification
大布局notification是在android4.1以后才增加的,大布局notification与小布局notification只在7部分有区别,其它部分都一致。大布局notification只有在所有notification的最上面时才会显示大布局,其它情况下显示小布局。你也可以用手指将其扩展为大布局(前提是它是大布局)。
大布局notification有三种类型:图1为NotificationCompat.InboxStyle类型。图2左部为NotificationCompat.BigTextStyle。图2右部 为:NotificationCompat.BigPictureStyle
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通过send
和cancel
方法来发送和取消特定的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。