在我们开发中我们在xml
里面写布局
<ImageView
android:src="@mipmap/ic_launcher_round"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
我们的src
中赋值@mipmap/ic_launcher_round
,然后就会得到对应的资源,那么有没有好奇我们这些资源是怎么加载进去的呢?我们点进ImageView去查看:
final Drawable d = a.getDrawable(R.styleable.ImageView_src);
if (d != null) {
setImageDrawable(d);
}
@Nullable
public Drawable getDrawable(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
return mResources.loadDrawable(value, value.resourceId, mTheme);
}
return null;
}
mResources.loadDrawable(value, value.resourceId, mTheme);
调用的是mResources
的loadDrawable
方法,这样就获取了相应的值。下面我们通过来分析Resources
是怎么创建和寻找对应的资源的。如果我们想要用自己的Resources
来加载资源,我们应该怎么做呢?
我们从Activity
的getResources()
里面入手:
private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
可以发现这个mResources
是在Context
里面获取到的,在Context
里面:
/**
* Returns a Resources instance for the application's package.
* <p>
* <strong>Note:</strong> Implementations of this method should return
* a Resources instance that is consistent with the AssetManager instance
* returned by {@link #getAssets()}. For example, they should share the
* same {@link Configuration} object.
*
* @return a Resources instance for the application's package
* @see #getAssets()
*/
public abstract Resources getResources();
这是一个抽象方法,从注释上我们可以知道这个应该和getAssets()
,也就是和AssetManager
有关,我们找到Context
的实现类ContextImpl
查看:
@Override
public Resources getResources() {
return mResources;
}
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
//....
Resources resources = packageInfo.getResources(mainThread);
if (resources != null) {
if (displayId != Display.DEFAULT_DISPLAY
|| overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) {
resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(),
packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId,
overrideConfiguration, compatInfo);
}
}
mResources = resources;
//....
}
发现是ResourcesManager
里面调用了getTopLevelResources
函数,点进去:
/**
* Creates the top level Resources for applications with the given compatibility info.
*
* @param resDir the resource directory.
* @param splitResDirs split resource directories.
* @param overlayDirs the resource overlay directories.
* @param libDirs the shared library resource dirs this app references.
* @param displayId display Id.
* @param overrideConfiguration override configurations.
* @param compatInfo the compatibility info. Must not be null.
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, int displayId,
Configuration overrideConfiguration, CompatibilityInfo compatInfo) {
//.....
Configuration overrideConfigCopy = (overrideConfiguration != null)
? new Configuration(overrideConfiguration) : null;
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
Resources r;
synchronized (this) {
// Resources is app scale dependent.
if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
//if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale
+ " key=" + key + " overrideConfig=" + overrideConfiguration);
return r;
}
}
AssetManager assets = new AssetManager();
// resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package
// already.
if (resDir != null) {
if (assets.addAssetPath(resDir) == 0) {
return null;
}
}
if (splitResDirs != null) {
for (String splitResDir : splitResDirs) {
if (assets.addAssetPath(splitResDir) == 0) {
return null;
}
}
}
if (overlayDirs != null) {
for (String idmapPath : overlayDirs) {
assets.addOverlayPath(idmapPath);
}
}
if (libDirs != null) {
for (String libDir : libDirs) {
if (libDir.endsWith(".apk")) {
// Avoid opening files we know do not have resources,
// like code-only .jar files.
if (assets.addAssetPath(libDir) == 0) {
Log.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
}
}
}
}
//Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics dm = getDisplayMetricsLocked(displayId);
Configuration config;
final 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);
if (DEBUG) Slog.v(TAG, "Applied overrideConfig=" + key.mOverrideConfiguration);
}
} else {
config = getConfiguration();
}
r = new Resources(assets, dm, config, compatInfo);
//....
return r;
}
}
从这段代码我们就可以知道,如果缓存里面有对应的Resources
就直接返回,如果没有就直接new
出来,对应的资源路径就是通过AssetManager.addAssetPath(String path)
函数设置,也验证了上面说的那句话,Resources
和AssetManager
有关。
AssetManager的创建
我们进入AssetManager
类中查看addAssetPath
函数:
/**
* 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) {
synchronized (this) {
int res = addAssetPathNative(path);
makeStringBlocks(mStringBlocks);
return res;
}
}
private native final int addAssetPathNative(String path);
可以看到调用了一个native
方法,从注释我们可以知道,这个路径对应的可以是一个目录或者一个zip
文件。我们看看AssetManager
的构造方法
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
* appropriate asset manager with {@link Resources#getAssets}. Not for
* use by applications.
* {@hide}
*/
public AssetManager() {
synchronized (this) {
if (DEBUG_REFS) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
init(false);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
}
}
我们可以查看到addAssetPath
这个方法和AssetManager
的构造方法注释上面都有{@hide}
,这个表示我们不能在应用层直接调用,如果我们还是要调用的话就需要用到反射:
//创建一个AssetManager
//AssetManager assetManager = new AssetManager(); hide的调用不了 只有用反射调用
AssetManager assetManager = AssetManager.class.newInstance();
//添加本地下载好的资源
// assetManager.addAssetPath(String path);// 也是hide的调用不了 继续用反射执行该方法
Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.invoke(assetManager, resPath);
通过前面的分析可知,Android系统中实际对资源的管理是AssetManager类.每个Resources对象都会关联一个AssetManager对象,Resources将对资源的操作大多数委托给了AssetManager。当然有些源码还有一层 ResourcesImpl 刚刚我们也看到了。
另外还会存在一个native层的AssetManager对象与java层的这个AssetManager对象相对应,而这个native层AssetManager对象在内存的地址存储在java层的AssetManager.mObject中。所以在java层AssetManager的jni方法中可以快速找到它对应的native层的AssetManager对象。
AssetManager的init()
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
* appropriate asset manager with {@link Resources#getAssets}. Not for
* use by applications.
* {@hide}
*/
public AssetManager() {
synchronized (this) {
if (DEBUG_REFS) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
init(false);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
}
}
// ndk的源码路径
// frameworks/base/core/jni/android_util_AssetManager.cpp
// frameworks/base/libs/androidfw/AssetManager.cpp
private native final void init(boolean isSystem);
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
if (isSystem) {
verifySystemIdmaps();
}
// AssetManager.cpp
AssetManager* am = new AssetManager();
if (am == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "");
return;
}
am->addDefaultAssets();
ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}
bool AssetManager::addDefaultAssets()
{
const char* root = getenv("ANDROID_ROOT");
LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
String8 path(root);
// framework/framework-res.apk
// 初始化的时候会去加载系统的framework-res.apk资源
// 也就是说我们为什么能加载系统的资源如颜色、图片、文字等等
path.appendPath(kSystemAssets);
return addAssetPath(path, NULL);
}
AssetManager
的addAssetPath(String path)
方法
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
asset_path ap;
// 省略一些校验代码
// 判断是否已经加载过了
for (size_t i=0; i<mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == ap.path) {
if (cookie) {
*cookie = static_cast<int32_t>(i+1);
}
return true;
}
}
// 检查路径是否有一个androidmanifest . xml
Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
kAndroidManifest, Asset::ACCESS_BUFFER, ap);
if (manifestAsset == NULL) {
// 如果不包含任何资源
delete manifestAsset;
return false;
}
delete manifestAsset;
// 添加
mAssetPaths.add(ap);
// 新路径总是补充到最后
if (cookie) {
*cookie = static_cast<int32_t>(mAssetPaths.size());
}
if (mResources != NULL) {
appendPathToResTable(ap);
}
return true;
}
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
// skip those ap's that correspond to system overlays
if (ap.isSystemOverlay) {
return true;
}
Asset* ass = NULL;
ResTable* sharedRes = NULL;
bool shared = true;
bool onlyEmptyResources = true;
MY_TRACE_BEGIN(ap.path.string());
// 资源覆盖机制,暂不考虑
Asset* idmap = openIdmapLocked(ap);
size_t nextEntryIdx = mResources->getTableCount();
ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
// 资源包路径不是一个文件夹,那就是一个apk文件了
if (ap.type != kFileTypeDirectory) {
// 对于app来说,第一次执行时,肯定为0,因为mResources刚创建,还没对其操作
// 下面的分支 指挥在参数是系统资源包路径时,才执行,
// 而且系统资源包路径是首次被解析的
// 第二次执行appendPathToResTable,nextEntryIdx就不会为0了
if (nextEntryIdx == 0) {
// mAssetPaths中存储的第一个资源包路径是系统资源的路径,
// 即framework-res.apk的路径,它在zygote启动时已经加载了
// 可以通过mZipSet.getZipResourceTable获得其ResTable对象
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTable(ap.path);
// 对于APP来说,肯定不为NULL
if (sharedRes != NULL) {
// 得到系统资源包路径中resources.arsc个数
nextEntryIdx = sharedRes->getTableCount();
}
}
// 当参数是mAssetPaths中除第一个以外的其他资源资源包路径,
// 比如app自己的资源包路径时,走下面的逻辑
if (sharedRes == NULL) {
// 检查该资源包是否被其他进程加载了,这与ZipSet数据结构有关,后面在详细介绍
ass = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTableAsset(ap.path);
// 对于app自己的资源包来说,一般都会都下面的逻辑
if (ass == NULL) {
ALOGV("loading resource table %s\n", ap.path.string());
// 创建Asset对象,就是打开resources.arsc
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
if (ass != NULL && ass != kExcludedAsset) {
ass = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
// 只有在zygote启动时,才会执行下面的逻辑
// 为系统资源创建 ResTable,并加入到mZipSet里。
if (nextEntryIdx == 0 && ass != NULL) {
// If this is the first resource table in the asset
// manager, then we are going to cache it so that we
// can quickly copy it out for others.
ALOGV("Creating shared resources for %s", ap.path.string());
// 创建ResTable对象,并把前面与resources.arsc关联的Asset对象,加入到这个ResTabl中
sharedRes = new ResTable();
sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else {
ALOGV("loading resource table %s\n", ap.path.string());
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
shared = false;
}
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
// 系统资源包时
if (sharedRes != NULL) {
ALOGV("Copying existing resources for %s", ap.path.string());
mResources->add(sharedRes);
} else {
// 非系统资源包时,将与resources.arsc关联的Asset对象加入到Restable中
// 此过程会解析resources.arsc文件。
ALOGV("Parsing resources for %s", ap.path.string());
mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
}
onlyEmptyResources = false;
if (!shared) {
delete ass;
}
} else {
mResources->addEmpty(nextEntryIdx + 1);
}
if (idmap != NULL) {
delete idmap;
}
MY_TRACE_END();
return onlyEmptyResources;
}
大家应该之前了解过这个文件resources.arsc
, 如果没了解过可以在网上找篇文章看一下。apk
在打包的时候会生成它,我们解压apk
就应该能够看到他。这里面基本都是存放的资源的索引,之所以不同的分辨率可以加载不同的图片它可是个大功臣。
我们获取到了assetManager
其他的都好办了,因为资源地址是由这个决定的,所以我们其他的都可以用当前app
本身的
//创建一个AssetManager
//AssetManager assetManager = new AssetManager(); hide的调用不了 只有用反射调用
AssetManager assetManager = AssetManager.class.newInstance();
//添加本地下载好的资源皮肤
// assetManager.addAssetPath(String path);// 也是hide的调用不了 继续用反射执行该方法
Method addAssetPathMethod = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
addAssetPathMethod.invoke(assetManager, resPath);
Resources superResources = context.getResources();
Resources mResources = new Resources(assetManager, superResources.getDisplayMetrics(), superResources.getConfiguration());
这样的话我们就可以通过自己创建Resources
来加载非本身apk的资源了
`