一.介绍
RemoteView表示的是一个View结构,他可以在其他进程中显示,由于它在其他进程中显示,为了能够更新他的界面,RemoteViews提供了一组基础的操作应用与跨进程更新它的界面。
二.应用场景
1.通知栏
2.桌面小部件
三.RemoteViews的应用
桌面小部件则是通过AppWidgetProvider来实现的,AppWidget本质是一个广播.
通知栏和桌面小部件的开发过程中都会用到RemoteView,它们在更新界面时无法像在Activity里面那样直接更新View,这是因为两者的界面都运行在其他线程中,确切的说是系统的SystemServer进程.为了跨进程更新界面,RemoteViews提供一系列set方法,并且这些方法只是View全部方法的子集,另外RemoteVIew支持的View类型也是有限的。
四.RemoteViews在通知栏和桌面小部件上的应用
(1)通知栏
- String apkName = fileName.substring(fileName.lastIndexOf("/") + 1);
- String time = new SimpleDateFormat("hh:MM:ss").format(new Date());
- Notification notification = new Notification(
- R.drawable.stat_sysl_complete, "下载失败",
- System.currentTimeMillis());
- notification.flags = Notification.FLAG_AUTO_CANCEL;
- notification.contentView = new RemoteViews(mContext.getPackageName(),
- R.layout.notifyed);
- notification.contentView.setImageViewResource(R.id.notifyLog, icon);
- notification.contentView.setTextViewText(R.id.notifyMessage,
- "下载失败,点击重新下载");
- notification.contentView.setTextViewText(R.id.notifyTitle, apkName);
- notification.contentView.setTextViewText(R.id.notifyTime, time);
- Intent intent = new Intent(SisterReceiver.ACTION_DOWNLOAD_FAILED);
- intent.putExtra("url", url);
- intent.putExtra("notifyId", notifyId);
- NotificationManager
- notifyM = (NotificationManager) context
- .getSystemService(Context.NOTIFICATION_SERVICE);
-
- notification.contentIntent = PendingIntent.getBroadcast(mContext,
- notifyId, intent, 0);
- notifyM.notify(notifyId, notification);
如何更新RemoteView呢?
1.无法直接访问里面的View,而必须通过RemoteView所提供的一系列方法来更新View,比如设置TextView的文本,要采用如下方式:RemoteView.setTextVIewText(R.id.msg,”Chapter_5”),其中setTextViewText的两个参数分别为TextView的id和要设置的文本。
2.如果要给一个控件加单击事件,则要使用PendingIntent并通过setOnClickPendingIntent方法来实现,比如remoteViews.setonClickPendingIntent(R.id.open_activity2,openActivity2Pending-Intent)这句代码会给id为open_activity的View加上单击事件。
关于PendingIntent,它表示的是一种待定的Intent,这个Intent中所包含的意图必须由用户来触发。
(2)桌面小部件的(不用启动,只要在清单文件中配置好了,直接在桌面长按添加小部件即可)
<1>简单实现
1.在res/layout下新建一个XML文件,命名为Widget.xml名称和内容可以自定义。
//Widget.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>
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="86400000" >
-
- </appwidget-provider>
3.定义小部件的实现类
//MyAppWidgetProvide.Java
- 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());
-
-
- 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);
- }
-
- }
-
-
-
-
-
-
-
-
- private void onWidgetUpdate(Context context,
- AppWidgetManager appWidgeManger, int appWidgetId) {
-
- Log.i(TAG, "appWidgetId = " + 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);
- 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;
- }
- }
4.千万别忘记要在清单文件注册!!!
- <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>
Android.appwidget.action.APPWIDGET_UPDATE作为小部件的标识必须存在,不加就不会出现在小部件列表里
onEnable :当窗口小部件第一次添加到桌面时调用该方法,可添加多次,但只在第一次调用.
onUpdate:小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机由updatePeriodMIllis来指定,每个周期小部件都会自动更新一次。
onDelete:每删除一次桌面小部件就调用一次
onDisable:当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个
onReceiver:这是广播的内置方法,用于分发具体的事件给其他方法
<2>简单实践(桌面有个控件实时展现占用内存,点击清理后台进程)
1.
自定义一个MyAppWidget(类名自定义)类继承AppWidgetProvider
功能:
1.在onupdate中开启服务
2.在ondiable中关闭服务
- public class MyAppWidget extends AppWidgetProvider {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- System.out.println("onreceiver");
- super.onReceive(context, intent);
- }
-
- @Override
- public void onUpdate(Context context, AppWidgetManager appWidgetManager,
- int[] appWidgetIds) {
-
- System.out.println("onupdate");
- Intent intent = new Intent(context, UpdateWidgetService.class);
- context.startService(intent);
- super.onUpdate(context, appWidgetManager, appWidgetIds);
- }
-
- @Override
- public void onDeleted(Context context, int[] appWidgetIds) {
- System.out.println("onDeleted");
- super.onDeleted(context, appWidgetIds);
- }
-
- @Override
- public void onEnabled(Context context) {
- System.out.println("onEnabled");
- super.onEnabled(context);
- }
-
- @Override
- public void onDisabled(Context context) {
- System.out.println("onDisabled");
- Intent intent = new Intent(context, UpdateWidgetService.class);
- context.stopService(intent);
- super.onDisabled(context);
- }
- }
2.
在AndroidManifest.xml 中注册MyAppWidget,因为AppWidgetProvider 继承了BroadcastReceiver
- <receiver android:name="com.daxiong.appwidget.MyAppWidget" >
- <intent-filter>
- <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
- </intent-filter>
- <meta-data
- android:name="android.appwidget.provider"
- android:resource="@xml/example_appwidget_info" />
- </receiver>
3.
@xml/example_appwidget_info这个文件要重写
该文件在res->xml 目录中。
- <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
- android:minWidth="294dp"
- android:minHeight="72dp"
- android:updatePeriodMillis="1800000"
- android:initialLayout="@layout/process_widget"
- >
- </appwidget-provider>
4.
编写布局文件process_widget.xml(文件名自定义),该布局文件用于widget 显示界面
注意:
1.widget的系统框架允许的最短更新时间是0.5hour,设置小于这个值也没有用。因为考虑到过于频繁更新比较耗电,但是有些需求就需要实时更新,比如桌面的时钟,或者天气之类的。
2.widget是显示在另外一个应用程序里(比如桌面原生的桌面),后期别人修改的桌面会导致widget的声明周期不一样,只要记住
不要记生命周期调用的先后顺序.
onenable 方法什么时候调用(第一次创建的时候)
ondisabled 方法什么时候调用(桌面所有该app的widget都被删除了才会调用)
onupdate方法 在每次创建新的widget的时候都会调用 , 并且当时间片(也就是最少半小时)到的时候也会调用
3.所以只能在widget中onupdate中开启一个服务来更新widget,这样避免了即使服务异常终止,导致更新异常,onupdate只要本app有新的widget创建就会开启服务,而且每过半小时,onupdate就会执行一次
如果只有上面的步骤那么我们的widget 就可以运行了,但是widget 一般都是需要动态更新的,比如我们的widget 是需要动态显示当前系统的内存信息的,因此我们还需要在我们的广播中开启一个service,在service 中对
widget 进行动态更新。
下面将把上面步骤的详细过程和代码清单展示出来。
5.用Service 动态更新widget
思路:
1.获取系统桌面更新widget的服务
awm = AppWidgetManager.getInstance(this)
2.获取准备更新的组件名称
provider = new ComponentName(this,MyAppWidget.class);
3.告诉桌面布局文件去哪里找(这里获取的views不是真正的view,而是一个描述(包括包名和布局文件),view对象市创建在桌面空间里,桌面应用来修改里面的内容),然后桌面获得这个描述然后通过他的布局文件,然后转成view对象,然后再找里面的个控件,所以我们只要告诉他找哪个控件就行
views = new RemoteViews(getPackageName(),R.layout.process_widget);
4.告诉桌面修改哪个控件就行
views.setTextViewText(R.id.process_count,"正在运行的软件个数"+SystemInfoUtils.getCountRunningProcess(getApplicationContext()));
views.setTextViewText(R.id.process_memory,"剩余内存"+Formatter.formatFileSize(getApplicationContext(),SystemInfoUtils.getAvailRam(getApplicationContext())));
5.最后不要忘了,显示出来
awm.updateAppWidget(provider, views);
代码:
//UpdateWidgetService.Java
- public class UpdateWidgetService extends Service {
- private Timer timer;
- private TimerTask task;
- private PendingIntent pendingIntent;
-
- @Override
- public IBinder onBind(Intent intent) {
-
- return null;
- }
-
- @Override
- public void onCreate() {
-
- final AppWidgetManager awm = AppWidgetManager.getInstance(this);
-
- final ComponentName provider = new ComponentName(this, MyAppWidget.class);
-
-
-
-
-
-
-
-
- final RemoteViews views = new RemoteViews(getPackageName(), R.layout.process_widget);
-
-
- if (timer == null && task == null) {
- timer = new Timer();
- task = new TimerTask() {
- @Override
- public void run() {
- System.out.println("更新了");
- views.setTextViewText(R.id.process_count, "正在运行的软件个数" + SystemInfoUtils.getCountRunningProcess(getApplicationContext()));
- views.setTextViewText(R.id.process_memory, "剩余内存" + Formatter.formatFileSize(getApplicationContext(), SystemInfoUtils.getAvailRam(getApplicationContext())));
-
-
- Intent intent = new Intent();
- intent.setAction("com.daxiong.killallprocess");
- pendingIntent = PendingIntent.getBroadcast(getApplicationContext(), 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
-
- views.setOnClickPendingIntent(R.id.btn_clear, pendingIntent);
-
- awm.updateAppWidget(provider, views);
- }
- };
- }
- timer.schedule(task, 0, 5000);
- super.onCreate();
- }
-
- @Override
- public void onDestroy() {
- if (timer != null && task != null) {
- timer.cancel();
- task.cancel();
- timer = null;
- task = null;
- }
- super.onDestroy();
- }
- }
//KillAllProcessReceiver.java
- public class KillAllProcessReceiver extends BroadcastReceiver {
-
- @Override
- public void onReceive(Context context,Intent intent) {
- ActivityManager am = (ActivityManager) context.getSystemService(context.ACTIVITY_SERVICE);
- List<RunningAppProcessInfo> list = am.getRunningAppProcesses();
- for (RunningAppProcessInfo runningAppProcessInfo : list) {
- am.killBackgroundProcesses(runningAppProcessInfo.processName);
- }
- Toast.makeText(context, "清除完毕了", 0).show();
- }
-
- }
5.PendingIntent概述
PendingIntent表示一种处于pending状态的意图,而pending状态表示的是一种待定,等待,即将发生的意思,就是说接下来有一个Intent(即意图)将在某个待定的时刻发生,而Intent是立即发生。
PendingIntent典型的使用场景是给RemoteView添加单击事件,因为RemoteViews运行在远程进程中,要想给RemoteViews设置单击事件,就必须使用PendingIntent,PendingIntent通过send和cancel方法来发送和取消特定的待定Intent.
PendingIntent支持三种待定意图:启动Activity,启动Service和发送广播,对应着它的三个接口方法
Static PengdingIntent | getActivity(Context context,Int requestCode , Intent intent,int flags) 获得一个PendingIntent,该待定意图发生时,效果相当于Context.startActivity(Intent) |
Static PengdingIntent | getService(Context context,int requestCode ,Intent intent,inte flags) 获得一个PendingIntent,该待定意图发生时,效果相当于Context.startService(intent) |
Static PengdingIntent | getBroadcast(Contex context,int requestCode,Intent intent ,int flags) 获得一个PendingIntent,该待定意图发生时,效果相当于 Context.sendBroadCast |
requestCode表示PendingIntent发送方的请求码,多数情况下设为0即可,PS:requestCode会影响到flags
常见Flag
FLAG_ONE_SHOT
当面描述的PendingIntent只能使用一次,然后他就会被自动cancel,如果后续还有相同的PendingIntent,那么它们的send方法就会调用失败.对于通知栏消息来说,如果采用此标记位,那么同类的通知只能使用一次,后续通知单击后将无法打开。
FLAG_NO_CREATE
当前描述的PendingIntent不会主动创建,如果当前PendingIntent之前不存在,那么getActivity,getService,getBroadcast方法会直接返回null,即获取PendingIntent失败,这个基本不用,不用看
FLAG_CANCEL_CURRENT
如果已经存在,那么它们都会被cancel,然后创建一个新的,对于通知栏消息来说,那些被cancel的消息单击将无法打开。
FLAG_UPDATE_CURRENT
如果已经存在,那么它们会被更新,即他们的Intent中的Extras会被替换成最新的.
Manager.notify(id,notification)
其实上面的各种flag的运用就分为下面两种情况
1.id是常量,那么不管PendingIntent是否匹配,后面的通知会直接替换前面的通知
2.Id每次都不同
<1>pendingIntent匹配
(1)FLAG_ONE_SHOT 产生新的通知并与第一条通知保持一致,点击任意一条,其他的都无法点击
(2)FLAG_CANCEL_CURRENT 产生新的通知,只有最新的才能打开,之前的都打不开
(3)FLAG_UPDATA_CURRENT 产生新的通知,之前的通知也会被更新,最后跟新产生的通知保持一致
<2>pendingIntent不匹配 这时候不管采用任何FlAG,这些通知都不会互相干扰
PendingIntent的匹配规则
(1)Intent
<1>ComponentName(就是new Intent(ComponentName),就是(MainActivity.this,MyActivity.class))
<2>intent-filter
只要上面的两者相同就行,即使他们的Extra不同,那么这两个Intent也是相同的
(2)requestCode
这两者相同那么就是同一个PendingIntent
<3>RemoteVIews的内部机制
Public RemoteViews(String packageName ,int layoutId),它接受两个参数,第一个表示当前应用的包名,第二个表示待加载的布局文件,这个很好理解
他所支持的所有类型如下:
Layout
FrameLayout,LInearLayout,RelativeLayout,GridLayout
View
AnalogClock,Button,Chornometer,ImageButton,ImageView,ProgressBar,TextView,ViewFlipper,ListView,GridView,StackView,AdapterViewFlipper,ViewStub
上面所描述的是RemoteViews所支持的所有View类型,RemoteViews不支持他们的子类以及其他View类型,也就是说RemoteViews中不能使用除了上述列表中以外的View,也无法使用自定义的view.
RemoteViews的部分set方法

事实上大部分set方法是通过反射来完成的

1.RemoteViews会通过Binder传递到SystemServer进程(因为RemoteViews实现了Parcelable接口,因此它可以跨进程传输)
2.然后会通过LayoutInflater去加载RemoteViews中的布局文件,然后在SystemServer中加载的是一个普通的View,只不过相对于我们的进程他是一个RemoteView而已
3.接着系统会对View执行一系列界面更新任务,这些任务就是之前我们通过set方法提交的,set方法对View所做的更新并不是立刻执行的(具体的执行时机要等到RemoteViews被加载以后才能执行,这样RemoteViews就可以在SystemServer进程中显示了),在RemoteViews内部会记录所有的更新操作!!!

1.我们的应用中没调用一次Set方法,RemoteViews中就会添加一个对应的Action对象
2.当我们通过NotificationManager和AppWidgerManager来提交更新时,这个Action对象就会传到远程进程中(SystemServer)并在远程进程中依次执行.RemoteVIews的apply方法内部则会去遍历所有的Action对象并调用他们的apply方法,具体的View更新操作是由Action对象的apply方法来完成的,remoteView的reApply则只会更新界面,apply会加载布局并更新界面
优点:不需要定义大量的Binder接口,其次通过在远程进程中批量执行RemoteViews的修改操作从而避免了大量的IPC操作这就提高了程序性能
关于单击事件,RemoteViews中只支持发起PendingIntent,不支持onClickListener那种模式。另外,我们需要注意setOnClickPendingIntent,setPendingIntentTemplatey以及setOnClickFillInIntent它们之间的区别和联系

AIDL和RemoteVIew使用的考虑
现在有两个应用,
1一个应用需要能够更新另一个应用中的某个界面,这个时候我们当然可以选择AIDL去实现,但是如果对界面的更新比较频繁,这个时候就会有效率问题,同时AIDL接口就有可能会变得很复杂
2.这个时候如果采用RemoteView来实现就没有这个问题了,当然remoteView也会有点缺点,那就是他仅支持一些常见的View,对于自定VIew他是不支持的
原文地址:http://blog.youkuaiyun.com/u011889786/article/details/51496772