第五章理解RemoteViews(Android开发艺术探索)
2017-03-13 18:03
507 查看
5.1、remoteViews的应用(主要用于通知栏和桌面小部件)
通知栏和桌面小部件不能直接去更新view,因为2者的页面都运行在其他进程中,确切来说是系统的SystemServer进程,为了跨进程更新页面,RemoteViews提供了一系列的set方法进行更新。
5.1.1、在通知栏的应用:
通知栏除了默认的效果还可以自定义布局,下面分别说明这2种情况:
默认的布局:
自定义的布局(使用了remoteViews来加载):
remoteViews使用起来很简单,2个参数即可创建,包名和资源id
更新remoteViews无法直接访问里面的view,必须通过remoteViews提供的方法进行更新
比如:
5.1.2、在桌面小部件上面的应用:
Android系统为我们提供了一个实现桌面小部件的类:AppWidgetProvider ,它继承广播。
1、自定义小部件界面:
2、定义小部件的配置信息,res/xml/myapp_widget.xml
3、定义小部件的实现类
上面的代码实现了一个简单的桌面小部件,在小部件上面显示一张图片,点击图片后,图片旋转一周。
桌面小部件不管是初始化界面和后续的更新界面都必须使用remoteViews来完成。
4、在AndoridMenifest中申明小部件(跟注册广播差不多)
inten-filter中有2个action,第一个是小部件的点击行为,第二个是小部件的标识,必须存在
拓展AppWidgetProvider的方法:
● 1)onEnable:当窗口小部件第一次添加到桌面时会调用。
● 2)onUpdate:小部件被添加时或者每次小部件更新时会调用。
● 3)onDeleted:每删除一次桌面小部件jiuhuidiaoy。
● 4)onDisabled:当最后一个桌面小部件被删除时会调用。
● 5)onReceive:广播的内置方法。
*启动时AppWidgetProvider的执行流程:
*第一步:onReceive()
* 接到广播事件:android.appwidget.action.APPWIDGET_ENABLED
*第二步:onEnabled()
*第三步:onReceive()
* 接到广播事件:android.appwidget.action.APPWIDGET_UPDATE
*第四步:onUpdate()
*
*被删除时AppWidgetProvider
*第一步:onReceive()
* 接到广播事件:android.appwidget.action.APPWIDGET_DELETED
*第二步:onDelete();
*第三步:onReceive()
* 接到广播事件:android.appwidget.action.APPWIDGET_DISABLED
*第四步:onDisabled()
5.1.3、PendingIntent概述
pending : 等待,即将发生
pendingIntent表示将来某个不确定的时刻发生,Intent表示立刻发生
pendingIntent的典型事例是给remoteViews添加点击事件,因为remoteViews运行在远程进程中,所以无法像普通的View一样添加点击事件
pendingIntent支持3种待定意图:
启动activity,
第一个参数和第三个参数好理解,我们来说说第二个参数requestCode和第四个参数flags
在了解这2个参数之前先要知道PendingIntent的匹配规则
flags的类型:
下面结合通知栏消息再详细描述一下4个标记位:
第一种情况:如果id是常量,那么多次调用notify只能弹出一个通知,后面的通知会覆盖前面的
第二种情况:每次id都不一样,多次调用notify会弹出多个通知
5.2、RemoteViews的内部机制
remoteViews的作用是跨进程显示并更新UI,
remoteViews不支持自定义view,只支持下列view,下列view的子类也不支持
否则就会抛异常:android.view.inflateException
因为remoteViews是跨进程显示页面,所以无法findviewbyid获得view,但是它提供了一系列的set方法来控制view,这些方法都是通过反射来完成的
我们知道,
通知栏和桌面小程序分别由NotificationManager和AppWidgetManager管理;
而NotificationManager是通过Binder和NotificationManagerService通信,
AppWidgetManager是通过Binder和AppWidgetService通信 。
由此可见:
通知栏是在NotificationManagerService中被加载的
桌面小部件是在AppWidgetService中被加载的
而它们运行在系统的SystemServer中,那么就是跨进程通信的场景
从理论上说:
系统完全可以通过Binder去支持所有的view和view的操作,但这样做代价太大了,因为view的方法太多了,另外就是大量的IPC操作会影响效率
Andorid系统怎么做的呢?
RemoteViews在通知栏和桌面小部件的工作过程和上面描述的是一样的。
当我们调用set方法时它们并不会立即去更新UI,而是
通知栏必须要通过NotificationManager的notify方法更新页面
桌面小部件必须要通过AppWidgetManager的updateAppWidget方法更新页面
实际上在NotificationManager和AppWidgetManager的内部实现中,它们的确是通过RemoteViews的apply(加载并且更新页面)和reapply(只更新页面)来加载和更新页面的
看代码可以知道,ReflectionAction其实表示的是一个反射的动作
关于单击事件,RemoteViews只支持PendingIntent,另外注意,setOnclickPendingIntent给普通view设置点击事件,如果想给listview设置点击事件必须使用setPendingIntentTemplate和setOnclickFillInIntent
5.2、RemoteViews的意义
比如现在有2个应用,一个应用能够更新另一个应用的页面,这个时候我们当然可以选择AIDL实现,但是如果界面更新比较频繁,那么效率就会降低,AIDL接口也会变得复杂,这个时候如果采用RemoteViews就没有问题了,但是RemoteViews只支持一些简单的view,如果更新的界面比较简单可以采用RemoteViews。
这种写法在同一个应用中的多进程是可以的,但是不同应用就访问不到了,
我们可以通过加载资源名称进行加载,两个应用约定好布局文件的名称。
可以通过如下代码实现:
通知栏和桌面小部件不能直接去更新view,因为2者的页面都运行在其他进程中,确切来说是系统的SystemServer进程,为了跨进程更新页面,RemoteViews提供了一系列的set方法进行更新。
5.1.1、在通知栏的应用:
通知栏除了默认的效果还可以自定义布局,下面分别说明这2种情况:
默认的布局:
Notification notification = new Notification(); //设置图标 notification.icon = R.drawable.ic_launcher; //设置内容 notification.tickerText = "hello world"; //要显示的时间,一般是当即显示,故填入系统当前时间。 notification.when = System.currentTimeMillis(); //// FLAG_AUTO_CANCEL表明当通知被用户点击时,通知将被自动清除。 notification.flags = Notification.FLAG_AUTO_CANCEL; Intent intent = new Intent(this, DemoActivity_2.class); ////该语句的作用是定义了一个不是当即显示的activity, // 只有当用户拉下notify显示列表,并且单击对应的项的时候,才会触发系统跳转到该activity. PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); //在此处设置在nority列表里的该norifycation得显示情况。 notification.setLatestEventInfo(this, "chapter_5", "this is notification.", pendingIntent); NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); // 通过通知管理器来发起通知。如果id不同,则每click,在statu那里增加一个提示 manager.notify(sId, notification);
自定义的布局(使用了remoteViews来加载):
Notification notification = new Notification(); notification.icon = R.drawable.ic_launcher; notification.tickerText = "hello world"; notification.when = System.currentTimeMillis(); notification.flags = Notification.FLAG_AUTO_CANCEL; Intent intent = new Intent(this, DemoActivity_1.class); intent.putExtra("sid", "" + sId); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); System.out.println(pendingIntent); //包名和资源ID RemoteViews remoteViews = new RemoteViews(getPackageName(), R.layout.layout_notification); remoteViews.setTextViewText(R.id.msg, "chapter_5: " + sId); remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1); PendingIntent openActivity2PendingIntent = PendingIntent.getActivity(this, 0, new Intent(this, DemoActivity_2.class), PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.open_activity2, openActivity2PendingIntent); notification.contentView = remoteViews; notification.contentIntent = pendingIntent; NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(sId, notification);
remoteViews使用起来很简单,2个参数即可创建,包名和资源id
更新remoteViews无法直接访问里面的view,必须通过remoteViews提供的方法进行更新
比如:
remoteViews.setTextViewText(R.id.msg, "chapter_5: " + sId); remoteViews.setImageViewResource(R.id.icon, R.drawable.icon1);
5.1.2、在桌面小部件上面的应用:
Android系统为我们提供了一个实现桌面小部件的类:AppWidgetProvider ,它继承广播。
public class AppWidgetProvider extends BroadcastReceiver
1、自定义小部件界面:
<?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/imageView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/icon1" /> </LinearLayout>
2、定义小部件的配置信息,res/xml/myapp_widget.xml
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialLayout="@layout/widget"//初始化布局 android:minHeight="84dp" android:minWidth="84dp" android:updatePeriodMillis="86400000" //自动更新周期> </appwidget-provider>
3、定义小部件的实现类
package com.ryg.chapter_5; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.os.SystemClock; import android.util.Log; import android.widget.RemoteViews; import android.widget.Toast; public class MyAppWidgetProvider extends AppWidgetProvider { public static final String TAG = "MyAppWidgetProvider"; public static final String CLICK_ACTION = "com.ryg.chapter_5.action.CLICK"; public MyAppWidgetProvider() { super(); } @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(CLICK_ACTION)) { Toast.makeText(context, "clicked it", Toast.LENGTH_SHORT).show(); new Thread(new Runnable() { @Override public void run() { Bitmap srcbBitmap = BitmapFactory.decodeResource( context.getResources(), R.drawable.icon1); 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.widget); remoteViews.setImageViewBitmap(R.id.imageView1, rotateBitmap(context, srcbBitmap, degree)); Intent intentClick = new Intent(); intentClick.setAction(CLICK_ACTION); PendingIntent pendingIntent = PendingIntent .getBroadcast(context, 0, intentClick, 0); remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent); appWidgetManager.updateAppWidget(new ComponentName( context, MyAppWidgetProvider.class),remoteViews); SystemClock.sleep(30); } } }).start(); } } /** * 每次窗口小部件被点击更新都调用一次该方法 */ @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 i = 0; i < counter; i++) { int appWidgetId = appWidgetIds[i]; onWidgetUpdate(context, appWidgetManager, appWidgetId); } } /** * 窗口小部件更新 * * @param context * @param appWidgeManger * @param appWidgetId */ private void onWidgetUpdate(Context context, AppWidgetManager appWidgeManger, int appWidgetId) { Log.i(TAG, "appWidgetId = " + appWidgetId); RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget); // "窗口小部件"点击事件发送的Intent广播 Intent intentClick = new Intent(); intentClick.setAction(CLICK_ACTION); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intentClick, 0); remoteViews.setOnClickPendingIntent(R.id.imageView1, pendingIntent); appWidgeManger.updateAppWidget(appWidgetId, remoteViews); } private Bitmap rotateBitmap(Context context, Bitmap srcbBitmap, float degree) { Matrix matrix = new Matrix(); matrix.reset(); matrix.setRotate(degree); Bitmap tmpBitmap = Bitmap.createBitmap(srcbBitmap, 0, 0, srcbBitmap.getWidth(), srcbBitmap.getHeight(), matrix, true); return tmpBitmap; } }
上面的代码实现了一个简单的桌面小部件,在小部件上面显示一张图片,点击图片后,图片旋转一周。
桌面小部件不管是初始化界面和后续的更新界面都必须使用remoteViews来完成。
4、在AndoridMenifest中申明小部件(跟注册广播差不多)
<receiver android:name=".MyAppWidgetProvider" > <meta-data android:name="android.appwidget.provider" android:resource="@xml/appwidget_provider_info" > </meta-data> <intent-filter> <action android:name="com.ryg.chapter_5.action.CLICK" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> </intent-filter> </receiver>
inten-filter中有2个action,第一个是小部件的点击行为,第二个是小部件的标识,必须存在
拓展AppWidgetProvider的方法:
● 1)onEnable:当窗口小部件第一次添加到桌面时会调用。
● 2)onUpdate:小部件被添加时或者每次小部件更新时会调用。
● 3)onDeleted:每删除一次桌面小部件jiuhuidiaoy。
● 4)onDisabled:当最后一个桌面小部件被删除时会调用。
● 5)onReceive:广播的内置方法。
*启动时AppWidgetProvider的执行流程:
*第一步:onReceive()
* 接到广播事件:android.appwidget.action.APPWIDGET_ENABLED
*第二步:onEnabled()
*第三步:onReceive()
* 接到广播事件:android.appwidget.action.APPWIDGET_UPDATE
*第四步:onUpdate()
*
*被删除时AppWidgetProvider
*第一步:onReceive()
* 接到广播事件:android.appwidget.action.APPWIDGET_DELETED
*第二步:onDelete();
*第三步:onReceive()
* 接到广播事件:android.appwidget.action.APPWIDGET_DISABLED
*第四步:onDisabled()
5.1.3、PendingIntent概述
pending : 等待,即将发生
pendingIntent表示将来某个不确定的时刻发生,Intent表示立刻发生
pendingIntent的典型事例是给remoteViews添加点击事件,因为remoteViews运行在远程进程中,所以无法像普通的View一样添加点击事件
pendingIntent支持3种待定意图:
启动activity,
public static PendingIntent getActivity(Context context, int requestCode, Intent intent, int flags) { return getActivity(context, requestCode, intent, flags, null); } 启动service, public static PendingIntent getService(Context context, int requestCode, Intent intent, int flags) { 发送广播, public static PendingIntent getBroadcast(Context context, int requestCode, Intent intent, int flags) { return getBroadcastAsUser(context, requestCode, intent, flags, new UserHandle(UserHandle.myUserId())); }
第一个参数和第三个参数好理解,我们来说说第二个参数requestCode和第四个参数flags
在了解这2个参数之前先要知道PendingIntent的匹配规则
PendingIntent的匹配规则是:intent和requestCode都相同,那么这2个pendingIntent就相同 Intent的匹配规则是:componentName和intent-filter相同
flags的类型:
//pendingIntent只能被使用一次,就会自动cancel public static final int FLAG_ONE_SHOT = 1<<30; //没有什么使用意义 public static final int FLAG_NO_CREATE = 1<<29; //如果pendingIntent已经存在,那么它们都会被取消,然后创建一个新的pendingIntent public static final int FLAG_CANCEL_CURRENT = 1<<28; ///如果pendingIntent已经存在,它们都会被更新,即Intent中的Extras会被替换成新的 public static final int FLAG_UPDATE_CURRENT = 1<<27;
下面结合通知栏消息再详细描述一下4个标记位:
manager.notify(id,notification)
第一种情况:如果id是常量,那么多次调用notify只能弹出一个通知,后面的通知会覆盖前面的
第二种情况:每次id都不一样,多次调用notify会弹出多个通知
5.2、RemoteViews的内部机制
remoteViews的作用是跨进程显示并更新UI,
remoteViews不支持自定义view,只支持下列view,下列view的子类也不支持
否则就会抛异常:android.view.inflateException
因为remoteViews是跨进程显示页面,所以无法findviewbyid获得view,但是它提供了一系列的set方法来控制view,这些方法都是通过反射来完成的
我们知道,
通知栏和桌面小程序分别由NotificationManager和AppWidgetManager管理;
而NotificationManager是通过Binder和NotificationManagerService通信,
AppWidgetManager是通过Binder和AppWidgetService通信 。
由此可见:
通知栏是在NotificationManagerService中被加载的
桌面小部件是在AppWidgetService中被加载的
而它们运行在系统的SystemServer中,那么就是跨进程通信的场景
第一步:RemoteViews会通过Binder传递到SystemServer进程(因为RemoteViews实现了Parcelable接口) 第二步:通过LayoutInflater加载RemoteViews中的layout布局(在SystemServer进程中返回的是一个普通的view) 第三步:调用RemoteViews的set方法进行UI的更新 第四步:通过NotificationManager和AppWidgetManager提交UI更新任务 这样,RemoteViews就在SystemServer进程中显示了(就是我们看到的通知栏消息和桌面小部件)
从理论上说:
系统完全可以通过Binder去支持所有的view和view的操作,但这样做代价太大了,因为view的方法太多了,另外就是大量的IPC操作会影响效率
Andorid系统怎么做的呢?
Android系统提供了一个Action,Action代表view的操作,action也实现了Parceable接口 Android系统在RemoteViews调用apply进行更新任务的提交时,其实是间接的调用了Action对象的apply方法进行提交的;然后在远程进程中批量执行RemoteViews的更新操作,避免了大量的IPC操作,提高了程序的性能,Android系统在这方面的确设计的很精妙。 (Action其实是利用了反射进行UI的更新)
RemoteViews在通知栏和桌面小部件的工作过程和上面描述的是一样的。
当我们调用set方法时它们并不会立即去更新UI,而是
通知栏必须要通过NotificationManager的notify方法更新页面
桌面小部件必须要通过AppWidgetManager的updateAppWidget方法更新页面
实际上在NotificationManager和AppWidgetManager的内部实现中,它们的确是通过RemoteViews的apply(加载并且更新页面)和reapply(只更新页面)来加载和更新页面的
//RemoteViews的内部类ReflectionAction private final class ReflectionAction extends Action { @Override public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { final View view = root.findViewById(viewId); if (view == null) return; Class<?> param = getParameterType(); if (param == null) { throw new ActionException("bad type: " + this.type); } try { getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value)); } catch (ActionException e) { throw e; } catch (Exception ex) { throw new ActionException(ex); } } }
看代码可以知道,ReflectionAction其实表示的是一个反射的动作
关于单击事件,RemoteViews只支持PendingIntent,另外注意,setOnclickPendingIntent给普通view设置点击事件,如果想给listview设置点击事件必须使用setPendingIntentTemplate和setOnclickFillInIntent
5.2、RemoteViews的意义
比如现在有2个应用,一个应用能够更新另一个应用的页面,这个时候我们当然可以选择AIDL实现,但是如果界面更新比较频繁,那么效率就会降低,AIDL接口也会变得复杂,这个时候如果采用RemoteViews就没有问题了,但是RemoteViews只支持一些简单的view,如果更新的界面比较简单可以采用RemoteViews。
//如果采用RemoteViews进行页面的更新,那么还有一个问题:布局文件加载的问题 View view = remoteViews.apply(this, mContent); mContent.addView(view);
这种写法在同一个应用中的多进程是可以的,但是不同应用就访问不到了,
我们可以通过加载资源名称进行加载,两个应用约定好布局文件的名称。
可以通过如下代码实现:
int layoutId = getResources.getIdentifier("layoutId"); View view = getInflate.inflate(layoutId, mContent); remoteViews.reapply(this, view); mContent.addView(view);
相关文章推荐
- Android开发艺术探索第五章——理解RemoteViews
- Android开发艺术探索第五章——理解RemoteViews
- Android 开发艺术探索笔记 第五章 理解RemoteViews
- Android开发艺术探索 第5章 理解RemoteViews 读书笔记
- Android开发艺术探索_理解RemoteViews(五)
- 第八章理解Window和WindowManager(Android开发艺术探索)
- 开发艺术探索--理解RemoteViews
- Android开发艺术探索——第八章:理解Window和WindowManager
- 读书笔记-Android开发艺术探索-第8章-理解Window和WindowManager
- 读书笔记-Android开发艺术探索-第12章-Bitmap的加载和Cache
- Android开发艺术探索学习-IPC之Binder(三)
- 【读书笔记】【Android开发艺术探索】第10章 Android 的消息机制
- 读书笔记-Android开发艺术探索-第11章-Android的线程和线程池
- 任玉刚【Android开发艺术探索】读后笔记一
- Android开发艺术探索学习-IPC之Binder(一)
- 任玉刚【Android开发艺术探索】读后笔记四
- 【读书笔记】Android开发艺术探索
- Android开发艺术探索学习-View的事件分发机制(二)
- Android开发艺术探索学习-View的滑动
- 任玉刚【Android开发艺术探索】读后笔记二