RemoteViews是一种远程View,可以在其他进程中显示,为了能够更新它的界面,RemoteViews提供了一组基础操作用于跨进程更新它的界面。
本章会介绍RemoteViews在通知栏和桌面小部件上的应用,分析RemoveViews的内部机制,最后分析RemoteViews的意义并给出一个采用RemoteViews来跨进程更新界面的示例。
RemoteViews主要用于通知栏和桌面小部件的开发。通知栏主要通过NotificationManager的notify方法来实现;桌面小部件则是通过AppWidgetProvider来实现的,AppWidgetProvider本质上是一个广播。因为RemoteViews运行在其他进程(SystemService进程),所以无法直接更新界面。
Notification notification = new Notification();
notification.icon = R.mipmap.ic_launcher;
notification.tickerText = "hello notification";
notification.when = System.currentTimeMillis();
notification.flags = Notification.FLAG_AUTO_CANCEL;
Intent intent = new Intent(this, RemoteViewsActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);//RemoveViews所加载的布局文件
remoteViews.setTextViewText(R.id.tv, "这是一个Test");//设置文本内容
remoteViews.setTextColor(R.id.tv, Color.parseColor("#abcdef"));//设置文本颜色
remoteViews.setImageViewResource(R.id.iv, R.mipmap.ic_launcher);//设置图片
PendingIntent openActivity2Pending = PendingIntent.getActivity
(this, 0, new Intent(this, MainActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);//设置RemoveViews点击后启动界面
remoteViews.setOnClickPendingIntent(R.id.tv, openActivity2Pending);
notification.contentView = remoteViews;
notification.contentIntent = pendingIntent;
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(2, notification);
定义好小部件界面
在res/layout下新建一个xml文件,命名为widget.xml,名称和内容可以自定义。
<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/iv"
android:layout_width="360dp"
android:layout_height="360dp"
android:layout_gravity="center" />
LinearLayout>
定义小部件的配置信息
在res/xml/下新建appwidget_provider_info.xml,名称任意。
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget"
android:minHeight="360dp"
android:minWidth="360dp"
android:updatePeriodMillis="864000"/>
定义小部件的实现类
这个类需要继承AppWidgetProvider;我们这里实现一个简单的widget,点击它后,3张图片随机切换显示。
public class ImgAppWidgetProvider extends AppWidgetProvider {
public static final String TAG = "ImgAppWidgetProvider";
public static final String CLICK_ACTION = "cn.hudp.androiddevartnote.action.click";
private static int index;
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
if (intent.getAction().equals(CLICK_ACTION)) {
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
updateView(context, remoteViews, appWidgetManager);
}
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget);
updateView(context, remoteViews, appWidgetManager);
}
// 由于onReceive 和 onUpdate中部分代码相同 则抽成一个公用方法
public void updateView(Context context, RemoteViews remoteViews, AppWidgetManager appWidgetManager) {
index = (int) (Math.random() * 3);
if (index == 1) {
remoteViews.setImageViewResource(R.id.iv, R.mipmap.haimei1);
} else if (index == 2) {
remoteViews.setImageViewResource(R.id.iv, R.mipmap.haimei2);
} else {
remoteViews.setImageViewResource(R.id.iv, R.mipmap.haimei3);
}
Intent clickIntent = new Intent();
clickIntent.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, clickIntent, 0);
remoteViews.setOnClickPendingIntent(R.id.iv, pendingIntent);
appWidgetManager.updateAppWidget(new ComponentName(context, ImgAppWidgetProvider.class), remoteViews);
}
}
在AndroidManifest.xml中声明小部件
因为桌面小部件的本质是一个广播组件,因此必须要注册。
<receiver android:name=".RemoveViews_5.ImgAppWidgetProvider">
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_info">
meta-data>
<intent-filter>
<action android:name="cn.hudp.androiddevartnote.action.click" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
intent-filter>
receiver>
上面代码中有两个action,第一个是用于识别小部件的单击行为,而第二个则是作为小部件的标识必须存在的;如果不加这个receiver就不是一个桌面小部件并且也无法显示在手机的小部件中。
广播到来的时候,AppWidgetProvider会自动根据广播的Action通过onReceive方法来分发广播,也就是调用
onEnable: 当该窗口小部件第一次添加到桌面时调用的方法,可添加多次但只在第一次调用。
onUpdate: 小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机是有updatePeriodMillis来指定,每个周期小部件就会自动更新一次。
onDeleted: 每删除一次桌面小部件就调用一次。
onDisabled: 当最后一个该类型的小部件被删除时调用该方法。
onReceive: 这是广播的内置方法,用于分发具体事件给其他方法。
PendingIntent表示一种处于pending(待定、等待、即将发生)状态的意图;PendingIntent通过send和cancel方法来发送和取消特定的待定Intent。
PendingIntent支持三种待定意图:启动Activity、启动Service和发送广播。分别对应:
getActivity / getService / getBroadcast
参数相同,都为:(Context context, int requestCode, Intent intent, int flags)
其中第二个参数,requestCode表示PendingIntent发送方的请求码,多少情况下为0即可,requestCode会影响到flags的效果。
PendingIntent的匹配规则是:如果两个PendingIntent他们内部的Intent相同并且requestCode也相同,那么这两个PendingIntent就是相同的。那么什么情况下Intent相同呢?Intent的匹配规则是,如果两个Intent的ComponentName和intent-filter都相同;那么这两个Intent也是相同的。
flags参数的含义:
FLAG_ONE_SHOP 当前的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会被替换为最新的。
NotificationManager的notify方法分析
manager.notify(1,notification);
RemoteViews最大的意义在于方便的跨进程更新UI。
final String pkg = "cn.hudp.remoteviews";//需要加载app的包名
Resources resources = null;
try {
resources = getPackageManager().getResourcesForApplication(pkg);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (resources != null) {
int layoutId = resources.getIdentifier("activity_main", "layout", pkg); //获取对于布局文件的id
RemoteViews remoteViews = new RemoteViews(pkg, layoutId);
View view = remoteViews.apply(this, llRemoteViews);//llRemoteViews是View所在的父容器
llRemoteViews.addView(view);
}