在我們開發中我們在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的資源了
`