RemoteViews应用以及内部机制、原理(上)。

一、概述。

RemoteViews是一种远程View,它提供了一组基础的操作用于跨进程更新它的界面。

一般RemoteViews在android中的应用场景有两种:一种是通知栏,另一种是桌面小部件。

二、应用。

(1)、通知栏。

首先使用系统默认的样式创建一个通知,

        Intent intent = new Intent(this, NotificationActivity.class);
        PendingIntent pi = PendingIntent.getActivity(this,0,intent,0); //待定时刻发生的Intent
        NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        Notification notification = new NotificationCompat.Builder(this)
                .setContentTitle("This is context title") // 标题
                .setContentText("This is context text") // 内容
                .setWhen(System.currentTimeMillis()) //创建通知的时间
                .setSmallIcon(R.mipmap.ic_launcher) //状态栏图标
                .setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher)) //通知栏的图标
                .setContentIntent(pi) //单击跳转
                .setAutoCancel(true) // 点击后清除本身
//                .setDefaults(NotificationCompat.DEFAULT_ALL)
//                .setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(), R.drawable.zz)))
//                .setStyle(new NotificationCompat.BigTextStyle().bigText("zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"))

.build();

manager.notify( 1 ,notification) ; 上述代码会创建一个系统默认的通知栏,单击后会跳转至NotificationActivity,并且会清除本身。

接下来,我们需要新建一个布局文件,然后使用RemoteViews加载这个布局文件,并且将那Notification的RemoteViews设置为新建的RemoteViews,即可改变通知样式。

布局文件remote_view.xml

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
            android:id="@+id/image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:src="@drawable/back"
        />
            android:id="@+id/content_text"
        android:layout_width="match_parent"
        android:textSize="20dp"
        android:layout_height="100dp"
        android:text="This is a aircraft"
        />
创建样式为RemoteView的通知:

Intent intent = new Intent(this, NotificationActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0); //待定时刻发生的Intent
NotificationManager manager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
RemoteViews remoteViews = new RemoteViews(getPackageName(),R.layout.remote_views);
remoteViews.setTextViewText(R.id.content_text,"This is a airCraft, la la la !"); //设置TextView内容
remoteViews.setImageViewResource(R.id.image,R.drawable.back); // 设置ImageView 图片
Notification notification = new NotificationCompat.Builder(this)
        .setContentTitle("This is context title") // 标题
        .setCustomContentView(remoteViews)
        .setWhen(System.currentTimeMillis()) //创建通知的时间
        .setSmallIcon(R.mipmap.ic_launcher) //状态栏图标
        .setContentIntent(pi) //单击跳转
        .setAutoCancel(true) // 点击后取消通知
        .build();
manager.notify(2,notification);
上述需要注意的是RemoteViews的构造函数,参数1为包名,参数2为布局资源Id。

RemoteView无法像Activity那样直接更新View,为了跨进程更新界面,因此它提供了许多set方法。

setTextViewText中第一个参数为TextView的id,第二个参数为内容。

setImageViewResource中第一个参数也为ImageView的id,第二个参数即为要设置图片资源的id.


结果如下,底下为系统默认样式,

上边为RemoteViews样式。


(2)桌面小部件。

AppWidgetProvider是用于实现桌面小部件的类,本质是一个广播,继承自BroadcastReceiver。

本例实现一个桌面计数的小部件。

首先需要在layout下新建一个XML文件,命名widget.xml,内容自定义。

由于我们只需要计数,所以一个TextView即可。

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
                    android:id="@+id/high_low_wather"
            android:layout_gravity="center"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

其次需要定义小部件的配置信息。

新建文件夹res/xml,在此文件夹下新建weather.xml,添加内容如下:

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    android:minWidth="294dp"
    android:minHeight="72dp"
    android:updatePeriodMillis="86400000"
    android:initialLayout="@layout/widget"
    android:resizeMode="horizontal|vertical">
initialLayout 为初始化的布局。

minHeight和minWidth定义小工具的最小尺寸,

updatePeriodMillis自动更新周期,单位为毫秒,每隔一个周期,小工具自动更新就会触发。

新建一个服务用来每隔一秒发送一个广播(action为con.yang.widget.UPDATE_ALL)并且给NumProvider提供的数据加1。

public class NumUpdateService extends Service {
    private static final String TAG="WeatherUpdateService";

    // 更新 widget 的广播对应的action
    private final String ACTION_UPDATE_ALL = "com.yang.widget.UPDATE_ALL";
    // 周期性更新 widget 的周期
    private static final int UPDATE_TIME = 1000;
    // 周期性更新 widget 的线程
    private UpdateThread mUpdateThread;
    private Context mContext;
    private int temp = 0;

    @Override
    public void onCreate() {
        // 创建并开启线程UpdateThread
        mUpdateThread = new UpdateThread();
        mUpdateThread.start();
        mContext = this.getApplicationContext();
        super.onCreate();
    }

    @Override
    public void onDestroy(){
        // 中断线程,即结束线程。
        if (mUpdateThread != null) {
            mUpdateThread.interrupt();
        }

        super.onDestroy();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    /*
     * 服务开始时,即调用startService()时,onStartCommand()被执行。
     */
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.d(TAG, "onStartCommand");
        super.onStartCommand(intent, flags, startId);
        return START_STICKY;
    }

    private class UpdateThread extends Thread {

        @Override
        public void run() {
            super.run();
            try {
                while (true) {
                    Intent updateIntent=new Intent(ACTION_UPDATE_ALL);
                    updateIntent.putExtra("temp",temp);
                    temp++;
//                    updateIntent.putExtra("high_low",32);
                    mContext.sendBroadcast(updateIntent);
                    Thread.sleep(UPDATE_TIME);
                }
            } catch (InterruptedException e) {
                // 将 InterruptedException 定义在while循环之外,意味着抛出 InterruptedException 异常时,终止线程。
                e.printStackTrace();
            }
        }
    }
}

定义小部件的实现类NumProvider

public class NumProvider extends AppWidgetProvider {
    private static final String TAG = "WeatherProvider";

    private boolean DEBUG = false;
    // 启动ExampleAppWidgetService服务对应的action
    private final Intent EXAMPLE_SERVICE_INTENT =
            new Intent("android.appwidget.action.EXAMPLE_APP_WIDGET_SERVICE");
    // 更新 widget 的广播对应的action
    private final String ACTION_UPDATE_ALL = "com.yang.widget.UPDATE_ALL";
    // 保存 widget 的id的HashSet,每新建一个 widget 都会为该 widget 分配一个 id。
    private int appId;
    private AppWidgetManager mAppWidgetManager;
    private int mTemp;

    @Override
    public void onEnabled(Context context) {
        Log.d(TAG, "onEnabled");
        Intent _intent = new Intent(context, NumUpdateService.class);
        context.startService(_intent);
        super.onEnabled(context);
    }



    @Override
    public void onDisabled(Context context) {
        Log.d(TAG, "onDisabled");
        Intent _intent = new Intent(context, NumUpdateService.class);
        context.stopService(_intent);
        super.onDisabled(context);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        super.onUpdate(context, appWidgetManager, appWidgetIds);
        appId = appWidgetIds[0];
    }

    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public void onReceive(Context context, Intent intent) {
        final String action = intent.getAction();
        ComponentName thisWidget = new ComponentName(context,NumProvider.class);//定义容器
        int temp = intent.getIntExtra("temp",0);
        AppWidgetManager appWidgetManager = AppWidgetManager
                .getInstance(context);
        if (ACTION_UPDATE_ALL.equals(action)) {
            // “更新”广播
            Log.d(TAG, "update");
            mTemp = temp;
            RemoteViews remoteView = new RemoteViews(context.getPackageName(), R.layout.widget);
            remoteView.setTextViewText(R.id.high_low_wather,String.valueOf(mTemp));
            remoteView.setTextViewTextSize(R.id.high_low_wather,TypedValue.COMPLEX_UNIT_DIP,150);
            appWidgetManager.updateAppWidget(thisWidget, remoteView);
        }
        super.onReceive(context, intent);
    }
}
在配置文件AndroidManifest.xml声明小部件:

android:name=".NumProvider" >
    
        android:name="android.appwidget.action.APPWIDGET_UPDATE" />
        android:name="com.yang.widget.UPDATE_ALL"/>
    
    android:name="android.appwidget.provider"
        android:resource="@xml/weather" />

当广播到来时,AppWidgetProvider会根据广播的action通过onReceive方法进行自动分发广播。

通过新建一个RemoteViews,通过set方法设置TextView内容以及字体大小,然后通过AppWidgetManager的updateAppWidget方法更新界面,方法中第一个参数为容器,第二个参数为RemoteViews的实例。

结果如图所示


AppWidgetProvider包含如下几个方法,

onEnable:

当窗口小部件第一次添加到桌面时调用该方法,可添加多次但只在第一次调用。

onUpdate:

小部件被添加时或者每次小部件更新时都会调用一次该方法,小部件的更新时机由updatePeriodMillis来指定,每个周期小部件都会自动更新一次。

onDeleted:

每删除一次桌面小部件就调用一次。

onDisabled:

当最后一个该类型的桌面小部件被删除时调用该方法,注意是最后一个。

onReceive:

广播的内置方法,用来分发具体事件给其它方法。


这两个例子在我的github上,需要的可以直接下载


通知: https://github.com/wangchenyangzz/NotificationTest

小部件:  https://github.com/wangchenyangzz/AppWidget


RemoteViews的应用就先说到这,下篇会分析RemoteViews的内部机制及原理。


你可能感兴趣的:(RemoteViews应用以及内部机制、原理(上)。)