1. RemoteViews 的概念
- RemoteViews 表示的是一个 View 结构,它可以在其他进程中(系统的 SystemServer 进程)显示。由于它在其他进程中显示,为了能够更新它的界面,RemoteViews 提供了一组基础的操作用于跨进程更新它的界面
- RemoteViews 在 Android 中的使用场景有两种:通知栏和桌面小部件
- 通知栏主要是通过 NotificationManager 的 notify 方法来实现的,除了默认效果,还可以另外定义布局
- 桌面小部件是通过 AppWidgetProvider 来实现的,AppWidgetProvider 继承自 BroadcastReceiver,所以本质上是一个广播
2. RemoteViews 在通知栏上的应用
使用系统默认的样式弹出一个通知:
1
2
3
4
5
6
7
8
9
10
11
12Notification 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);
notification.setLatestEventInfo(this, "chapter_5", "this is notification.", pendingIntent);
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(1, notification);为满足个性化需求,自定义通知,提供一个布局文件,通过 RemoteViews 加载布局文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20Notification notification = new Notification();
notification.icon = R.drawable.ic_launcher;
notification.tickerText = "hello world";
notification.when = System.currentTimeMillis();
notification.flags = Notification.FLAG_AUTO_CANCLE;
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 表示的是一种待定的 intent,这个 Intent 中所包含的意图必须由用户来触发
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(2, notification);
3. RemoteViews 在桌面小部件上的应用
桌面小部件的开发步骤
定义小部件界面
1
2
3
4
5
6
7
8
9
10
11
12
13// 在 res/layout/ 下新建一个 XML 文件,命名为 widget.xml,名称和内容可以自定义
<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>定义小部件配置信息
1
2
3
4
5
6
7
8
9// 在 res/xm/ 下新建 appwidget_provider_info.xml,名称自定义
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget"
android:minWidth="84dp"
android:minHeight="84dp"
android:updatePeriodMillis="86400000" >
</appwidget-provider>initialLayout
: 小工具所使用的初始化布局minWidth/minHeight
: 定义小工具的最小尺寸updatePeriodMillis
: 定义小工具的自动更新周期,单位为毫秒,每隔一个周期,小工具的自动更新就会触发
定义小部件的实现类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79// 实现类需要继承 AppWidgetProvider
public class MyAppWidgetProvider extends AppWidgetProvider {
public static final String TAG = "MyAppWidgetProvider";
public static final String CLICK_ACTION = "com.szy.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 arcbBitmap = 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 appWidgetManager
* @param appWidgetId
*/
private void onWidgetUpdate(Context context, AppWidgetManager appWidgetManager, 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);
appWidgetManager.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 来实现
- 桌面小部件不管是初始化界面还是后续的更新界面都必须使用 RemoteViews 来完成
在 AndroidManifest.xml 中声明小部件
1
2
3
4
5
6
7
8
9
10
11
12<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.szy.chapter_5.action.CLICK" />
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
</receiver>- 因为桌面小部件本质上是一个广播,因此必须要注册
- 第一个 action 用于识别小部件的单击行为
- 第二个 action 作为小部件的标识必须存在,这是系统的规范。如果不加,那么这个 receiver 就不是一个桌面小部件并且也无法出现在手机的小部件列表里
AppWidgetProvider 除了最常用的
onUpdate()
方法,还有其他几个方法:onEnabled()
、onDisabled()
、onDeleted()
和onReceive()
,这些方法会自动地被onReceive()
方法在合适的时间(根据不同的 Action)调用确切来说,当广播到来以后,AppWidgetProvider 会自动根据广播的 Action 通过
onReceive()
方法来自动分发广播,也就是调用上面几个方法。这几个方法的调用时机如下:onEnabled()
: 当该窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用onUpdate()
: 小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机由updatePeriodMillis
来指定,每个周期小部件都会自动更新一次onDeleted()
: 每删除一次桌面小部件就调用一次onDisabled()
: 当最后一个该类型的桌面小部件被删除时调用该方法onReceive()
: 广播的内置方法,用于分发具体的事件给其他方法
4. PendingIntent 概述
PendingIntent 的概念
- PendingIntent 表示一种处于 pending 状态的意图,而 pending 状态表示的是一种待定、等待、即将发生的意思
- PendingIntent 和 Intent 的区别:PendingIntent 是在将来的某个不确定的时刻发生,而 Intent 是立刻发生
- PendingIntent 典型的使用场景是给 RemoteViews 添加单击事件,并通过 send 和 cancel 方法来发送和取消特定的 Intent
PendingIntent 的主要方法
返回值 接口方法 static PendingIntent
getActivity(Context context, int requestCode, Intent intent, int flags)
: 获得一个 PendingIntent,该待定意图发生时,效果相当于Context.startActivity(Intent)
static PendingIntent
getService(Context context, int requestCode, Intent intent, int flags)
: 获得一个 PendingIntent,该待定意图发生时,效果相当于Contet.startService(Intent)
static PendingIntent
getBroadcast(Context context, int requestCode, Intent intent, int flags)
: 获得一个 PendingIntent,该待定意图发生时,效果相当于Context.sendBroadcast(Intent)
接口方法的参数含义
- requestCode 表示 PendingIntent 发送方的请求码,多数情况下设为 0 即可,另外 requestCode 会影响到 flags 的效果
- flags 常见的类型有:
FLAG_ONE_SHOT
、FLAG_NO_CREATE
、FLAG_CANCEL_CURRENT
和FLAG_UPDATE_CURRENT
PendingIntent 的匹配规则
- 如果两个 PendingIntent 它们内部的 Intent 相同并且 requestCode 也相同,那么这两个 PendingIntent 就是相同的
- Intent 的匹配规则:如果两个 Intent 的 ComponentName 和 intent-filter 都相同,那么这两个 Intent 就是相同的(Extras 不参与 Intent 的匹配过程)
flags 参数含义
FLAG_ONE_SHOT
:- 当前描述的 PendingIntent 只能被使用一次,然后它就会被自动 cancel,如果后续还有相同的 PendingIntent,那么它们的 send 方法就会调用失败
- 对于通知栏消息来说,如果采用此标记位,那么同类的通知只能使用一次,后续的通知单击后将无法打开
FLAG_NO_CREATE
:- 当前描述的 PendingIntent 不会主动创建,如果当前 PendingIntent 之前不存在,那么
getActivity()
、getService()
和getBroadcast()
方法会直接返回 null,即获取 PendingIntent 失败 - 这个标记位很少见,无法单独使用,日常开发中使用意义不大
- 当前描述的 PendingIntent 不会主动创建,如果当前 PendingIntent 之前不存在,那么
FLAG_CANCEL_CURRENT
:- 当前描述的 PendingIntent 如果已经存在,那么它们都会被 cancel,然后系统会创建一个新的 PendingIntent
- 对于通知栏消息来说,那些被 cancle 的消息单击后将无法打开
FLAG_UPDATE_CURRENT
:- 当前描述的 PendingIntent 如果已经存在,那么它们都会被更新,即它们的 Intent 中的 Extras 会被替换成最新的