当前位置: 移动技术网 > IT编程>移动开发>Android > Android源码:资源加载机制

Android源码:资源加载机制

2018年12月11日  | 移动技术网IT编程  | 我要评论

小蚂蚁信息网,歌曲凑热闹,liuliangji

前言

我们知道,在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就是典型的例子。

[java] view plain copy

 

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);

}

weakreferencewr=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,”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){

weakreferencewr=mactiveresources.get(key);

resourcesexisting=wr!=null?wr.get():null;

if(existing!=null&&existing.getassets().isuptodate()){

//someoneelsealreadycreatedtheresourceswhilewewere

//unlocked;goaheadandusetheirs.

r.getassets().close();

returnexisting;

}

//xxxneedtoremoveentrieswhenweakreferencesgoaway

mactiveresources.put(key,newweakreference(r));

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 copy

 

print?

 

/**

*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(token);

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 copy

 

print?

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 copy

 

print?

/**

*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 copy

 

print?

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等,欢迎大家交流。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网