RemoteViews简介
RemoteViews,根据字面意思应该是一种远程的View,其实RemoteView表示的是一个View结构,它可以在其他进程中显示,由于它在其他进程中显示,为了能够更新它的界面,RemoteViews提供了一组基础的操作用于跨进程更新它的界面。
RemoteViews的作用
通过上面的简介,我们也大致了解RemoteViews主要用于实现跨进程更新界面,在实际开发中,RemoteViews主要用于通知栏和桌面小部件的开发。接下来,我们就以一些简单的案例来走进RemoteViews。
RemoteViews的简单实用
首先,我们看一下RemoteView在通知栏上的应用。
这里使用Notification简单实现一个通知栏弹窗,关于Notification的使用不是本文的重点,这里便不再详述,读者感兴趣可自行了解。
接下来我们尝试自定义通知栏,我们需要提供提供一个布局文件,然后使用RemoteViews加载此文件,就可以达到实现自定义通知的效果。
这里R.layout.remote布局文件中简单放置了一个imageview以及textview,需要注意的是,这里不可以直接访问布局文件中的view,需要通过remoteViews提供的方法来更新view中的内容。
接下来,我们再来了解一下RemoteViews在桌面小部件上的应用。
再实现桌面小部件之前,我们需要先了解下AppWidgetProvider
,它是Android中提供的用于实现桌面小部件的类,其实本质就是一个广播。接下来我们简单地使用RemoteViews实现桌面小部件的开发。
1. 定义小部件界面
新建XML文件,然后自定义里面的布局,我这里命名为widget.xml,里面简单放置了一个ImageView。
2. 定义小部件的配置信息
在res/xml下新建app_provider_info.xml,添加内容如下:
其中,initialLayout加载的就是小部件界面布局,minHeight与minWidth定义小部件的最小尺寸,updatePeriodMillis为小部件自动更新的周期,单位为毫秒。
3. 定义小部件的实现类
这个类需要继承AppWidgetProvider,上面已经谈及AppWidgetProvider本质就是一个广播,所以实现和广播相差不多。
public class MyAppWidgetProvider extends AppWidgetProvider {
public static final String TAG = "TAG";
public static final String CLICK_ACTION = "app_widget_provider";
@Override
public void onReceive(final Context context, final Intent intent) {
super.onReceive(context, intent);
Log.d(TAG, "action:" + intent.getAction());
//当收到的action为我们自定义的action时,做一个动画效果的处理
if (intent.getAction().equals(CLICK_ACTION)) {
new Thread(new Runnable() {
@Override
public void run() {
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher);
AppWidgetManager manager = 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.iv_widget,rotateBitmap(context,bitmap,degree));
Intent intentClick=new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intentClick,0);
remoteViews.setOnClickPendingIntent(R.id.iv_widget,pendingIntent);
manager.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.d(TAG,"app widget provider update...");
int count=appWidgetIds.length;
Log.d(TAG,"count="+count);
for (int i = 0; i < count; i++) {
int appWidgetId=appWidgetIds[i];
onWidgetUpdate(context,appWidgetManager,appWidgetId);
}
}
/**
* 桌面小部件更新操作
* @param context
* @param manager
* @param appWidgetId
*/
private void onWidgetUpdate(Context context,AppWidgetManager manager,int appWidgetId){
RemoteViews remoteViews=new RemoteViews(context.getPackageName(),R.layout.widget);
Intent intent=new Intent(CLICK_ACTION);
PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,0);
remoteViews.setOnClickPendingIntent(R.id.iv_widget,pendingIntent);
manager.updateAppWidget(appWidgetId,remoteViews);
}
/**
* 将Bitmap旋转相对应角度
* @param context
* @param bitmap 原始bitmap
* @param degree 角度
* @return
*/
private Bitmap rotateBitmap(Context context,Bitmap bitmap,float degree){
Matrix matrix=new Matrix();
matrix.reset();
matrix.setRotate(degree);
Bitmap temBitmap=Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
return temBitmap;
}
上面代码实现了一个简单的桌面小部件,小部件中显示一张图片,点击它,图片会旋转一周,小部件被添加到桌面后,通过RemoteViews加载布局文件,当小部件被单击后的旋转效果则是通过不断更新RemoteViews来实现的,所以,桌面小部件不管是初始化界面还是后续界面更新,都需要通过RemoteViews来完成。
4. 在AndroidManifest.xml中声明小部件
因为桌面小部件本质上是一个广播组件,因为需要注册。
<!--注册桌面小部件-->
<receiver android:name=".MyAppWidgetProvider">
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/app_provider_info">
</meta-data>
<intent-filter>
<action android:name="app_widget_provider"></action> //标志点击行为
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"></action> //作为小部件的标识,必须存在
</intent-filter>
</receiver>
AppWidgetProvider中的常用方法
方法名 | 解释 |
---|---|
onUpdate | 每次桌面小部件更新时都调用一次该方法,更新时机由updatePeriodMillis来指定,每个周期小部件都会自动更新一次 |
onEnable | 小部件第一次添加到桌面时调用,可以添加多次但只在第一次调用 |
onDelete | 删除一次桌面小部件就调用一次 |
onDisable | 最后一个该类型的桌面小部件被删除时调用 |
onReceive | 广播的内置方法,分发具体事件 |
PeddingIntent概述
peddingIntent表示一种处于pending状态的意图,pending状态表示一种待定、等待、即将发生。就是说接下来有一个Intent将要在某个待定的时刻发生。PendingIntent和Intent的区别在于,PendingIntent是在将来某个时刻发生,而Intent是立刻发生。PendingIntent典型的使用场景是给RemoteViews添加单机事件。由于RemoteViews运行在远程进程中,无法直接调用setOnClickListener方法来设置单击事件,就需要使用pendingIntent,PendingIntent通过send和cancel来发送和取消待定的Intent。
PendingIntent支持的三种待定意图:启动Activity、启动Service以及发送广播。
方法原型|解释
—|—|
getActivity(Context context,int requestCode,Intent intent,int flags)|获取一个pendingIntent意图,意图发生时,相当于startActivity(Intent)
getService(Context context,int requestCode,Intent intent,int flags)|获取一个pendingIntent意图,意图发生时,相当于startService(Intent)
getBroadcast(Context context,int requestCode,Intent intent,int flags)|获取一个pendingIntent意图,意图发生时,相当于sendBroadcast(Intent)
第二个参数requestCode,requestCode表示PendingIntent方的请求码,多数情况设为0即可。
PendingIntent的匹配规则为:如果两个PendingIntent它们内部的Intent相同并且 requestCode也相同则相同。其中Intent相同指ComponentName和intent-filter都相同。
另外flags常见类型如下。
flags类型 | 解释 |
---|---|
FLAG_ONE_SHOT | PendingIntent只被使用一次,然后被自动cancel,后续如果还有相同的PendIntent,那么它们的send方法调用失败 |
FLAG_CANCEL_CURRENT | 当前描述的PendIntent如果已经存在,那么它们会被cancel,然后系统创建一个新的PendingIntent。 |
FLAG_UPDATE_CURRENT | 当前描述的PendIntent如果已经存在,那么它们都会被更新。 |
remoteviews的工作原理
通知栏以及小部件分别由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的set方法时,并不会立刻更新它们的界面,必须通过NotificationManager的notify方法以及AppWidgetManager的updateAppWidget方法才会更新他们的界面。
参考文献
Android开发艺术探索第5章 《理解RemoteViews》