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

来源:互联网 发布:软件过程管理 pdf 编辑:程序博客网 时间:2024/04/20 11:28

一、概述。

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"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="horizontal"    android:layout_width="match_parent"    android:layout_height="match_parent">    <ImageView        android:id="@+id/image"        android:layout_width="100dp"        android:layout_height="100dp"        android:src="@drawable/back"        />    <TextView        android:id="@+id/content_text"        android:layout_width="match_parent"        android:textSize="20dp"        android:layout_height="100dp"        android:text="This is a aircraft"        /></LinearLayout>
创建样式为RemoteView的通知:

Intent intent = new Intent(this, NotificationActivity.class);PendingIntent pi = PendingIntent.getActivity(this,0,intent,0); //待定时刻发生的IntentNotificationManager 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"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="match_parent"    android:layout_height="match_parent">        <TextView            android:id="@+id/high_low_wather"            android:layout_gravity="center"            android:layout_width="match_parent"            android:layout_height="match_parent" /></LinearLayout>

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

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

<?xml version="1.0" encoding="utf-8"?><appwidget-provider 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"></appwidget-provider>
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声明小部件:

<receiver android:name=".NumProvider" >    <intent-filter>        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />        <action android:name="com.yang.widget.UPDATE_ALL"/>    </intent-filter>    <meta-data android:name="android.appwidget.provider"        android:resource="@xml/weather" /></receiver>

当广播到来时,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的内部机制及原理。


原创粉丝点击