关闭

Android源码分析-资源加载机制

标签: androidResourcesManager资源加载
22876人阅读 评论(20) 收藏 举报
分类:

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

前言

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

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。很简单,首先看一下它的几个构造方法:

    /**
     * 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}关键字,这意味着即使它是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等,欢迎大家交流。

32
4
查看评论

Android动态加载—Res文件

简介动态加载res文件,就是将资源文件打包进一个统一的apk,而在我们的app安装后通过后台下载,然后再写到我们的应用中,将这个含有资源的apk,可以称为插件apk,而我们的app,可以称为宿主apk,在宿主中加载插件中的资源文件的方法。介绍 下载 加载 获取资源文件 对于下载可以参考上一篇so文件...
  • u012858313
  • u012858313
  • 2016-04-05 21:33
  • 2365

Android应用程序资源管理器(Asset Manager)的创建过程分析

在前面一篇文章中,我们分析了Android应用程序资源的编译和打包过程,最终得到的应用程序资源就与应用程序代码一起打包在一个APK文件中。Android应用程序在运行的过程中,是通过一个称为AssetManager的资源管理器来读取打包在APK文件里面的资源文件的。在本文中,我们就将详细分析Andr...
  • Luoshengyang
  • Luoshengyang
  • 2013-04-22 00:56
  • 75833

Android 资源加载机制详解

Android提供了一种非常灵活的资源系统,可以根据不同的条件提供可替代资源。因此,系统基于很少的改造就能支持新特性,比如Android N中的分屏模式。这也是Android强大部分之一。本文主要讲述Android资源系统的实现原理,以及在应用开发中需要注意的事项。
  • thesingularityisnear
  • thesingularityisnear
  • 2016-06-03 19:43
  • 3292

Android App启动时Apk资源加载机制源码分析

在Andorid开发中我们要设置文字或图片显示,都直接通过Api一步调用就完成了,不仅是我们工程下res资源以及系统自带的framwork资源也可以,那这些资源打包成Apk之后是如何被系统加载从而显示出来的呢。
  • u010019468
  • u010019468
  • 2017-06-23 11:27
  • 1216

Android 动态加载 之 如何获取插件res资源

直接上干货: 要获取插件的资源信息我们采用的是通过实例packageInfo,并动态的更改里边的sourceDir和publicSourceDir为插件Apk路径地址。 而这里关键的是什么时候去加载的资源信息呢: 1,pm.getPackageArchiveInfo 如图,在执行取packag...
  • yangdeli888
  • yangdeli888
  • 2015-04-21 17:40
  • 4780

Android 资源管理利器Resources和AssetManager介绍

Android如何加载第三方的资源,Resources与AssetManager获取资源的机制,本文教你如何快速了解
  • MeteorLuoyidong
  • MeteorLuoyidong
  • 2015-10-31 10:19
  • 1639

Android资源访问机制

Android经常使用getResources()方法获取app的一些资源,getResource()方法是Context接口的方法,具体是有ContextImpl类实现的,Activity、Service、Application都是继承自Context接口。      ...
  • linghu_java
  • linghu_java
  • 2015-01-13 17:22
  • 3014

Android学习第二课:Assets资源文件读取及AssetManager介绍

Assets资源介绍,以及AssetManager API小结
  • hitgaoxing
  • hitgaoxing
  • 2015-06-20 11:34
  • 3758

Android动态资源加载原理和应用

动态加载资源原理 通常我们调用getResources()方法获取资源文件 public Resources getResources() { return mResources; }mResources是在创建ContextImp对象后的init方法里面创建的 mResources = ...
  • cauchyweierstrass
  • cauchyweierstrass
  • 2016-04-05 20:45
  • 6944

Android资源加载源码分析

这篇文章主要介绍Android加载资源的主要流程
  • u012170463
  • u012170463
  • 2016-05-15 16:47
  • 1041
    我的书
    微信公众号
    公众号
    聚焦于『Android开发前沿、AI技术、职业发展、生活感悟、妹子图』,欢迎大家关注。
    一句话介绍自己
    Android 资深工程师、混过腾讯、百度和滴滴,《Android开发艺术探索》作者
    爱生活,爱技术,爱妹子,爱游戏
    1群:215680213(已满)
    2群:190283084(已满)
    8群:635778578

    我的联系方式
    QQ:289832127
    singwhatiwanna@gmail.com

    知识星球 - 欢迎铁粉加入
    公众号
    个人资料
    • 访问:1966104次
    • 积分:16346
    • 等级:
    • 排名:第754名
    • 原创:89篇
    • 转载:2篇
    • 译文:0篇
    • 评论:3091条
    博客专栏
    我的微博
    最新评论