RemoteViews的作用和工作原理

                           RemoteViews简介

RemoteViews,根据字面意思应该是一种远程的View,其实RemoteView表示的是一个View结构,它可以在其他进程中显示,由于它在其他进程中显示,为了能够更新它的界面,RemoteViews提供了一组基础的操作用于跨进程更新它的界面。

                          RemoteViews的作用

通过上面的简介,我们也大致了解RemoteViews主要用于实现跨进程更新界面,在实际开发中,RemoteViews主要用于通知栏和桌面小部件的开发。接下来,我们就以一些简单的案例来走进RemoteViews。

                        RemoteViews的简单实用

首先,我们看一下RemoteView在通知栏上的应用。

image.png

这里使用Notification简单实现一个通知栏弹窗,关于Notification的使用不是本文的重点,这里便不再详述,读者感兴趣可自行了解。
接下来我们尝试自定义通知栏,我们需要提供提供一个布局文件,然后使用RemoteViews加载此文件,就可以达到实现自定义通知的效果。
image.png
这里R.layout.remote布局文件中简单放置了一个imageview以及textview,需要注意的是,这里不可以直接访问布局文件中的view,需要通过remoteViews提供的方法来更新view中的内容。

接下来,我们再来了解一下RemoteViews在桌面小部件上的应用。
再实现桌面小部件之前,我们需要先了解下AppWidgetProvider,它是Android中提供的用于实现桌面小部件的类,其实本质就是一个广播。接下来我们简单地使用RemoteViews实现桌面小部件的开发。

1. 定义小部件界面

新建XML文件,然后自定义里面的布局,我这里命名为widget.xml,里面简单放置了一个ImageView。

image.png

2. 定义小部件的配置信息

在res/xml下新建app_provider_info.xml,添加内容如下:
image.png

其中,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>

gif5新文件.gif

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_SHOTPendingIntent只被使用一次,然后被自动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对象就会传输到远程进程中并依次执行。

20180804140225692.png

注意,当我们调用RemoteViews的set方法时,并不会立刻更新它们的界面,必须通过NotificationManager的notify方法以及AppWidgetManager的updateAppWidget方法才会更新他们的界面。

参考文献

Android开发艺术探索第5章 《理解RemoteViews》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值