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

时间:2021-12-27 02:11:16

转载请注明出处: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等,欢迎大家交流。

Android源代码分析-资源载入机制的更多相关文章

  1. Android源代码分析之拍照、图片、录音、视频和音频功能

    Android源代码分析之拍照.图片.录音.视频和音频功能   //选择图片 requestCode 返回的标识 Intent innerIntent = new Intent(Intent.ACTI ...

  2. Cordova Android源代码分析系列一(项目总览和CordovaActivity分析)

    版权声明:本文为博主offbye西涛原创文章.未经博主同意不得转载. https://blog.csdn.net/offbye/article/details/31776833 PhoneGap/Co ...

  3. Android Framework 分析---2消息机制Native层

    在Android的消息机制中.不仅提供了供Application 开发使用的java的消息循环.事实上java的机制终于还是靠native来实现的.在native不仅提供一套消息传递和处理的机制,还提 ...

  4. Android源代码分析之Framework的MediaPlayer

    在Android中MediaPlayer用来播放音频和视频文件,在这里分析下在Framework层中MediaPlayer是怎样调用的.MediaPlayer的代码位于:./frameworks/ba ...

  5. android源代码分析 android toast使用具体解释 toast自己定义

    在安卓开发过程中.toast使我们常常使用的一个类.当我们须要向用户传达一些信息,可是不须要和用户交互时,该方式就是一种十分恰当的途径. 我们习惯了这样使用toast:Toast.makeText(C ...

  6. nginx的源代码分析--间接回调机制的使用和类比

    nginx使用了间接回调机制.结合upstream机制的使用来说明一下,首先明白几个事实: 1)事实上ngxin和下游client的连接使用的是ngx_http_connection_t,每一个连接相 ...

  7. 1、android源代码下载及目录分析,和eclipser的跟踪

    1.在eclipse中跟踪源代码:假如对mainactivity.java里面的activity按Ctrl+鼠标左键(前提已经导入android源代码:方法1:在项目点击右键,然后找到properti ...

  8. Cocos2d-x 3&period;0多线程异步资源载入

    Cocos2d-x从2.x版本号到上周刚刚才公布的Cocos2d-x 3.0 Final版,其引擎驱动核心依然是一个单线程的"死循环".一旦某一帧遇到了"大活儿&quot ...

  9. Tomcat7&period;0源代码分析——启动与停止服务原理

    前言 熟悉Tomcat的project师们.肯定都知道Tomcat是怎样启动与停止的. 对于startup.sh.startup.bat.shutdown.sh.shutdown.bat等脚本或者批处 ...

随机推荐

  1. noi题库(noi&period;openjudge&period;cn) 1&period;7编程基础之字符串T31——T35

    T31 字符串P型编码 描述 给定一个完全由数字字符('0','1','2',-,'9')构成的字符串str,请写出str的p型编码串.例如:字符串122344111可被描述为"1个1.2个 ...

  2. sql 联合查询并更新

    update sales set sales.adminId = t.adminIdfrom (select a.adminId,a.realName from sales ,admin_admini ...

  3. php的冒泡算法

    <?php /* 冒泡算法  * @para $arr 传人进去排序的数组  * @return $newArr 排序之后的数组  */   function maopao($arr){     ...

  4. JS高级程序设计学习笔记之第三章基本概念(语法,数据类型,流控制语句,函数)——查漏补缺

    一.语法: 区分大小写; 2.标识符:就是指变量.函数.属性的名字,或者函数的参数 a.标志符的规则:①第一个字符必须是一个字母.下划线(_)或一个美元符号($).                   ...

  5. javaio学习笔记-字符流类&lpar;2&rpar;

    1.java.io包中的字符流类-FileReader和FileWriter: BufferedReader:缓存的输入字符流; BufferedWriter:缓存的输出字符流; FileReader ...

  6. C&num;中几种数据库的大数据批量插入

    C#语言中对SqlServer.Oracle.SQLite和MySql中的数据批量插入是支持的,不过Oracle需要使用Orace.DataAccess驱动. IProvider里有一个用于实现批量插 ...

  7. C语言&lowbar;结构体变量指针做函数参数的使用案例

    # include <stdio.h> # include <stdlib.h> # include <string.h> # include <malloc ...

  8. Tomcat通过Memcached实现session共享的完整部署记录

    对于web应用集群的技术实现而言,最大的难点就是:如何能在集群中的多个节点之间保持数据的一致性,会话(Session)信息是这些数据中最重要的一块.要实现这一点, 大体上有两种方式:一种是把所有Ses ...

  9. numpy&amp&semi;pandas笔记

    1.基础属性: array = np.array([[1,2,3],[2,3,4]]) #列表转化为矩阵 print('number of dim:',array.ndim) # 维度 # numbe ...

  10. oracle 变量作用域

    以下为测试 代码块DECLARE  v_i number := 100;  v_p VARCHAR2(200) := 'a';BEGIN  DECLARE    v_i number := 999;  ...