爱上AndroidAndroid开发学习Android知识

《Android开发艺术》读书笔记-RemoteView

2016-07-28  本文已影响1935人  sunbinqiang

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

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而存在的, 下面我们看看它内部的机制:

  1. 可以看到一个现象:RemoteView没有提供findViewById方法, 因此无法直接访问里面的view元素,而必须通过一系列的set方法(例如 setTextViewText(int viewId, CharSequence text))
  2. 上述的两个例子NotificationManager/AppWidgetmanager分别通过Binder和NotificationManagerService/AppWidgetService进程通信, 所以通知栏和桌面部件的界面也是在SystemService中被加载, 从而构成了跨进程通信的场景。
  3. RemoteView实现了Parcelable接口,会通过Binder传递到SystemServer进程。
  4. 如果View的操作通过Binder实现的话,view的方法太多成本较高,而且大量的IPC操作会影响效率。解决方法:Android系统提供了Action的概念,Action封装了具体的操作, 然后传输到远程SystemServer进程中。
  5. 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等

上一篇 下一篇

猜你喜欢

热点阅读