Android源码:资源加载机制
前言
我们知道,在activity内部访问资源(字符串,图片等)是很简单的,只要getresources然后就可以得到resources对象,有了resources对象就可以访问各种资源了,这很简单,不过本文不是介绍这个的,本文主要介绍在这套逻辑之下的资源加载机制
资源加载机制
很明确,不同的context得到的都是同一份资源。这是很好理解的,请看下面的分析
得到资源的方式为context.getresources,而真正的实现位于contextimpl中的getresources方法,在contextimpl中有一个成员 private resources mresources,它就是getresources方法返回的结果,mresources的赋值代码为:
mresources = mresourcesmanager.gettoplevelresources(mpackageinfo.getresdir(),
display.default_display, null, compatinfo, activitytoken);
下面看一下resourcesmanager的gettoplevelresources方法,这个方法的思想是这样的:在resourcesmanager中,所有的资源对象都被存储在arraymap中,首先根据当前的请求参数去查找资源,如果找到了就返回,否则就创建一个资源对象放到arraymap中。有一点需要说明的是为什么会有多个资源对象,原因很简单,因为res下可能存在多个适配不同设备、不同分辨率、不同版本的目录,按照android系统的设计,不同设备在访问同一个应用的时候访问的资源可以不同,比如drawable-hdpi和drawable-xhdpi就是典型的例子。
print?
publicresourcesgettoplevelresources(stringresdir,intdisplayid,
configurationoverrideconfiguration,compatibilityinfocompatinfo,ibindertoken){
finalfloatscale=compatinfo.applicationscale;
resourceskeykey=newresourceskey(resdir,displayid,overrideconfiguration,scale,
token);
resourcesr;
synchronized(this){
//resourcesisappscaledependent.
if(false){
slog.w(tag,”gettoplevelresources:”+resdir+“/”+scale);
}
weakreference
r=wr!=null?wr.get():null;
//if(r!=null)slog.i(tag,“isuptodate”+resdir+”:”+r.getassets().isuptodate());
if(r!=null&&r.getassets().isuptodate()){
if(false){
slog.w(tag,”returningcachedresources”+r+“”+resdir
+”:appscale=”+r.getcompatibilityinfo().applicationscale);
}
returnr;
}
}
//if(r!=null){
//slog.w(tag,“throwingawayout-of-dateresources!!!!”
//+r+””+resdir);
//}
assetmanagerassets=newassetmanager();
if(assets.addassetpath(resdir)==0){
returnnull;
}
//slog.i(tag,“resource:key=”+key+”,displaymetrics=”+metrics);
displaymetricsdm=getdisplaymetricslocked(displayid);
configurationconfig;
booleanisdefaultdisplay=(displayid==display.default_display);
finalbooleanhasoverrideconfig=key.hasoverrideconfiguration();
if(!isdefaultdisplay||hasoverrideconfig){
config=newconfiguration(getconfiguration());
if(!isdefaultdisplay){
applynondefaultdisplaymetricstoconfigurationlocked(dm,config);
}
if(hasoverrideconfig){
config.updatefrom(key.moverrideconfiguration);
}
}else{
config=getconfiguration();
}
r=newresources(assets,dm,config,compatinfo,token);
if(false){
slog.i(tag,”createdappresources”+resdir+“”+r+“:”
+r.getconfiguration()+”appscale=”
+r.getcompatibilityinfo().applicationscale);
}
synchronized(this){
weakreference
resourcesexisting=wr!=null?wr.get():null;
if(existing!=null&&existing.getassets().isuptodate()){
//someoneelsealreadycreatedtheresourceswhilewewere
//unlocked;goaheadandusetheirs.
r.getassets().close();
returnexisting;
}
//xxxneedtoremoveentrieswhenweakreferencesgoaway
mactiveresources.put(key,newweakreference
returnr;
}
}
public resources gettoplevelresources(string resdir, int displayid, configuration overrideconfiguration, compatibilityinfo compatinfo, ibinder token) { final float scale = compatinfo.applicationscale; resourceskey key = new resourceskey(resdir, displayid, overrideconfiguration, scale, token); resources r; synchronized (this) { // resources is app scale dependent. if (false) { slog.w(tag, "gettoplevelresources: " + resdir + " / " + scale); } weakreference wr = mactiveresources.get(key); r = wr != null ? wr.get() : null; //if (r != null) slog.i(tag, "isuptodate " + resdir + ": " + r.getassets().isuptodate()); if (r != null && r.getassets().isuptodate()) { if (false) { slog.w(tag, "returning cached resources " + r + " " + resdir + ": appscale=" + r.getcompatibilityinfo().applicationscale); } return r; } } //if (r != null) { // slog.w(tag, "throwing away out-of-date resources!!!! " // + r + " " + resdir); //} assetmanager assets = new assetmanager(); if (assets.addassetpath(resdir) == 0) { return null; } //slog.i(tag, "resource: key=" + key + ", display metrics=" + metrics); displaymetrics dm = getdisplaymetricslocked(displayid); configuration config; boolean isdefaultdisplay = (displayid == display.default_display); final boolean hasoverrideconfig = key.hasoverrideconfiguration(); if (!isdefaultdisplay || hasoverrideconfig) { config = new configuration(getconfiguration()); if (!isdefaultdisplay) { applynondefaultdisplaymetricstoconfigurationlocked(dm, config); } if (hasoverrideconfig) { config.updatefrom(key.moverrideconfiguration); } } else { config = getconfiguration(); } r = new resources(assets, dm, config, compatinfo, token); if (false) { slog.i(tag, "created app resources " + resdir + " " + r + ": " + r.getconfiguration() + " appscale=" + r.getcompatibilityinfo().applicationscale); } synchronized (this) { weakreference wr = mactiveresources.get(key); resources existing = wr != null ? wr.get() : null; if (existing != null && existing.getassets().isuptodate()) { // someone else already created the resources while we were // unlocked; go ahead and use theirs. r.getassets().close(); return existing; } // xxx need to remove entries when weak references go away mactiveresources.put(key, new weakreference(r)); return r; } }根据上述代码中资源的请求机制,再加上resourcesmanager采用单例模式,这样就保证了不同的contextimpl访问的是同一套资源,注意,这里说的同一套资源未必是同一个资源,因为资源可能位于不同的目录,但它一定是我们的应用的资源,或许这样来描述更准确,在设备参数和显示参数不变的情况下,不同的contextimpl访问到的是同一份资源。设备参数不变是指手机的屏幕和android版本不变,显示参数不变是指手机的分辨率和横竖屏状态。也就是说,尽管application、activity、service都有自己的contextimpl,并且每个contextimpl都有自己的mresources成员,但是由于它们的mresources成员都来自于唯一的resourcesmanager实例,所以它们看似不同的mresources其实都指向的是同一块内存(c语言的概念),因此,它们的mresources都是同一个对象(在设备参数和显示参数不变的情况下)。在横竖屏切换的情况下且应用中为横竖屏状态提供了不同的资源,处在横屏状态下的contextimpl和处在竖屏状态下的contextimpl访问的资源不是同一个资源对象。
代码:单例模式的resourcesmanager类
[java] view plain copy
print?
publicstaticresourcesmanagergetinstance(){
synchronized(resourcesmanager.class){
if(sresourcesmanager==null){
sresourcesmanager=newresourcesmanager();
}
returnsresourcesmanager;
}
}
public static resourcesmanager getinstance() { synchronized (resourcesmanager.class) { if (sresourcesmanager == null) { sresourcesmanager = new resourcesmanager(); } return sresourcesmanager; } }
resources对象的创建过程
通过resources类的可以知道,resources对资源的访问实际上是通过assetmanager来实现的,那么如何创建一个resources对象呢,有人会问,我为什么要去创建一个resources对象呢,直接getresources不就可以了吗?我要说的是在某些特殊情况下你的确需要去创建一个资源对象,比如动态加载apk。很简单,首先看一下它的几个构造方法:
[java] view plain copyprint?
/**
*createanewresourcesobjectontopofanexistingsetofassetsinan
*assetmanager.
*
*@paramassetspreviouslycreatedassetmanager.
*@parammetricscurrentdisplaymetricstoconsiderwhen
*selecting/computingresourcevalues.
*@paramconfigdesireddeviceconfigurationtoconsiderwhen
*selecting/computingresourcevalues(optional).
*/
publicresources(assetmanagerassets,displaymetricsmetrics,configurationconfig){
this(assets,metrics,config,compatibilityinfo.default_compatibility_info,null);
}
/**
*createsanewresourcesobjectwithcompatibilityinfo.
*
*@paramassetspreviouslycreatedassetmanager.
*@parammetricscurrentdisplaymetricstoconsiderwhen
*selecting/computingresourcevalues.
*@paramconfigdesireddeviceconfigurationtoconsiderwhen
*selecting/computingresourcevalues(optional).
*@paramcompatinfothisresource’scompatibilityinfo.mustnotbenull.
*@paramtokentheactivitytokenfordeterminingstackaffiliation.usuallynull.
*@hide
*/
publicresources(assetmanagerassets,displaymetricsmetrics,configurationconfig,
compatibilityinfocompatinfo,ibindertoken){
massets=assets;
mmetrics.settodefaults();
if(compatinfo!=null){
mcompatibilityinfo=compatinfo;
}
mtoken=newweakreference
updateconfiguration(config,metrics);
assets.ensurestringblocks();
}
/** * create a new resources object on top of an existing set of assets in an * assetmanager. * * @param assets previously created assetmanager. * @param metrics current display metrics to consider when * selecting/computing resource values. * @param config desired device configuration to consider when * selecting/computing resource values (optional). */ public resources(assetmanager assets, displaymetrics metrics, configuration config) { this(assets, metrics, config, compatibilityinfo.default_compatibility_info, null); } /** * creates a new resources object with compatibilityinfo. * * @param assets previously created assetmanager. * @param metrics current display metrics to consider when * selecting/computing resource values. * @param config desired device configuration to consider when * selecting/computing resource values (optional). * @param compatinfo this resource's compatibility info. must not be null. * @param token the activity token for determining stack affiliation. usually null. * @hide */ public resources(assetmanager assets, displaymetrics metrics, configuration config, compatibilityinfo compatinfo, ibinder token) { massets = assets; mmetrics.settodefaults(); if (compatinfo != null) { mcompatibilityinfo = compatinfo; } mtoken = new weakreference(token); updateconfiguration(config, metrics); assets.ensurestringblocks(); }除了这两个构造方法还有一个私有的无参方法,由于是私有的,所以没法访问。上面两个构造方法,从简单起见,我们应该采用第一个
public resources(assetmanager assets, displaymetrics metrics, configuration config)
它接受3个参数,第一个是assetmanager,后面两个是和设备相关的配置参数,我们可以直接用当前应用的配置就好,所以,问题的关键在于如何创建assetmanager,下面请看分析,为了创建一个我们自己的assetmanager,我们先去看看系统是怎么创建的。还记得getresources的底层实现吗,在resourcesmanager的gettoplevelresources方法中有这么两句:
[java] view plain copyprint?
assetmanagerassets=newassetmanager();
if(assets.addassetpath(resdir)==0){
returnnull;
}
assetmanager assets = new assetmanager(); if (assets.addassetpath(resdir) == 0) { return null; }
这两句就是创建一个assetmanager对象,后面会用这个对象来创建resources对象,ok,assetmanager就是这么创建的,assets.addassetpath(resdir)这句话的意思是把资源目录里的资源都加载到assetmanager对象中,具体的实现在jni中,大家感兴趣自己去了解下。而资源目录就是我们的res目录,当然resdir可以是一个目录也可以是一个zip文件。有没有想过,如果我们把一个未安装的apk的路径传给这个方法,那么apk中的资源是不是就被加载到assetmanager对象里面了呢?事实证明,的确是这样。addassetpath方法的定义如下,注意到它的注释里面有一个{@hide}关键字,这意味着即使它是public的,但是外界仍然无法访问它,因为android sdk导出的时候会自动忽略隐藏的api,因此只能通过反射来调用。
[java] view plain copyprint?
/**
*addanadditionalsetofassetstotheassetmanager.thiscanbe
*eitheradirectoryorzipfile.notforusebyapplications.returns
*thecookieoftheaddedasset,or0onfailure.
*{@hide}
*/
publicfinalintaddassetpath(stringpath){
intres=addassetpathnative(path);
returnres;
}
/** * add an additional set of assets to the asset manager. this can be * either a directory or zip file. not for use by applications. returns * the cookie of the added asset, or 0 on failure. * {@hide} */ public final int addassetpath(string path) { int res = addassetpathnative(path); return res; }
有了assetmanager对象后,我们就可以创建自己的resources对象了,代码如下:
[java] view plain copyprint?
try{
assetmanagerassetmanager=assetmanager.class.newinstance();
methodaddassetpath=assetmanager.getclass().getmethod(”addassetpath”,string.class);
addassetpath.invoke(assetmanager,mdexpath);
massetmanager=assetmanager;
}catch(exceptione){
e.printstacktrace();
}
resourcescurrentres=this.getresources();
mresources=newresources(massetmanager,currentres.getdisplaymetrics(),
currentres.getconfiguration());
try { assetmanager assetmanager = assetmanager.class.newinstance(); method addassetpath = assetmanager.getclass().getmethod("addassetpath", string.class); addassetpath.invoke(assetmanager, mdexpath); massetmanager = assetmanager; } catch (exception e) { e.printstacktrace(); } resources currentres = this.getresources(); mresources = new resources(massetmanager, currentres.getdisplaymetrics(), currentres.getconfiguration());有了resources对象,我们就可以通过resources对象来访问里面的各种资源了,通过这种方法,我们可以完成一些特殊的功能,比如换肤、换语言包、动态加载apk等,欢迎大家交流。
上一篇: layui表格数据复选框回显设置方法
下一篇: 深入Linux内核架构——锁与进程间通信
推荐阅读
-
Android源码:资源加载机制
-
Android 加载assets中的资源文件实例代码
-
Android 游戏引擎libgdx 资源加载进度百分比显示案例分析
-
android的消息处理机制(图文+源码分析)—Looper/Handler/Message
-
Android图片加载利器之Picasso源码解析
-
Android中加载网络资源时的优化可使用(线程+缓存)解决
-
Android 游戏引擎libgdx 资源加载进度百分比显示案例分析
-
android的消息处理机制(图文+源码分析)—Looper/Handler/Message
-
Android在多种设计下实现懒加载机制的方法
-
Android图片加载利器之Picasso源码解析