Skip to content

Latest commit

 

History

History
245 lines (177 loc) · 9.63 KB

10. RemoteViews.md

File metadata and controls

245 lines (177 loc) · 9.63 KB
  1. RemoteViews ==

1. RemoteViews 原理

RemoteViews 在实际开发中,主要用在通知栏和桌面小部件的开发过程中。RemoteViews 在通知栏上的应用中,主要是通过 NotificationManagernotify 方法来实现的,它除了默认效果外,还可以自定义布局。

桌面小部件则是通过 AppWidgetProvider 来实现的,AppWidgetProvider 本质上是一个广播。通知栏和桌面小部件的开发过程中都会用到 RemoteViews它们在更新界面时无法像在 Activity 里面那样去直接更新 View,这是因为二者的界面都运行在其他进程中,确切的说,是系统的 SystemServer 进程

为了跨进程更新界面,RemoteViews 提供了一系列 set 方法,并且这些方法只是 View 全部方法的子集,另外 RemoteViews 中所支持的 View 类型也是有限的。


RemoteViews 在通知栏上的应用

    // 普通 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);

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

2.1 小部件界面

@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>

2.2 小部件配置信息

@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>

2.3 实现类

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

}

2.4 AndroidManifest.xml 配置

<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>

3. RemoteViews 工作过程

通知栏 和 桌面小部件分别由 NotificationManagerAppWidgetManager 管理。而 NotificationManagerAppWidgetManager 通过 Binder 分别和 SystemServer 进程中的 NotificationManagerService 以及 AppWidgetManager 进行 通信。由此可见,通知栏 和 桌面小部件中的布局文件实际上是在 NotificationManagerService 以及 AppWidgetManager 中被加载的,而它们运行在系统的 SystemServer 中,这就是 跨进程通信 的场景。


4. RemoteViews 工作原理

4.1 通过 Binder 传递到 SystemServer 进程

首先 RemoteViews 会通过 Binder 传递到 SystemServer 进程,这是因为 RemoteViews 实现了 Parcelable 接口,因此它可以 进程传输,系统会 根据 RemoteViews 中的包名等信息去得到该应用的资源


4.2 LayoutInflater 加载 RemoteViews

然后会 通过 LayoutInflater 去加载 RemoteViews 中的布局文件。在 SystemServer 进程中加载后的布局文件是一个普通的 View,只不过相对于我们的进程它是一个 RemoteViews


4.3 set 方法进行更新

接着 系统会对 View 执行一系列界面更新任务,这些任务就是之前通过 set 方法来提交的。set 方法对 View 所做的更新 并不是立刻执行的,在 RemoteViews 内部 会记录所有的更新操作,具体的执行时机要等到 RemoteViews 被加载以后才执行


4.4 RemoteViews 显示

通过以上步骤后,RemoteViews 就可以在 SystemServer 进程中显示了,这就是我们所看到的通知栏消息或者桌面小部件。当需要更新 RemoteViews 时,我们需要调用一系列 set 方法并通过 NotificationManagerServiceAppWidgetManager 来提交更新任务,具体的更新操作也是在 SystemServer 进程中完成的。