网站如何调用手机淘宝做淘宝客,企业cms源码,网站引导页面设计,信誉好的网站建设公司Widget俗称“小插件”#xff0c;是Android系统中一个很常用的工具。比如我们可以在Launcher中添加一个音乐播放器的Widget。
在Launcher上可以添加插件#xff0c;那么是不是说只有Launcher才具备这个功能呢#xff1f;
Android系统并没有具体规定谁才能充当“Widget容器…Widget俗称“小插件”是Android系统中一个很常用的工具。比如我们可以在Launcher中添加一个音乐播放器的Widget。
在Launcher上可以添加插件那么是不是说只有Launcher才具备这个功能呢
Android系统并没有具体规定谁才能充当“Widget容器”这个角色。它定义了一套完整的Widget添加/移除和显示机制使得人人都能当“Widget提供者”人人也都有资格做“Widget容器”。
上面我们提到了“Widget提供者”和“Widget容器”这样的概念前者如一个天气插件后者则如Launcher。在Widget机制中它们都有各自的专有名词同时也是类名分别是AppWidgetProvider和AppWidgetHost。除此之外我们能猜想到系统中还需要一个全局的Widget管理器。类似于WindowManagerService、WallpaperManagerService的命名方式它叫作AppWidgetService。 “功能的提供者”——AppWidgetProvider
既然叫作Provider言下之意就是“功能的提供者”。从Host的角度来说它没有办法预先知晓用户会添加多少个Widget也没有办法知晓这些添加的Widget都实现了哪些功能。所以在Host的“世界”里一个Widget只是一个View——它只需要按照要求进行正确显示即可具体的功能实现则由AppWidgetProvider来完成。 Host把Widget看成View的一个“变种”。 一个有效的Provider要提供至少以下几方面的内容。
AppWidgetProviderInfo 也就是用于描述这个Widget的各种信息包括它的layout布局、刷新频率以及下面要提到的AppWidgetProvider等。这些信息以XML格式的文件表示Tag标志为。 AppWidgetProvider 既然Widget最终是要被显示在Host中的那么它的功能实现和普通应用程序就一定会有差异。AppWidgetProvider主要借助于Broadcast事件来对Widget进行“远程更新”。 View布局 AppWidgetProviderInfo用于描述这个Widget的整体信息而这里的Layout则是专门用于描述Widget的“显示部分”确切地说是初始化时的显示。 Provider就是一个BroadcastReceiver。比如我们可以在AndroidManifest.xml中声明以下内容来定义一个AppWidgetProvider
receiver android:nameExampleAppWidgetProvider intent-filteraction android:nameandroid.appwidget.action.APPWIDGET_UPDATE //intent-filtermeta-data android:nameandroid.appwidget.providerandroid:resourcexml/example_appwidget_info /
/receiver这个receiver要接收的唯一消息就是APPWIDGET_UPDATE并且它还需要带有信息明确指明自己是一个android.appwidget.provider最后的android:resource即前面说到的AppWidgetProviderInfo。比如
appwidget-provider xmlns:androidhttp://schemas.android.com/apk/res/androidandroid:minWidth294dpandroid:minHeight72dpandroid:updatePeriodMillis86400000android:previewImagedrawable/previewandroid:initialLayoutlayout/example_appwidget
/appwidget-provider这个XML文件的最后一项属性android:initialLayout指定了初始的View布局为example_ appwidget它和我们编写普通应用程序的布局语法一样。
当我们编写一个自己的Widget Provider时首先要继承自AppWidgetProvider。后者的内部实现并不复杂它继承自BroadcastReceiver并在onReceive中将具体事件通过重载函数通知我们的AppWidgetProvider实例
/*frameworks/base/core/java/android/appwidget/AppWidgetProvider.java*/
public class AppWidgetProvider extends BroadcastReceiver {…public void onReceive(Context context, Intent intent) {String action intent.getAction();if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) {Bundle extras intent.getExtras();if (extras ! null) {int[] appWidgetIds extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET _IDS);if (appWidgetIds ! null appWidgetIds.length 0) {this.onUpdate(context, AppWidgetManager.getInstance(context), appWid getIds);}}}…}…也就是说编写一个Widget应该根据需求来重载onReceiver如果有需要的话onUpdateonAppWidgetOptionsChangedonDeletedonEnabled以及onDisabled。它们分别会在此Widget被更新、Option改变、被删除等情况下被调用。
不过要特别注意的是一个Widget是可以有多个具体实例的。比如我们写了一个“天气”插件供用户使用那么理论上并不限制用户会在Launcher中添加多少个“天气”实例。因而需要有相应的WidgetId来唯一标识每一个实例。 AppWidgetHost
上一小节我们了解了AppWidgetProvider所要做的工作接下来再看看Host又是如何配合Provider的。简而言之Host这个“东道主”需要提供相应的空间供Widget来展现自己的UI界面。打个比方AppWidgetHost就好比一个展厅而至于陈列的汽车是大众还是奔驰品牌都是没问题的——取决于Widget本身的意愿。
成为一个AppWidgetHost它需要解决以下问题。
如何显示Widget的UI界面如何与AppWidgetProvider通信
一个AppWidgetProvider与外界的接口就是onReceive然后再细化为onUpdateonEnable等事件处理。而产生这些事件的根源除了AppWidgetService这一系统元素外就是AppWidgetHost了。只不过后者也是要通过前者来发送事件的。 简图中的Host_Application是指扮演Host角色的应用程序如Launcher。它在整个Widget机制中只会与AppWidgetManager进行交互而不会直接调用AppWidgetService的接口这有点类似于ServiceManager.java的作用。可想而知AppWidgetManager内部还是要通过间接调用AppWidgetService来实现的。另外每个Host_Application还要持有一个AppWidgetHost我们可以认为它是Host的代理。
当一个Host_Application创建后它需要向AppWidgetService注册监听Widget事件并提供一个callback实现。这个callback实际上继承自IAppWidgetHost.Stub即一个基于AIDL的BinderServer这就保证了AppWidgetService在事件发生时可以回调到Host。需要接收的回调事件包括
updateAppWidget updateAppWidgetViewAppWidgetHostproviderChangedonProviderChangedAppWidgetHostviewDataChangedviewDataChanged AppWidgetHost
我们以updateAppWidget为例来分析其内部实现
/*frameworks/base/core/java/android/appwidget/AppWidgetHost.java*/void updateAppWidgetView(int appWidgetId, RemoteViews views, int userId) {AppWidgetHostView v;synchronized (mViews) {v mViews.get(appWidgetId);}if (v ! null) {v.updateAppWidget(views);}}当WidgetProvider希望更新Host中的View显示时比如天气插件更新气温它会通过AppWidgetManager.updateAppWidgetint appWidgetIdRemoteViews views来指定新的View样式RemoteViews。这个请求最终由AppWidgetService发送给相应的Host来实现即updateApp WidgetView。
上面代码中的mViews定义如下
HashMapInteger,AppWidgetHostView mViews new HashMapInteger, AppWidgetHostView();它是一个AppWidgetHostView的集合。换句话说是当前这个Host所包含的所有Widget的View对象。比如在Launcher中用户每添加一个Widget或者设备刚开机时Launcher自己从保存的配置中读取需要加载显示的Widgets就会用AppWidgetHost.createView把它加入这个集合中。另外因为Widget数量众多必须为它们分配一个全局唯一的WidgetId。
Launcher中添加widget的操作过程
首先Host通过AppWidgetManager.getAppWidgetInfo来得到相应WidgetId的Info信息即我们前一小节中讲到的AppWidgetProviderInfo。接着Host会通过AppWidgetHost.createView产生一个AppWidgetHostView——这个View对应的布局是由前面的initialLayout指定的。后续AppWidget Provider根据实际情况还会通过RemoteViews来实时更新它的Widget显示。
那么createView都做了哪些工作呢
/*frameworks/base/core/java/android/appwidget/AppWidgetHost.java*/public final AppWidgetHostView createView(Context context, int appWidgetId,AppWidgetProviderInfo appWidget) {final int userId mContext.getUserId();AppWidgetHostView view onCreateView(mContext, appWidgetId, appWidget);//本地的View对象view.setUserId(userId);view.setOnClickHandler(mOnClickHandler);view.setAppWidget(appWidgetId, appWidget);synchronized (mViews) {mViews.put(appWidgetId, view);}RemoteViews views;try {views sService.getAppWidgetViews(appWidgetId, userId);//得到该Widget的RemoteViewsif (views ! null) {views.setUser(new UserHandle(mContext.getUserId()));}} catch (RemoteException e) {throw new RuntimeException(system server dead?, e);}view.updateAppWidget(views);//通过RemoteViews“搭建”本地的Viewreturn view;}第一步是要产生一个AppWidgetHostView默认情况下onCreateView内部只是new了一个AppWidgetHostView对象然后就直接返回。如果读者有特殊需求可以重载这个函数
/*frameworks/base/core/java/android/appwidget/AppWidgetHostView.java*/
public class AppWidgetHostView extends FrameLayout {
…可见AppWidgetHostView实际上是FrameLayout的扩展子类。而setAppWidget一方面将widgetId与此AppWidgetHostView联系起来另一方面设置了将要显示的widget的padding值我们同样可以重载这一实现。
接下来就是widget显示的重点即我们如何把Widget Provider定义的界面显示到Host_ Application中。 在分析源码前我们先来打个比方。张三在北京建了一栋别墅李四看了后很喜欢于是也想自己在上海建一栋一模一样的。怎么办显然不可能将张三的别墅直接挪到上海因为它们是异地的属于两个不同的“进程空间”。一个可行的办法就是将张三的建筑图纸完完本本地递交给李四然后李四就可以在他自己的“进程空间”中兴建一栋一模一样的别墅了。虽然砖瓦、水泥可能用的不是一个品牌但这丝毫不会影响大家认为“这两栋别墅的样式风格是完全一样的”。 Widget的显示也类似。我们需要在另一个进程空间即Host_Application中显示自己的View那么也完全可以把View的“图纸”交给对方——这样对方只要“依葫芦画瓢”也就不难“还原”出Widget的“真实面目”了。而这张“图纸”就是RemoteViews
/*RemoteViews.java*/
public class RemoteViews implements Parcelable, Filter {…虽然它的名称中也带有“Views”但实际上没有任何View的影子。它继承自可以跨进程传递的Parcelable类以及对数据进行约束的Filter类。
有了这些基础我们再回头接着看前面的createView
views sService.getAppWidgetViews(appWidgetId);上面这句代码根据WidgetId来得到一个RemoteViews它借助于sService即AppWidgetService提供的接口来实现。实际上它只是简单填写了Widget的LayoutId和PackageName等后面真正构造Widget的UI界面时才会去取“图纸”。
最后调用的updateAppWidget是真正构建widget界面的地方分段阅读
public void updateAppWidget(RemoteViews remoteViews) {…boolean recycled false;View content null;Exception exception null;…if (remoteViews null) {…} else {mRemoteContext getRemoteContext(remoteViews);int layoutId remoteViews.getLayoutId();/*Step1. 描述Widget的LayoutId*/…if (content null) {try {content remoteViews.apply(mContext, this, mOnClickHandler);/*Step2.创建Widget的View*/} catch (RuntimeException e) {exception e;}}mLayoutId layoutId;mViewMode VIEW_MODE_CONTENT;}…if (!recycled) {prepareView(content);addView(content);/*添加Widget的View到全局管理中*/}…}小结一下这个函数简单来讲它做了两件事。 生成一个Viewcontent变量 这个View根据推测就是由Widget的“图纸”生成的因而代表了Widget的UI界面。 将上述View加到AppWidgetHostView中 AppWidgetHostView是一个FrameLayout它将content作为子View添加进来。这样当整个View重绘时Widget的界面自然也就呈现出来了。
来看看上面代码段中apply函数的实现 /*frameworks/base/core/java/android/widget/RemoteViews.java*/public View apply(Context context, ViewGroup parent, OnClickHandler handler) {RemoteViews rvToApply getRemoteViewsToApply(context);View result;Context c prepareContext(context);LayoutInflater inflater (LayoutInflater)c.getSystemService(Context.LAYOUT_ INFLATER_ SERVICE);…resultinflater.inflate(rvToApply.getLayoutId(), parent, false);…return result;}这样程序就按照Widget提供的“图纸”成功地在host进程中构造出本地的View对象了——它会和Host_Application中其他View一起经过SurfaceFlinger的处理后最终显示到屏幕上。