• 【Android


      RemoteViews,顾名思义,就是远程的View,也就是可以运行在其他进程中的View。RemoteViews常用在通知和桌面小组件中。

    一、RemoteViews应用到通知

      首先来介绍一下系统自带的通知(Notification)的使用。Notification的使用有两种方法,分别是Notification直接创建的方式和使用Notification.Builder创建者模式创建的方式。

      先来看一下使用Notification直接创建的方式的代码:

    Notification notification = new Notification();
    notification.icon = R.mipmap.ic_launcher; // 小图标
    notification.largeIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher_round); // 大图标
    notification.defaults = Notification.DEFAULT_ALL; // 设置默认的提示音、振动方式、灯光等
    notification.category = "Category";
    notification.when = System.currentTimeMillis(); // 设置通知发送的时间戳
    notification.tickerText = "Ticker Text"; // 设置通知首次弹出时,状态栏上显示的文本
    notification.flags = Notification.FLAG_AUTO_CANCEL; // 点击通知后通知在通知栏上消失
    notification.contentIntent = PendingIntent.getActivity(MainActivity.this, 0x001,
            new Intent(MainActivity.this, TargetActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); // 设置通知的点击事件
    ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(1, notification); // 发送系统通知

      通过上面的代码,就可以简单地发送一条通知到通知栏中。使用这种方式不需要有API版本的限制,但可以进行的操作比较少。

      下面来看一下使用Notification.Builder创建者模式创建通知的代码:

    PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0x001,
            new Intent(MainActivity.this, TargetActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
    Notification.Builder nb = new Notification.Builder(MainActivity.this)
            .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.mipmap.ic_launcher_round)) // 大图标
            .setSmallIcon(R.mipmap.ic_launcher) // 小图标
            .setContentText("Content Text") // 内容
            .setSubText("Sub Text") // 在通知中,APP名称的副标题
            .setContentTitle("Content Title") // 标题
            .setTicker("Ticker") // 设置通知首次弹出时,状态栏上显示的文本
            .setWhen(System.currentTimeMillis()) // 设置通知发送的时间戳
            .setAutoCancel(true) // 点击通知后通知在通知栏上消失
            .setDefaults(Notification.DEFAULT_ALL) // 设置默认的提示音、振动方式、灯光等
            .setContentIntent(pi); // 设置通知的点击事件
    ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(1, nb.build()); // build()方法需要的最低API为16

      使用这种方式,需要注意对项目的API版本进行一定的控制,上面这段代码需要的API版本最低是16。

      上面两种方式都是发送系统自带的通知。如果我们需要自定义通知的样式,就需要使用到 RemoteViews 了。RemoteViews给我们提供了一种可以在其他进程中生成View并进行更新的机制,但是它可以控制和操作的View有一定的限制,具体如下:

    Layout:
        FrameLayout、LinearLayout、RelativeLayout、GridLayout
    View:
        AnalogClock、Button、Chronometer、ImageButton、ImageView、ProgressBar、TextView、ViewFlipper、ListView、GridView、StackView、AdapterViewFlipper、ViewStub

      使用RemoteViews发送一个自定义系统通知的代码如下:

    PendingIntent pi = PendingIntent.getActivity(MainActivity.this, 0x001,
            new Intent(MainActivity.this, TargetActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
    RemoteViews remoteView = new RemoteViews(getPackageName(), R.layout.remoteview_main);
    remoteView.setTextViewText(R.id.remoteview_main_title, "Title");
    remoteView.setTextViewText(R.id.remoteview_main_content, "ContentContentContent");
    remoteView.setImageViewResource(R.id.remoteview_main_icon, R.mipmap.ic_launcher_round);
    remoteView.setOnClickPendingIntent(R.id.remoteview_main_view, pi);
    Notification.Builder nb = new Notification.Builder(MainActivity.this)
            .setSmallIcon(R.mipmap.ic_launcher) // 小图标
            .setCustomContentView(remoteView) // 设置自定义的RemoteView,需要API最低为24
            .setWhen(System.currentTimeMillis()) // 设置通知发送的时间戳
            .setAutoCancel(true) // 点击通知后通知在通知栏上消失
            .setDefaults(Notification.DEFAULT_ALL); // 设置默认的提示音、振动方式、灯光等
    ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(1, nb.build()); // build()方法需要的最低API为16

      可以看到,我们在自定义的通知布局中设置了TextView和ImageView,并在代码中动态地更新了其显示的内容。

    二、RemoteViews应用到桌面小组件

      RemoteViews也可以应用到桌面小组件中。这里我们通过一个例子来了解RemoteViews应用到桌面小组件的步骤,它总共分为五步,分别是:设置桌面小组件的布局、编写桌面小组件的配置文件、编写桌面小组件更新的Service、编写桌面小组件的控制类AppWidgetProvider、配置配置文件。

      我们通过下面这个例子来介绍RemoteViews在桌面小组件中的应用。在这个例子中,我们向系统中添加一个小组件,在这个小组件中显示当前的日期和时间。

      首先,设置桌面小组件的布局,具体代码如下:

    <?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">
    
        <TextView
            android:id="@+id/widget_main_tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@android:color/white"
            android:textSize="22.0sp"
            android:textStyle="bold" />
    
    </LinearLayout>

      然后,编写桌面小组件的配置文件,具体步骤是:在项目res文件夹下新建一个xml文件夹,在xml文件夹中创建一个XML文件,具体代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
        android:initialLayout="@layout/widget_main"
        android:minHeight="100.0dip"
        android:minWidth="150.0dip"
        android:updatePeriodMillis="8640000">
    
    </appwidget-provider>

      这个文件中的各个参数的解释如下:

    initialLayout:桌面小组件的布局XML文件
    minHeight:桌面小组件的最小显示高度
    minWidth:桌面小组件的最小显示宽度
    updatePeriodMillis:桌面小组件的更新周期。这个周期最短是30分钟

      然后,编写一个Service,在这个Service中动态地获取到当前的时间并更新到桌面小组件中,代码如下:

    import android.app.Service;
    import android.appwidget.AppWidgetManager;
    import android.content.ComponentName;
    import android.content.Intent;
    import android.os.IBinder;
    import android.support.annotation.Nullable;
    import android.widget.RemoteViews;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    /**
     * 定时器Service
     */
    public class TimerService extends Service {
        private Timer timer;
        private SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    
        @Override
        public void onCreate() {
            super.onCreate();
            timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    updateViews();
                }
            }, 0, 1000);
        }
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    
        private void updateViews() {
            String time = formatter.format(new Date());
            RemoteViews remoteView = new RemoteViews(getPackageName(), R.layout.widget_main);
            remoteView.setTextViewText(R.id.widget_main_tv_time, time);
            AppWidgetManager manager = AppWidgetManager.getInstance(getApplicationContext());
            ComponentName componentName = new ComponentName(getApplicationContext(), WidgetProvider.class);
            manager.updateAppWidget(componentName, remoteView);
        }
    
        @Override
        public void onDestroy() {
            super.onDestroy();
            timer = null;
        }
    }

      然后,编写一个类继承自AppWidgetProvier,用来统一管理项目中的小组件,代码如下:

    import android.appwidget.AppWidgetManager;
    import android.appwidget.AppWidgetProvider;
    import android.content.Context;
    import android.content.Intent;
    
    /**
     * AppWidgetProvider的子类,相当于一个广播
     */
    public class WidgetProvider extends AppWidgetProvider {
        /**
         * 当小组件被添加到屏幕上时回调
         */
        @Override
        public void onEnabled(Context context) {
            super.onEnabled(context);
            context.startService(new Intent(context, TimerService.class));
        }
    
        /**
         * 当小组件被刷新时回调
         */
        @Override
        public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
            super.onUpdate(context, appWidgetManager, appWidgetIds);
        }
        /**
         * 当widget小组件从屏幕移除时回调
         */
        @Override
        public void onDeleted(Context context, int[] appWidgetIds) {
            super.onDeleted(context, appWidgetIds);
        }
    
        /**
         * 当最后一个小组件被从屏幕中移除时回调
         */
        @Override
        public void onDisabled(Context context) {
            super.onDisabled(context);
            context.stopService(new Intent(context, TimerService.class));
        }
    }

      最后,在Manifest文件中配置刚刚编写的Service和BroadcastReceiver(AppWidgetProvider相当于一个广播),代码如下:

    <service android:name=".TimerService" />
    <receiver android:name=".WidgetProvider">
        <intent-filter>
            <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        </intent-filter>
        <meta-data
            android:name="android.appwidget.provider"
            android:resource="@xml/widget" />
    </receiver>

      这里需要注意,<intent-filter>标签中的action的name和<meta-data>标签中的name的值是固定的,reousrce代表的是第二步中配置文件的位置。

      编写完上述代码之后,运行结果如下图所示:

    三、RemoteViews原理

      我们的通知和桌面小组件分别是由NotificationManager和AppWidgetManager管理的,而NotificationManager和AppWidgetManager又通过Binder分别和SystemServer进程中的NotificationManagerServer和AppWidgetService进行通信,这就构成了跨进程通信的场景。

      RemoteViews实现了Parcelable接口,因此它可以在进程间进行传输。

      首先,RemoteViews通过Binder传递到SystemServer进程中,系统会根据RemoteViews提供的包名等信息,去项目包中找到RemoteViews显示的布局等资源,然后通过LayoutInflator去加载RemoteViews中的布局文件,接着,系统会对RemoteViews进行一系列的更新操作,这些操作都是通过RemoteViews对象的set方法进行的,这些更新操作并不是立即执行的,而是在RemoteViews被加载完成之后才执行的,具体流程是:我们调用了set方法后,通过NotificationManager和AppWidgetManager来提交更新任务,然后在SystemServer进程中进行具体的更新操作。

  • 相关阅读:
    位运算操作
    C 动态分配内存
    数据查询语言 DQL
    数据操纵语言 ,DML, 增删改
    Convert Sorted List to Binary Search Tree
    Longest Consecutive Sequence
    Binary Tree Postorder Traversal
    Triangle
    4Sum
    3Sum Closest
  • 原文地址:https://www.cnblogs.com/itgungnir/p/6923949.html
Copyright © 2020-2023  润新知