• Android源代码分析-资源载入机制


    转载请注明出处:http://blog.csdn.net/singwhatiwanna/article/details/23387079 (来自singwhatiwanna的csdn博客)

    前言

    我们知道,在activity内部訪问资源(字符串,图片等)是非常easy的,仅仅要getResources然后就能够得到Resources对象,有了Resources对象就能够訪问各种资源了,这非常easy,只是本文不是介绍这个的,本文主要介绍在这套逻辑之下的资源载入机制

    资源载入机制

    非常明白,不同的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中。有一点须要说明的是为什么会有多个资源对象,原因非常easy,由于res下可能存在多个适配不同设备、不同分辨率、不同系统版本号的文件夹,依照android系统的设计,不同设备在訪问同一个应用的时候訪问的资源能够不同,比方drawable-hdpi和drawable-xhdpi就是典型的样例。

    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<Resources> 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<Resources> 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<Resources>(r));
    		return r;
    	}
    }
    依据上述代码中资源的请求机制,再加上ResourcesManager採用单例模式,这样就保证了不同的ContextImpl訪问的是同一套资源,注意,这里说的同一套资源未必是同一个资源,由于资源可能位于不同的文件夹,但它一定是我们的应用的资源,也许这样来描写叙述更准确,在设备參数和显示參数不变的情况下,不同的ContextImpl訪问到的是同一份资源。设备參数不变是指手机的屏幕和android版本号不变,显示參数不变是指手机的分辨率和横竖屏状态。也就是说,虽然Application、Activity、Service都有自己的ContextImpl,而且每一个ContextImpl都有自己的mResources成员,可是由于它们的mResources成员都来自于唯一的ResourcesManager实例,所以它们看似不同的mResources事实上都指向的是同一块内存(C语言的概念),因此,它们的mResources都是同一个对象(在设备參数和显示參数不变的情况下)。在横竖屏切换的情况下且应用中为横竖屏状态提供了不同的资源,处在横屏状态下的ContextImpl和处在竖屏状态下的ContextImpl訪问的资源不是同一个资源对象。

    代码:单例模式的ResourcesManager类
        public static ResourcesManager getInstance() {
            synchronized (ResourcesManager.class) {
                if (sResourcesManager == null) {
                    sResourcesManager = new ResourcesManager();
                }
                return sResourcesManager;
            }
        }

    Resources对象的创建过程

    通过阅读Resources类的源代码能够知道,Resources对资源的訪问实际上是通过AssetManager来实现的,那么怎样创建一个Resources对象呢,有人会问,我为什么要去创建一个Resources对象呢,直接getResources不就能够了吗?我要说的是在某些特殊情况下你的确须要去创建一个资源对象,比方动态载入apk。非常easy,首先看一下它的几个构造方法:

        /**
         * 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<IBinder>(token);
            updateConfiguration(config, metrics);
            assets.ensureStringBlocks();
        }
    除了这两个构造方法另一个私有的无參方法,由于是私有的,所以没法訪问。上面两个构造方法,从简单起见,我们应该採用第一个

    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)

    它接受3个參数,第一个是AssetManager,后面两个是和设备相关的配置參数,我们能够直接用当前应用的配置就好,所以,问题的关键在于怎样创建AssetManager,以下请看分析,为了创建一个我们自己的AssetManager,我们先去看看系统是怎么创建的。还记得getResources的底层实现吗,在ResourcesManager的getTopLevelResources方法中有这么两句:

    	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对象里面了呢?事实证明,的确是这样,详细情况能够參见Android apk动态载入机制的研究(二):资源载入和activity生命周期管理这篇文章。addAssetPath方法的定义例如以下,注意到它的凝视里面有一个{@hide}keyword,这意味着即使它是public的,可是外界仍然无法訪问它,由于android sdk导出的时候会自己主动忽略隐藏的api,因此仅仅能通过反射来调用。

        /**
         * 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对象了,代码例如以下:

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

  • 相关阅读:
    Maven项目Spring配置XML报错
    Emcas配置快捷代码块
    ubuntu安装ipython
    基于Docker的集成开发环境包含gvim&Emacs
    deepin安装v11vnc服务
    Eclipse 默认工作区设置
    docker方式搭建DNS服务器
    deepin安装vnc服务
    DataReader类型化数据读取与装箱性能研究
    评《禁止分班考试惹争议:学霸吃不饱,学渣吃不消》:营造公平社会环境从不分班开始
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4313512.html
Copyright © 2020-2023  润新知