欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android源码:资源加载机制

程序员文章站 2024-02-01 14:13:22
前言 我们知道,在activity内部访问资源(字符串,图片等)是很简单的,只要getresources然后就可以得到resources对象,有了resources对象就可以访问各种资源了,这很简单...

前言

我们知道,在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等,欢迎大家交流。