听名字就可以看出,remote views是一种远程view,感觉有点像远程service,其实remote views是view的一个结构,他可以在其他的进程中显示,由于它可以在其他的进程中显示,那么他就可以跨进程的更新其他进程的view,这听起来有点不可思议,感觉有点像aidl,但是我要告诉你这确实不是,那它的原理是什么呢?且听后面慢慢道来。
remote views在Android中有两个常见的应用场景:通知栏和桌面小部件。
Remote Views的应用
桌面部件与通知栏分别由AppWidgetManager 与 NotificationManager来管理.。分别与systemService进程中的AppWidgetServer的NotificationManagerServer进行通讯 ,所以,才需要RemoteView来更新界面.RemoteView实现了Paracelable,通过Bindler传递到 systemService进程中。
RemoteViews的内部结构
我们首先来看看RomoteViews的类图:
RemoteViews:中保存Remote端的mPackage和mLayoutId;并用mActions:ArrayList<RemoteViews.Action>保存各种Action。
mPackage和mLayoutId是在构造RemoteViews时传进去的[上文图中的seq#1];
mActions是设置各种Remote端的响应Intent以及图形元素的时候,保存到相应的Action中,然后把Action加入到这里保存的;
mLayoutId里的各种控件通过setTextViewText()/ setImageViewResource() / setProgressBar(),等函数在remote端设置的。这些方法再调用setType()[Type可为Boolean
/ Byte / Short / Int/ Long / Char / String / Uri / Bitmap/ Bundle, etc]保存到ReflectionAction中。
SetOnClickPendingIntent是用来在local端用户点击viewId时,发出pendingIntent通知的。在SetOnClickPendingIntent的构造方法中保存viewId和pendingIntent。
ReflectionAction用来在local端显示时,通过Reflect机制执行获得Remote端资源的。在ReflectionAction的构造方法中保存viewId,methodName,type以及value。
ViewGroupAction和SetDrawableParameters也是RemoteViews.Action的子类,在这个场景中并未用到,基本原理相似,读者可自行分析。
AppWidgetHostView(显示RemoteViews内容)
RemoteViews提供了内容之后,AppWidgetHost会通过IAppWidgetHost.updateAppWidget()被通知到Remote端有更新,本地端把RemoteViews提供的内容显示在AppWidgetHostView上。
我们来看下类图:
我们分析下流程:
1. 获取RemoteViews里Remote端(AppWidgetProvider)的packageName和layoutId,通过packageName创建远端的context——remoteContext。
2. 通过RemoteViews的apply()方法,真正开始执行侦听Click操作的动作;通过远端Layout获得本地使用的View。
2.1. 克隆一个本地的LayoutInflater;[Seq#8]
2.2. 用克隆出的LayoutInflater对remote端的layoutId执行Inflate,获得Layout所描述的View的Hierarchy,亦即后面用到的rootView;[Seq#9~ 10]
2.3. 对2.2中获得的view执行performApply。[Seq#11~ 19]
performApply()对所有mActions中的Action都执行apply()操作。这样,
2.3.1 对于setOnClickPendingIntent来说,[Seq#12~ 15]
- 通过rootView(2.2获得的Remote端的Layout的总的View)的findViewById(viewId),找到要侦听的View;[Seq#13]
- 对找到的要侦听的View设置Click的Listener。[Seq#14]
2.3.2对于ReflectionAction来说,[Seq#16~ 19]
- 通过rootView(2.2获得的Remote端的Layout的总的View)的findViewById(viewId),找到要设置内容的对象View;[Seq#17]
- 然后通过Reflect机制,执行View实现类里的方法(比如这里是setImageResource()),把相应的资源的Id设置给它. [Seq#18]
3. 把获得的View加入到本地的View系统中。[Seq#21]
看一段关键代码,通过Reflect机制设置内容的代码片段:
@Override public void apply(View root) { final View view = root.findViewById(viewId); Class param = getParameterType(); // 通过this.type得到class:int.class Class klass = view.getClass(); // 这个类在Remote的Layout中定义,这里为ImageView Method method = klass.getMethod(this.methodName, param); // methodName是实现View类里的方法名:setImageResource(int) try { // 执行ImageView.setImageResource(value),value为resId method.invoke(view, this.value); } catch (Exception ex) { throw new ActionException(ex); } }
应用实例:
本文开始说过Android RemoteView主要两个应用,其中一个就是桌面小控件。那么接下来说说AppWidget。
AppWidget
AppWidget也就是“窗口小部件”,当我们点击桌面的小部件的时候,其实是触发Remote端的AppWidgetProvider实现;具体显示是Local的AppWidgetHost通过AppWidgetHostView实现。AppWidgetHost、AppWidgetProvider与AppWidgetService和AppWidgetManager按照特有的机制组合在一起,才能完整的实现AppWidget机制。接下来我们具体分析下他的实现流程。
AppWidget系统框架
AppWidget实现Remote端提供UI元素,Local端具体显示。AppWidgetHost在AppWidget系统中是Local端;AppWidgetProvider端是Remote端。AppWidgetHost和AppWidgetProvider直接或通过IAppWidgetService或间接的通过AppWidgetManager,与AppWidgetService实现交互。AppWidgetService是所有元素的总管,负责协调其他各个部分。
AppWidget简要分析
AppWidgetHost
AppWidgetHost通过IAppWidgetService利用Binder机制实现与系统进程中的AppWidgetService通信;
AppWidgetHost有IAppWidgetHost(通过Callbacks)的实现,并在AppWidgetHost.startListening()中注册到AppWidgetService中,实现当Remote端的数据有更新时,通过IAppWidgetHost.updateAppWidget()通知AppWidgetHost更新本地的显示;或者当Remote端的Provider改变时通知AppWidgetHost。
AppWidgetHost创建本地AppWidgetHostView时,会以AppWidgetId和AppWidgetHostView加入mViews: HashMap<Interger,AppWidgetHostView>
AppWidgetProvider
AppWidgetProvider是AppWidget的Remote端内容提供方,并能注册响应其所提供内容的某个View被点击时,响应的Intent。
AppWidgetProvider是一个抽象类,实现类需要实现抽象方法onUpdate() / onDeleted()/ onEnabled()和onDisabled()。这是AppWidgetProvider的一个模板模式实现,要求AppWidgetProvider的实现者:
- 在AndroidManefest.xml中声明这个AppWidgetProvider是"android.appwidget.action.APPWIDGET_UPDATE"的Receiver,这样AppWidgetProvider作为一个BroardcastReceiver才能接收到AppWidgetService发出的消息。而AppWidgetService查询系统中已经安装了哪些AppWidgetProvider也是通过查询这个接收者的Intent来的实现。所以如果没有这个Receiver,安装的Provider里就没有这个Provider,亦即,未加入到AppWidget系统中。
- 另外,这个Receiver的meta-data的name指定为“android.appwidget.provider”;resource中用xml定义appwidget-provider内的各种属性。这些属性按包被安装时,检索出来赋给AppWidgetProviderInfo。
通常,对于应用开发来说不太注重AppWidget其他的部分,只是写AppWidgetProvider,但一般也都称AppWidgetProvider为AppWidget开发。
AppWidgetService
AppWidgetService通过IAppWidgetService提供方法给AppWidgetHost、AppWidgetProvider使用。
在mInstalledProviders:AppWidgetService.Provider中保存AppWidgetProvider的信息;在mHost:AppWidgetService.Host中保存AppWidgetHost的信息;并用mAppWidgetIds:AppWidgetService.AppWidgetId保存AppWidgetHost与AppWidgetProvider的绑定关系。
注意:
AppWidgetService运行于三个各自不同的进程空间:
- AppWidgetService运行于system_process进程;
- AppWidgetHost运行于自己的进程空间,典型的桌面上的AppWidgetHost运行于Launcher中;
- AppWidgetProvider也是运行于自己的进程空间,典型的如“电量控制”这个AppWidgetProvider运行于Settings中。
AppWidgetHost和AppWidgetProvider要用到AppWidgetService的服务时,用Binder机制通过IAppWidgetService实现。AppWidgetService通过IAppWidgetHost通知AppWidgetHost;AppWidgetService通过发Broadcast通知AppWidgetProvider。
讲完概念,我们来两个实用的例子:
通知栏:
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,NotificationActivity.class); //定义延迟的意图 PendingIntent pendingIntent=PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); notification.setLatestEventInfo(this, "我的标题","我的文本", pendingIntent); RemoteViews remoteViews=new RemoteViews(getPackageName(), R.layout.remoteview); remoteViews.setTextViewText(R.id.title, "我的标题"); remoteViews.setTextViewText(R.id.content, "我的内容"); notification.contentView=remoteViews; notification.contentIntent=pendingIntent; NotificationManager manager=(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); manager.notify(1, notification);
桌面小控件:
要完成桌面小控件需要如下几个步骤:
1 自定义布局
2 定义配置文件
3 自定义类继承AppWidgetProvider
4 功能清单注册
<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:minWidth="84dip" android:minHeight="84dip" android:initialLayout="@layout/widget" android:updatePeriodMillis="300000" > </appwidget-provider>
功能清单的配置:
<receiver android:name="com.example.remoteviewfinal.MyAppWidgetProvider"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> </intent-filter> <meta-data android:name="android.appwidget.provider" android:resource="@xml/provider_info" ></meta-data> </receiver>
自定义类:
public class MyAppWidgetProvider extends AppWidgetProvider { @Override public void onReceive(final Context context, Intent intent) { // TODO 自动生成的方法存根 super.onReceive(context, intent); /** * 将图片旋转一圈 */ new Thread(new Runnable() { @Override public void run() { Bitmap bitmap=BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher); AppWidgetManager manager=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.imageview, rotateBitmap(context,bitmap,degree)); ComponentName cn=new ComponentName(context, MyAppWidgetProvider.class); //更新界面 manager.updateAppWidget(cn, remoteViews); } } }).start(); } @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { // TODO 自动生成的方法存根 super.onUpdate(context, appWidgetManager, appWidgetIds); } /** * 当第一个实例被添加的时候调用 */ @Override public void onEnabled(Context context) { // TODO 自动生成的方法存根 super.onEnabled(context); } /** * 当实例被删除的时候调用 */ @Override public void onDeleted(Context context, int[] appWidgetIds) { // TODO 自动生成的方法存根 super.onDeleted(context, appWidgetIds); } /** * 当最后一个实例被删除的时候调用 */ @Override public void onDisabled(Context context) { // TODO 自动生成的方法存根 super.onDisabled(context); } private Bitmap rotateBitmap(Context context,Bitmap bitmap,float degree){ Matrix matrix=new Matrix(); matrix.reset(); matrix.setRotate(degree); Bitmap result=Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(), bitmap.getHeight(), matrix,true); return result; } }
更新不同进程的界面:
定义界面A
来更新不同进程的界面B. 我们可以将界面A中的remoteView 传递到界面B,界面B 获取对象,调用控件的apply方法更新界面 修改A界面的process属性,使其在不同的进程中运行。
界面A:
public void send(){ RemoteViews remoteViews=new RemoteViews(getPackageName(), R.layout.widget); remoteViews.setTextViewText(R.id.msg, "我的信息"); PendingIntent pendingIntent=PendingIntent.getActivity(this,0, new Intent(this,NotificationActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); remoteViews.setOnClickPendingIntent(R.id.btn, pendingIntent); Intent intent=new Intent(Constant.ACTION); intent.putExtra(Constant.VIEW, remoteViews); sendBroadcast(intent); }
界面B:
private BroadcastReceiver mRemoteBroadcastReceiver=new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { RemoteViews remoteViews=intent.getParcelableExtra(Constant.VIEW); if(remoteViews!=null){ View view=remoteViews.apply(NotificationActivity.this, container); container.addView(view); } } };