《Android开发艺术》读书笔记-RemoteView
RemoteView 在Android中的使用场景有两种: 通知栏和桌面小部件。《Android开发艺术》第5章重点分析了通过RemoteViews实现远程更新(通知栏和小部件)界面。
1 RemoteViews应用
通知栏
Notification相关类,做了更新,例子很简单,最新的API代码可以参考官方教程
关于RemoteViews更新界面的代码:
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
remoteViews.setTextViewText(R.id.msg, "chapter_5: " + sId);
remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1);
PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(this,
0, new Intent(this, DemoActivity_2.class), PendingIntent.FLAG_UPDATE_CURRENT);
remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent);
桌面小部件
桌面小部件通过AppWidgetProvider来实现的,它的本质是一个广播。AppWidgetProvider提供了几个主要的方法, onUpdate, onEnable, onDisable, onDeleted 以及 onReceive。 其中onReceive会根据广播的Action响应, 然后再调用其它方法。
与广播类似, 当桌面小部件接收到用户的交互信息,则会通过onReceive传递, 用户通过重写onReceive方法,并判断intent.getAction()是否需要做相应的处理。 如果是更新界面,就需要通过RemoveView实现:
@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();
}
}
2 PendingIntent
-
为什么使用PendingIntent
PendingIntent表示一种处于Pending状态的意图, 即是一种特定,等待,即将发生的意思。为什么要使用PendingIntent呢? 我在stackoverflow中看到一个更好理解的解释:
A PendingIntent is a token that you give to a foreign application (e.g. NotificationManager, AlarmManager, HomeScreen AppWidgetManager , or other 3rd party applications), which allows the foreign application to use your application's permissions to execute a predefined piece of code.
也就是说, 其它进程如果想要在你的app上做一些事情,如果还是传一个Intent,他们是没有执行权限的, 你必须给他们传PendingIntent, 他们才可以执行,因为PendingIntent包含了执行的权限。
所以, 我们看到上面通知的那段代码, PendingIntent的应用场景,就是给Remoteview设定一个点击的行为,打开DemoActivity_2这个activity。
--方法参数
PendingIntent支持3种特定意图: getActivity(), getService(),getBroadcast(), 分别是打开activity, service和broadcast。
getActivity(Context context, int requestCode, Intent intent, int flags)
requestCode: 表示发送方的请求码(多数情况为0)
flags: 表示PendingIntent类型的标志位,当多个PendingIntent匹配,通过这个标志位来决定是否替代或者重复。
如果Intent与requestCode相同, 即表示PendingIntent匹配。Intent匹配依赖于ComponentName 和 intent-filter
flags 表示的含义:
FLAG_ONE_SHOT: 表示当前的PendingIntent只能被使用一次, 然后就会被自动Cancel掉, 后续如果还有匹配的,send方法就会失效。
FLAG_NO_CREATE: 无法单独使用,在开发中很少见。(不会主动创建)
FLAG_CANCEL_CURRENT: 当前描述的PendingIntent如果存在, 那么它们都会被cancel, 然后系统会创建一个新的。
FLAG_UPDATE_CURRENT: 当前如果存在,那么都会被更新, 即Intent中的Extras会被替换成最新的。
RemoteView 内部机制
我们已经在上面的例子中看到了RemoteView主要是为了在远程界面中更新View而存在的, 下面我们看看它内部的机制:
- 可以看到一个现象:RemoteView没有提供findViewById方法, 因此无法直接访问里面的view元素,而必须通过一系列的set方法(例如 setTextViewText(int viewId, CharSequence text))
- 上述的两个例子NotificationManager/AppWidgetmanager分别通过Binder和NotificationManagerService/AppWidgetService进程通信, 所以通知栏和桌面部件的界面也是在SystemService中被加载, 从而构成了跨进程通信的场景。
- RemoteView实现了Parcelable接口,会通过Binder传递到SystemServer进程。
- 如果View的操作通过Binder实现的话,view的方法太多成本较高,而且大量的IPC操作会影响效率。解决方法:Android系统提供了Action的概念,Action封装了具体的操作, 然后传输到远程SystemServer进程中。
- RemoteViews内部有主要的方法(apply,reapply)来实现Action的操作。
apply 方法
RemoteView中的核心方法apply,用来加载布局,更新界面,下面是主要的代码:
public class RemoteViews implements Parcelable, Filter {
......
public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
RemoteViews rvToApply = getRemoteViewsToApply(context);
View result;
LayoutInflater inflater =(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
......
//加载布局
result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
rvToApply.performApply(result, parent, handler);
return result;
}
......
}
可以看到,上述代码通过LayoutInflater加载布局,然后在performApply()对所有mActions中的Action都执行apply()操作:
1, 通过findViewById找到对应的view;
2, 通过Reflect机制,执行View实现类里的方法;
RemoteView的意义
可以看出,RemoteView主要是提供了进程间View更新的一种高效快速的解决方案, 主要应用在Notification 和 AppWidgetProvider中, 但是RemoteView并不支持所有的View:
布局:FrameLayout、LinearLayout、RelativeLayout、GridLayout
组件:Button、ImageButton、ImageView、TextView、ListView、GridView、ViewStub等