《Android开发艺术探索》笔记(五)——理解RemoteViews

《Android开发艺术探索》笔记(五)——理解RemoteViews

作者:cmad 时间:2016-05-16 分类:Android 评论:0条 浏览:617



  RemoteViews是一种远程View,RemoteViews表示一个View结果,它可以在其他进程中显示。

  RemoteViews在实际开发中,主要用于通知栏和桌面小部件的开发过程中。它们在更新界面时无法像在Activity中那样直接的去更新View,因为RemoteViews跟我们的应用是运行在不同的进程中,确切来说它运行在SystemServer进程。

  我们先来看两个RemoteViews的应用,也就是上面说的在通知栏和桌面小部件上的应用。

1.RemoteViews在通知栏上的应用

  使用系统默认方式弹出通知栏的实现我们这里就不做介绍了,这里主要看使用RemoteViews自定义view实现通知栏的实现。

Notification notification = new Notification();
notification.icon = R.drawable.ic_launcher;
notification.tickerText = "hello world";
notification.when = System.currentTimeMillis();
notification.flags = Notification.FLAG_AUTO_CANCEL;
Intent intent = new Intent(this, DemoActivity_1.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,
0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification);
remoteViews.setTextViewText(R.id.msg, "chapter_5");
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);
notification.contentView = remoteViews;
notification.contentIntent = pendingIntent;
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(sId, notification);

  上面我们就创建了一个通过RemoteViews实现自定义view的通知栏效果,通过包名和资源文件layoutId创建一个RemoteViews对象,然后调用RemoteViews的set方法设置界面显示效果,比如这里的setTextViewText和setImageViewResource设置TextView和ImageView的显示内容,第一个参数是控件在资源文件中的id,第二个参数是显示内容。setOnClickPendingIntent是设置控件点击事件启动对应的界面。

  R.layout.layout_notification的内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/light_green"
android:padding="10dp"
android:gravity="center"
android:orientation="horizontal" >

<ImageView
android:id="@+id/icon"
android:background="#000000"
android:layout_width="40dp"
android:scaleType="centerInside"
android:layout_height="40dp" />

<LinearLayout
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right"
android:orientation="vertical" >

<TextView
android:id="@+id/msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:textColor="@android:color/white"
android:visibility="visible"
android:text="TextView" />

<TextView
android:id="@+id/open_activity2"
android:padding="2dp"
android:background="@drawable/common_bkg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/white"
android:text="open DemoActivity_2" />
</LinearLayout>

</LinearLayout>

  布局很简单就不多说了。

2.RemoteViews在桌面小部件上的应用

  AppWidgetProvider是Android中提供实现桌面小部件的类,其本质是一个广播即BroadcastReceiver,它继承于BroadcastReceiver。

  下面来看一下桌面小部件的实现步骤。

1)定义小部件界面

  在layout目录下创建widget.xml,当然名称可以自己定,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<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/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon1" />


</LinearLayout>

  这里很简单,我们就放了一个ImageView控件。

2)定义小部件配置信息

  在res/xml目录下创建appwidget_provider_info.xml,名称和目录可以自定义,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget"
android:minHeight="84dp"
android:minWidth="84dp"
android:updatePeriodMillis="86400000" >


</appwidget-provider>

  简单的介绍一下上面内容中的几个属性的意义,initialLayout就是指小部件显示的界面布局也就是我们前面第一步创建的widget.xml,minHeight和minWidth是定义小部件的最小尺寸,updatePeriodMillis是定义小部件的自动更新周期单位为ms,每隔一个周期小部件的自动更新就会被触发。

3)定义小部件的实现类

public class MyAppWidgetProvider extends AppWidgetProvider {
public static final String TAG = "MyAppWidgetProvider";
public static final String CLICK_ACTION = "com.ryg.chapter_5.action.CLICK";

public MyAppWidgetProvider() {
super();
}

@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();
}
}

/**
* 每次窗口小部件被点击更新都调用一次该方法
*/

@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds)
{
super.onUpdate(context, appWidgetManager, appWidgetIds);
Log.i(TAG, "onUpdate");

final int counter = appWidgetIds.length;
Log.i(TAG, "counter = " + counter);
for (int i = 0; i < counter; i++) {
int appWidgetId = appWidgetIds[i];
onWidgetUpdate(context, appWidgetManager, appWidgetId);
}

}

/**
* 窗口小部件更新
*
* @param context
* @param appWidgeManger
* @param appWidgetId
*/

private void onWidgetUpdate(Context context,
AppWidgetManager appWidgeManger, int appWidgetId)
{

Log.i(TAG, "appWidgetId = " + appWidgetId);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.widget);

// "窗口小部件"点击事件发送的Intent广播
Intent intentClick = new Intent();
intentClick.setAction(CLICK_ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
intentClick, 0);
remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent);
appWidgeManger.updateAppWidget(appWidgetId, remoteViews);
}

private Bitmap rotateBitmap(Context context, Bitmap srcbBitmap, float degree) {
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(degree);
Bitmap tmpBitmap = Bitmap.createBitmap(srcbBitmap, 0, 0,
srcbBitmap.getWidth(), srcbBitmap.getHeight(), matrix, true);
return tmpBitmap;
}
}

  上面的代码实现了一个简单的桌面小部件,在小部件上显示一张图片,单击它后,这个图片就会旋转一周。当小部件添加到桌面后,会通过RemoteViews来加载布局文件,而当小部件被单击后的选择效果则是通过不断更新RemoteViews来实现的。

4)在AndroidManifest.xml中声明小部件

  前面说了AppWidgetProvider其本质是一个广播,所以注册跟广播一样:

<receiver android:name=".MyAppWidgetProvider" >
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_info" >

</meta-data>

<intent-filter>
<action android:name="com.ryg.chapter_5.action.CLICK" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
</receiver>

  上面代码里meta-data的name是固定写法,resource就是我们第二步创建的小部件配置文件。在intent-filter中有两个action,其中第一个用于识别小部件的单击行为,而第二个action则作为小部件的标识而必须存在的,这是系统规范,如果不加,那么这个小部件就无法出现在手机的小部件列表里。

PendingIntent概述

  在前面RemoteViews在通知栏和桌面小部件中我们都用到了PendingIntent,那么PendingIntent到底是什么东西呢?它跟Intent的区别又是什么呢?

  Pending表示一种待定、等待、即将发生的状态,那么顾名思义PendingIntent就是一种在某种特定时刻触发的Intent。PendingIntent跟Intent的区别在于,PendingIntent是在将来的某个不确定的时刻发生,而Intent是立刻发生。

  PendingIntent支持三种待定意图:启动Activity、启动Service和发送广播,对应三个接口方法:

  • getActivity(Context context, int requestCode, Intent intent, int flags);

    对应Context.startActivity(Intent)
  • getService(Context context, int requestCode, Intent intent, int flags);

    对应Context.startService(Intent)
  • getBroadcast(Context context, int requestCode, Intent intent, int flags);

    对应Context.sendBroadcast(Intent)

三个方法的参数相同,第一个参数跟第三个参数好理解,第二个参数requestCode表示PendingIntent发送方的请求码。第四个参数flags有四个值:FLAG_ONE_SHOT、FLAG_NO_CREATE、FLAG_CANCEL_CURRENT、FLAG_UPDATE_CURRENT。

  介绍这四个值之前我们先介绍一下PendingIntent的匹配规则,即在什么情况下认定两个PendingIntent是相同的。两个PendingIntent内容的intent相同并且requestCode相同,那么这两个PendingIntent相同。而两个Intent的ComponentName和intent-filter相同则两个Intent相同,跟Intent的Extras无关,即使Extras不同只要ComponentName跟intent-filter相同则两个intent相同。为什么要介绍这个呢?因为flags的值跟PendingIntent的匹配有关。

  • FLAG_ONE_SHOT

    当前PendingIntent只能被使用一次,然后它就会被自定cancel,如果后续还有相同的PendingIntent,那么它们的send方法就会调用失败,也就是单击失效。
  • FLAG_NO_CREATE

    当前PendingIntent不会主动创建,如果当前PendingIntent之前不存在,那么getActivity、getService、getBroadcast方法会直接返回null,即获取PendingIntent失败。这个标记位很少见,它无法单独使用。
  • FLAG_CANCEL_CURRENT

    当前描述的PendingIntent如果已经存在,那么它们都会被cancel,然后系统会创建一个新的PendingIntent。对于通知栏来说如果已经存在相同的PendingIntent的通知栏,那么之前的相同的PendingIntent的通知栏都无法打开,只有最新的能被打开。
  • FLAG_UPDATE_CURRENT

    当前描述的PendingIntent如果已经存在,那么它们都会被更新,即它们的Intent中的Extras会被替换成最新的。

RemoteViews的内部机制

  我们来看一下RemoteViews的构造函数public RemoteViews(String packageName, int layoutId)根据包名和布局文件id来初始化RemoteViews。

  RemoteViews目前不支持所有的View,只支持部分View,如下:

  • Layout

    FrameLayout、LinearLayout、RelativeLayout、GridLayout
  • View

    AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewStub。

以上所描述的是RemoteViews所支持的所有View类型,RemoteViews不支持它们的子类以及其他View类型,包括我们的自定义View。

  RemoteViews因为是在远程进程中进行显示,因此无法通过findViewById的方法来访问里面的View进行更新,而必须通过RemoteViews提供的一系列的set方法来完成比如我们前面用到的setTextViewText、setImageViewResource、setOnClickPendingIntent等。

  每调用一次set方法,RemoteViews中就添加一个Action,Action封装对应的View操作,当我们通过NotificationManager或者AppWidgetManager来更新view时,这些Action就会传输到远程进程并在远程进程中依次执行。


相关推荐
更多

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>