- RemoteViews ==
RemoteViews 在实际开发中,主要用在通知栏和桌面小部件的开发过程中。RemoteViews 在通知栏上的应用中,主要是通过 NotificationManager 的 notify
方法来实现的,它除了默认效果外,还可以自定义布局。
桌面小部件则是通过 AppWidgetProvider 来实现的,AppWidgetProvider 本质上是一个广播。通知栏和桌面小部件的开发过程中都会用到 RemoteViews,它们在更新界面时无法像在 Activity 里面那样去直接更新 View,这是因为二者的界面都运行在其他进程中,确切的说,是系统的 SystemServer 进程。
为了跨进程更新界面,RemoteViews 提供了一系列 set
方法,并且这些方法只是 View 全部方法的子集,另外 RemoteViews 中所支持的 View 类型也是有限的。
// 普通 Notification 的创建
Notification notification = new NotificationCompat.Builder(this)
.setLargeIcon(BitmapUtils.drawableToBitmap(
ResourcesCompat.getDrawable(this.getResources(), R.drawable.ic_camnter,
this.getTheme())))
.setSmallIcon(R.drawable.ic_send_light_small)
.setTicker("Save you from anything")
.setWhen(System.currentTimeMillis()).build();
notification.flags = Notification.FLAG_AUTO_CANCEL;
// 跳转到 RemoteViewsActivity
Intent intent = new Intent(this, RemoteViewsActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivities(this, 0, new Intent[] { intent },
PendingIntent.FLAG_UPDATE_CURRENT);
// 自定义 Notification 的布局
RemoteViews remoteViews = new RemoteViews(this.getPackageName(),
R.layout.notification_remote_normal);
// 特定的 set 方法去修改规定的 View
remoteViews.setImageViewResource(R.id.remote_icon, R.drawable.ic_camnter);
remoteViews.setTextViewText(R.id.remote_text, "Save you from anything");
// Notification 设置上 RemoteViews 和 PendingIntent
notification.contentView = remoteViews;
notification.contentIntent = pendingIntent;
NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
managerCompat.notify(2, notification);
@layout/appwidget.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/appwidget_image"
android:layout_width="66dp"
android:layout_height="66dp"
android:src="@drawable/ic_camnter"/>
</LinearLayout>
@xml/appwidget_provider_info.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:initialLayout="@layout/appwidget"
android:updatePeriodMillis="86400000"
android:orientation="vertical">
</appwidget-provider>
public class RemoteViewsAppWidgetProvider extends AppWidgetProvider {
public static final String TAG = "RemoteViewsAppWidgetProvider";
public static final String ACTION = "com.camnter.newlife.widget.RemoteViewsAppWidgetProvider";
public RemoteViewsAppWidgetProvider() {
super();
}
@SuppressLint("LongLogTag") @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(ACTION)) {
Toast.makeText(context,
"RemoteViewsAppWidgetProvider.Action = com.camnter.newlife.widget.RemoteViewsAppWidgetProvider",
Toast.LENGTH_SHORT).show();
new Thread(new Runnable() {
@Override public void run() {
Bitmap srcBitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.ic_camnter);
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.appwidget);
remoteViews.setImageViewBitmap(R.id.appwidget_image,
rotateBitmap(srcBitmap, degree));
Intent clickIntent = new Intent();
clickIntent.setAction(ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
clickIntent, 0);
remoteViews.setOnClickPendingIntent(R.id.appwidget_image, pendingIntent);
appWidgetManager.updateAppWidget(
new ComponentName(context, RemoteViewsAppWidgetProvider.class),
remoteViews);
SystemClock.sleep(60);
}
}
});
}
}
/**
* 每次窗口小部件被点击更新都调用一次该方法
*/
@SuppressLint("LongLogTag") @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 appWidgetId : appWidgetIds) {
this.onWidgetUpdate(context, appWidgetManager, appWidgetId);
}
}
/**
* 窗口小部件更新
*/
@SuppressLint("LongLogTag") private void onWidgetUpdate(Context context,
AppWidgetManager appWidgetManger,
int appWidgetId) {
Log.i(TAG, "appWidgetId = " + appWidgetId);
RemoteViews remoteViews = new RemoteViews(context.getPackageName(),
R.layout.appwidget);
// "窗口小部件"点击事件发送的Intent广播
Intent intentClick = new Intent();
intentClick.setAction(ACTION);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
intentClick, 0);
remoteViews.setOnClickPendingIntent(R.id.appwidget_image, pendingIntent);
appWidgetManger.updateAppWidget(appWidgetId, remoteViews);
}
private Bitmap rotateBitmap(Bitmap srcBitmap, float degree) {
Matrix matrix = new Matrix();
matrix.reset();
matrix.setRotate(degree);
return Bitmap.createBitmap(srcBitmap, 0, 0,
srcBitmap.getWidth(), srcBitmap.getHeight(), matrix, true);
}
}
<receiver android:name=".widget.RemoteViewsAppWidgetProvider">
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/appwidget_provider_info"/>
<intent-filter>
<action android:name="com.camnter.newlife.widget.RemoteViewsAppWidgetProvider"/>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
</intent-filter>
</receiver>
通知栏 和 桌面小部件分别由 NotificationManager 和 AppWidgetManager 管理。而 NotificationManager 和 AppWidgetManager 通过 Binder 分别和 SystemServer 进程中的 NotificationManagerService 以及 AppWidgetManager 进行 通信。由此可见,通知栏 和 桌面小部件中的布局文件实际上是在 NotificationManagerService 以及 AppWidgetManager 中被加载的,而它们运行在系统的 SystemServer 中,这就是 跨进程通信 的场景。
首先 RemoteViews 会通过 Binder 传递到 SystemServer 进程,这是因为 RemoteViews 实现了 Parcelable 接口,因此它可以 进程传输,系统会 根据 RemoteViews 中的包名等信息去得到该应用的资源。
然后会 通过 LayoutInflater 去加载 RemoteViews 中的布局文件。在 SystemServer 进程中加载后的布局文件是一个普通的 View,只不过相对于我们的进程它是一个 RemoteViews。
接着 系统会对 View 执行一系列界面更新任务,这些任务就是之前通过 set 方法来提交的。set 方法对 View 所做的更新 并不是立刻执行的,在 RemoteViews 内部 会记录所有的更新操作,具体的执行时机要等到 RemoteViews 被加载以后才执行。
通过以上步骤后,RemoteViews 就可以在 SystemServer 进程中显示了,这就是我们所看到的通知栏消息或者桌面小部件。当需要更新 RemoteViews 时,我们需要调用一系列 set 方法并通过 NotificationManagerService 和 AppWidgetManager 来提交更新任务,具体的更新操作也是在 SystemServer 进程中完成的。