RemoteViews原理分析及应用.docx
《RemoteViews原理分析及应用.docx》由会员分享,可在线阅读,更多相关《RemoteViews原理分析及应用.docx(28页珍藏版)》请在冰豆网上搜索。
RemoteViews原理分析及应用
RemoteViews原理分析及应用
RemoteViews基本概念
RemoteViews乍一看名字似乎也是一种View,实则不然,它并不是View。
来看RemoteViews的定义及官方说明:
/**
*Aclassthatdescribesaviewhierarchythatcanbedisplayedin
*anotherprocess.Thehierarchyisinflatedfromalayoutresource
*file,andthisclassprovidessomebasicoperationsformodifying
*thecontentoftheinflatedhierarchy.
*/
publicclassRemoteViewsimplementsParcelable,Filter{
……
}
我们可以得到以下几点结论:
RemoteViews只是一个实现了Parcelable和Filter接口的类,而并非继承自View。
RemoteViews用来描述可运行在其他进程中的视图结构,但RemoteViews本身不是视图,只是一个描述类。
RemoteViews描述的远程视图需要通过layout资源文件定义。
RemoteViews类提供了一系列修改远程视图的方法。
现在我们对RemoteViews应该有了一个大概的认识,它可以跨进程来显示和更新视图。
RemoteViews主要有两个应用场景:
自定义通知栏
桌面小部件
本文最后会给出RemoteViews的应用实例,接下来我们先分析RemoteViews的实现原理。
RemoteViews原理分析
构造函数
RemoteViews提供了多个构造函数,如:
publicRemoteViews(StringpackageName,intlayoutId){}
publicRemoteViews(StringpackageName,intuserId,intlayoutId){}
publicRemoteViews(RemoteViewslandscape,RemoteViewsportrait){}
publicRemoteViews(Parcelparcel){}
以一个最常用的构造方法为例:
/**
*CreateanewRemoteViewsobjectthatwilldisplaytheviewscontained
*inthespecifiedlayoutfile.
*
*@parampackageNameNameofthepackagethatcontainsthelayoutresource
*@paramlayoutIdTheidofthelayoutresource
*/
publicRemoteViews(StringpackageName,intlayoutId){
this(getApplicationInfo(packageName,UserHandle.myUserId()),layoutId);
}
由注释可知,第一个参数为包名,第二个参数为布局资源文件的ID,接下来会调用:
/**
*CreateanewRemoteViewsobjectthatwilldisplaytheviewscontained
*inthespecifiedlayoutfile.
*
*@paramapplicationTheapplicationwhosecontentisshownbytheviews.
*@paramlayoutIdTheidofthelayoutresource.
*
*@hide
*/
protectedRemoteViews(ApplicationInfoapplication,intlayoutId){
mApplication=application;
mLayoutId=layoutId;
mBitmapCache=newBitmapCache();
//setupthememoryusagestatistics
mMemoryUsageCounter=newMemoryUsageCounter();
recalculateMemoryUsage();
}
同样也很简单,第一个参数为远程视图展示内容所属的Application信息,第二个参数为布局文件ID。
该构造方法主要是初始化mApplication与mLayoutId,其他代码为图片缓存及内存计算的一些逻辑。
核心属性字段
RemoteView有如下几个比较重要的属性字段:
/**
*Applicationthathoststheremoteviews.
*
*@hide
*/
privateApplicationInfomApplication;
/**
*TheresourceIDofthelayoutfile.(Addedtotheparcel)
*/
privatefinalintmLayoutId;
/**
*Anarrayofactionstoperformontheviewtreeonceithasbeen
*inflated
*/
privateArrayListmActions;
前两个比较好理解,而且上文提到是在构造函数中赋值的。
mActions是用来存储Action的一个列表,而Action可以理解为对远程视图操作的一个封装,下文会详细解释。
RemoteView注解
在RemoteViews源码中声明了如下注解:
/**
*ThisannotationindicatesthatasubclassofViewisalllowedtobeused
*withthe{@linkRemoteViews}mechanism.
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public@interfaceRemoteView{
}
从注解类型来看为运行时注解,作用于类或接口,结合注释可知此注解用于View的子类,用来标识该View是否可以作为远程视图使用。
由此我们也可以推断,并非所有View都可以作为远程视图,只有声明了RemoteView注解的View才可以。
我们从源码定义来简单验证一下:
TextView的定义
@RemoteView
publicclassTextViewextendsViewimplementsViewTreeObserver.OnPreDrawListener{}
Button的定义
@RemoteView
publicclassButtonextendsTextView{}
ImageView的定义
@RemoteView
publicclassImageViewextendsView{}
ProgressBar的定义
@RemoteView
publicclassProgressBarextendsView{}
LinearLayout的定义
@RemoteView
publicclassLinearLayoutextendsViewGroup{}
EditText的定义
publicclassEditTextextendsTextView{}
不再一一列举,可见EditText虽然是继承自TextView的,但它没有使用@RemoteView注解,因此并不能用作远程视图。
RemoteViews所支持的View类型如下:
LinearLayout、RelativeLayout、FrameLayout、GridLayout、AbsoluteLayout(已弃用)
TextView、Button、ImageView、ImageButton、Chronometer、ProgressBar、ListView、GridView、StackView、ViewFlipper、AdapterViewFlipper、ViewStub、AnalogClock(已弃用)
也就是说远程视图只能使用上述所列举的View,它们的子类及其他View都是不支持的,如果使用了不支持的View,则会报异常。
实现Parcelable和Filter接口的意义
Parcelable比较容易理解,就是支持序列化以便于跨进程操作。
那么Filter的作用是什么呢?
Filter接口的定义如下:
/**
*HooktoallowclientsoftheLayoutInflatertorestrictthesetofViewsthatareallowed
*tobeinflated.
*
*/
publicinterfaceFilter{
/**
*HooktoallowclientsoftheLayoutInflatertorestrictthesetofViews
*thatareallowedtobeinflated.
*
*@paramclazzTheclassobjectfortheViewthatisabouttobeinflated
*
*@returnTrueifthisclassisallowedtobeinflated,orfalseotherwise
*/
@SuppressWarnings("unchecked")
booleanonLoadClass(Classclazz);
}
从注释中不难看出Filter是用来限制和过滤View用的,上文提到并非所有的View都能用作远程视图,如果为上述列举的View,则onLoadClass(Classclazz)返回true,否则返回false。
在RemoteViews中实现了Filter接口的方法:
publicbooleanonLoadClass(Classclazz){
returnclazz.isAnnotationPresent(RemoteView.class);
}
可以看到就是根据@RemoteView注解来判断是否可以使用该View作为远程视图。
RemoteViews实现原理
跨进程是哪两个进程
很显然我们的应用自身是一个进程,那么另一个进程是什么呢?
在RemoteViews的应用场景中,如自定义通知栏和桌面小部件,它们都运行在系统进程中,即SystemServer进程。
如此一来,远程视图运行在SystemServer进程中,我们在自己的应用进程中跨进程来操作远程视图。
前文说到RemoteViews实现了Parcelable接口,那么RemoteViews便可以从应用进程传输到系统进程了。
远程View是如何加载的
通常情况下,我们使用LayoutInflater加载布局只需要知道布局ID即可。
还记得前文讲RemoteViews的构造函数时,有两个重要的字段:
mApplication
mLayoutId
在系统进程中加载远程视图正是利用了上述两个字段。
在RemoteViews的源码中加载布局的逻辑如下:
/**
*Inflatestheviewhierarchyrepresentedbythisobjectandapplies
*alloftheactions.
*
*
Callerbeware:
thismaythrow
*
*@paramcontextDefaultcontexttouse
*@paramparentParentthattheresultingviewhierarchywillbeattachedto.Thismethod
*doesnotattachthehierarchy.Thecallershoulddosowhenappropriate.
*@returnTheinflatedviewhierarchy
*/
publicViewapply(Contextcontext,ViewGroupparent){
returnapply(context,parent,null);
}
接下来会调用:
publicViewapply(Contextcontext,ViewGroupparent,OnClickHandlerhandler){
RemoteViewsrvToApply=getRemoteViewsToApply(context);
Viewresult=inflateView(context,rvToApply,parent);
loadTransitionOverride(context,handler);
rvToApply.performApply(result,parent,handler);
returnresult;
}
先重点关注下面这一行代码:
Viewresult=inflateView(context,rvToApply,parent);
其内部实现就是常见的布局加载方式了:
privateViewinflateView(Contextcontext,RemoteViewsrv,ViewGroupparent){
//RemoteViewsmaybebuiltbyanapplicationinstalledinanother
//user.Sobuildacontextthatloadsresourcesfromthatuserbut
//stillreturnsthecurrentusersuserIdsosettingslikedata/timeformats
//areloadedwithoutrequiringcrossuserpersmissions.
finalContextcontextForResources=getContextForResources(context);
ContextinflationContext=newContextWrapper(context){
@Override
publicResourcesgetResources(){
returncontextForResources.getResources();
}
@Override
publicResources.ThemegetTheme(){
returncontextForResources.getTheme();
}
@Override
publicStringgetPackageName(){
returncontextForResources.getPackageName();
}
};
LayoutInflaterinflater=(LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//Cloneinflatersoweloadresourcesfromcorrectcontextand
//wedon'taddafiltertothestaticversionreturnedbygetSystemService.
inflater=inflater.cloneInContext(inflationContext);
inflater.setFilter(this);
returninflater.inflate(rv.getLayoutId(),parent,false);
}
主要关注最后几行代码即可,把握主要流程,一些细节可以暂时忽略。
RemoteViews的apply方法中还有这样一行代码:
rvToApply.performApply(result,parent,handler);
接下来会调用:
privatevoidperformApply(Viewv,ViewGroupparent,OnClickHandlerhandler){
if(mActions!
=null){
handler=handler==null?
DEFAULT_ON_CLICK_HANDLER:
handler;
finalintcount=mActions.size();
for(inti=0;iActiona=mActions.get(i);
a.apply(v,parent,handler);
}
}
}
即遍历RemoteViews中存储的Action,然后执行Action的apply方法。
远程View是如何操作的
上一小节提到了Action对象,那么Action又是什么呢?
由于RemoteViews运行在远端进程中,因此无法通过findViewById的方法来获取View。
为了操作远程视图,于是就将对视图的操作封装成一个Action对象,Action是一个实现了Parcelable接口的抽象类,因此可以跨进程传输。
先来看下Action的定义:
/**
*Baseclassforallactionsthatcanbeperformedonan
*inflatedview.
*
*SUBCLASSESMUSTBEIMMUTABLESOCLONEWORKS!
!
!
!
!
*/
privateabstractstaticclassActionimplementsParcelable{
publicabstractvoidapply(Viewroot,ViewGrouprootParent,
OnClickHandlerhandler)throwsActionException;
publicstaticfinalintMERGE_REPLACE=0;
publicstaticfinalintMERGE_APPEND=1;
publicstaticfinalintMERGE_IGNORE=2;
publicintdescribeContents(){
return0;
}
/**
*Overriddenbyeachclasstoreportonit'sownmemoryusage
*/
publicvoidupdateMemoryUsageEstimate(MemoryUsageCountercounter){
//WecurrentlyonlycalculateBitmapmemoryusage,sobydefault,
//don'tdoanythinghere
}
publicvoidsetBitmapCache(BitmapCachebitmapCache){
//Donothing
}
publicintmergeBehavior(){
returnMERGE_REPLACE;
}
publicabstractStringgetActionName();
publicStringgetUniqueKey(){
return(getActionName()+viewId);
}
/**
*Thisiscalledonthebackgroundthread.Itshouldperformanynon-uicomputations
*andreturnthefinalonwhichwillrunontheUIthread.
*Overridethisifsomeofthetaskscanbeperformedasync.
*/
publicActioninitActionAsync(ViewTreeroot,ViewGrouprootParent,OnClickHandlerhandler){
returnthis;
}
intviewId;
}
从说明来看,Action就是对远程视图操作的一个封装,它提供了一个抽象方法:
publicabstractvoidapply(Viewroot,ViewGrouprootParent,
OnClickHandlerhandler)throwsActionException;
该抽象方法需要子类做具体实现。
Action有很多子类,几乎每个子类都用来辅助一种View操作,下面简单罗列两个:
/**
*HelperactiontosetcompounddrawablesonaTextView.Supportsrelative
*(s/t/e/b)orcardinal(l/t/r/b)arrangement.
*/
privateclassTextViewDrawableActionextendsAction{}
/**
*HelperactiontosettextsizeonaTextViewinanysupportedunits.
*/
privateclassTextViewSizeActionextendsAction{}
这里不再一一列举,有兴趣的可以参考源码。
了解了Action的概念之后,我们以为远程的TextView设置文本为例,来具体看一下其工作流程。
平时我们给TextView设置文本只需要调用其setText方法即可,但RemoteViews无法这样使用,RemoteViews提供了一系列关于View操作的set方法,这里会用到如下方法:
/**