RemoteViews在日常开发中主要用于通知栏和桌面小部件的开发。它继承了Parcelable,所以可以很好的通过Binder来进行跨进程通信,里面提供了一系列的方法用于跨进程的更新界面。RemoteViews一个最常用的构造方法是RemoteViews(String packageName,int layoutId),第一个参数是包名,第二个参数是待加载的布局文件。RemoteViews支持的控件如下:
Layout:
FrameLayout、LinearLayout、RelativeLayout、GridLayout
View
TextView 、Button、ImageView、ImageButton、ProgressBar、ListView、GridView、ViewFlipper、AdapterViewFlipper 、ViewStub、StackView、AnalogClock、Chronometer
RemoteViews仅仅支持上面的这些控件,不支持自定义View和其他类型的View。
RemoteViews的简单用法
RemoteViews提供了一系列的set方法来跨进程更新View,常见的set方法如下:
setViewVisibility(int viewId, int visibility) 设置某个View是否可见
setTextViewText(int viewId, CharSequence text) 设置TextView文本
setTextViewTextSize(int viewId, int units, float size);设置TextView字体大小
setImageViewUri(int viewId, Uri uri);设置ImageView 图片相当于调用ImageView的setImageURI方法
setImageViewBitmap(int viewId, Bitmap bitmap);设置ImageView图片
setOnClickPendingIntent(int viewId, PendingIntent pendingIntent)为View添加点击事件
setBoolean(int viewId, String methodName, boolean value)反射调用View对象参数为boolean的方法
setString(int viewId, String methodName, String value)反射调用View对象参数为String类型的方法
RemoteViews类中还有很多类似的方法,可以去参考android api。这一系列的set方法大多数都是通过反射来实现的。举个关于自定义通知栏的梨子:
NotificationCompat.Builder builder8 = new NotificationCompat.Builder(this);
builder8.setSmallIcon(R.mipmap.me_pressed);
builder8.setDefaults(NotificationCompat.DEFAULT_ALL);
builder8.setAutoCancel(true);
Intent intent_8 = new Intent(this, NotificationActivity.class);
PendingIntent pendingIntent_8 = PendingIntent.getActivity(this, 0, intent_8, PendingIntent.FLAG_UPDATE_CURRENT);
builder8.setContentIntent(pendingIntent_8);
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_remoteviews);
remoteViews.setImageViewResource(R.id.iv_image, R.mipmap.test_widget_1);
remoteViews.setTextViewText(R.id.tv_content, "测试内容");
remoteViews.setTextViewText(R.id.tv_title, "测试标题");
remoteViews.setTextColor(R.id.tv_content, getResources().getColor(R.color.colorAccent));
Intent intent8 = new Intent(this, APPWidgetActivity.class);
PendingIntent pendingIntent8 = PendingIntent.getActivity(this, 0, intent8, PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.tv_content, pendingIntent8);
builder8.setContent(remoteViews);
if (notificationManager == null) {
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}
notificationManager.notify(116, builder8.build());
效果图如下:
RemoteViews内部机制
在通知栏或者桌面小部件中,RemoteViews会通过Binder机制和系统进程进行通信,系统会根据RemoteViews中的包名去获取该应用的资源(布局文件,图片等资源)。然后通过LayoutInflater去加载RemoteViews中的布局文件。这时在系统进程中就是一个普通的View了。在应用中我们可以通过一系列的set方法去更新View。这些方法不是立刻去执行的。RemoteViews会把这些操作保存起来。当通过NotificationManager的notify方法或者AppWidgetManager的updateAppWidget方法提交时,这些操作会通过Binder传输到系统进程来进行更新View。在我们的应用中每调用一个set方法,RemoteViews中就会添加一个对应的Action对象,当我们通过NotificationManager和AppWidgetManager提交更新时,这些Action对象就会传输到系统进程中依次执行。通过阅读RemoteViews源码可以发现。远程进程通过RemoteViews的apply方法更新操作。在apply方法里会去遍历一个个Action对象并调用Action的apply方法。所以具体的更新操作是Action的apply方法完成的。除了apply方法还有一个reapply方法可以用来更新界面。他们的区别是apply方法会加载界面并更新界面,而reapply方法只会进行更新界面。
/**
* Equivalent to calling TextView.setText
*
* @param viewId The id of the view whose text should change
* @param text The new text for the view
*/
public void setTextViewText(int viewId, CharSequence text) {
setCharSequence(viewId, "setText", text);
}
/**
* Call a method taking one CharSequence on a view in the layout for this RemoteViews.
*
* @param viewId The id of the view on which to call the method.
* @param methodName The name of the method to call.
* @param value The value to pass to the method.
*/
public void setCharSequence(int viewId, String methodName, CharSequence value) {
addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
}
/**
* ReflectionAction的apply方法
*/
@Override
public 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);
}
}
/**
*RemoteViews的apply方法
*
*/
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result = inflateView(context, rvToApply, parent);
loadTransitionOverride(context, handler);
rvToApply.performApply(result, parent, handler);
return result;
}
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);
}
}
}
/**
* RemoteViews的reapply方法
*
*/
public void reapply(Context context, View v, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
// In the case that a view has this RemoteViews applied in one orientation, is persisted
// across orientation change, and has the RemoteViews re-applied in the new orientation,
// we throw an exception, since the layouts may be completely unrelated.
if (hasLandscapeAndPortraitLayouts()) {
if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
" that does not share the same root layout id.");
}
}
rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
}
总结
1.参考自《Android开发艺术探索》这本书。
2.关于窗口小部件的用法http://blog.csdn.net/liuqz2009/article/details/50922122