我们知道,每一个View的子类都可以设置backgroud,那么这个背景是如何加载出来的呢?
找到View的构造方法
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
......
case com.android.internal.R.styleable.View_background:
background = a.getDrawable(attr);
break;
......
}
@Nullable
public Drawable getDrawable(@StyleableRes int index) {
return getDrawableForDensity(index, 0);
}
@Nullable
public Drawable getDrawableForDensity(@StyleableRes int index, int density) {
......
return mResources.loadDrawable(value, value.resourceId, density, mTheme);
......
}
看到这一行
return mResources.loadDrawable(value, value.resourceId, density, mTheme);
进入Resources中
@NonNull
Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
throws NotFoundException {
return mResourcesImpl.loadDrawable(this, value, id, density, theme);
}
可以看到,背景最终是被Resources中的ResourcesImpl加载得到Drawable的,ResourcesImpl在Resources构造中创建出来
@Deprecated
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
private Resources() {
......
mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config,
new DisplayAdjustments());
}
我们再来看Resources是如何创建的,平常我们获取一些资源文件的时候,会这样获取Resources
context.getResources()
我们知道Context是抽象类,所以直接到Context的子类ContextImpl中去找
@Override
public Resources getResources() {
return mResources;
}
那么这个mResources是什么时候创建的,找到这个方法
void setResources(Resources r) {
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
mResources = r;
}
然后看到很多地方调用了这个方法
c.setResources(createResources(mActivityToken, pi, null, displayId, null,
getDisplayAdjustments(displayId).getCompatibilityInfo()));
c.setResources(createResources(mActivityToken, pi, null, displayId, null,
getDisplayAdjustments(displayId).getCompatibilityInfo()));
context.setResources(packageInfo.getResources());
context.setResources(ResourcesManager.getInstance().getResources(
mActivityToken,
mPackageInfo.getResDir(),
paths,
mPackageInfo.getOverlayDirs(),
mPackageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
null,
mPackageInfo.getCompatibilityInfo(),
classLoader));
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
classLoader));
这样的方法有好几个,而且我们找不到其他地方给mResources赋值的地方,初步可以判断,Resources就是这样创建的,顺着这几个方法去看,你会发现,尽管setResources方法有好几种形式,但最后都会进入到ResourcesManger这个类中,这个方法的注释可以看看,Resources是会被缓存的,一个Resources的生命周期和这个Activity同等,当classloader改变,Resources也会改变
/**
* Gets or creates a new Resources object associated with the IBinder token. References returned
* by this method live as long as the Activity, meaning they can be cached and used by the
* Activity even after a configuration change. If any other parameter is changed
* (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
* is updated and handed back to the caller. However, changing the class loader will result in a
* new Resources object.
* <p/>
* If activityToken is null, a cached Resources object will be returned if it matches the
* input parameters. Otherwise a new Resources object that satisfies these parameters is
* returned.
*
* @param activityToken Represents an Activity. If null, global resources are assumed.
* @param resDir The base resource path. Can be null (only framework resources will be loaded).
* @param splitResDirs An array of split resource paths. Can be null.
* @param overlayDirs An array of overlay paths. Can be null.
* @param libDirs An array of resource library paths. Can be null.
* @param displayId The ID of the display for which to create the resources.
* @param overrideConfig The configuration to apply on top of the base configuration. Can be
* null. Mostly used with Activities that are in multi-window which may override width and
* height properties from the base config.
* @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
* {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
* @param classLoader The class loader to use when inflating Resources. If null, the
* {@link ClassLoader#getSystemClassLoader()} is used.
* @return a Resources object from which to access resources.
*/
public @Nullable Resources getResources(@Nullable IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
/**
* Gets an existing Resources object set with a ResourcesImpl object matching the given key,
* or creates one if it doesn't exist.
*
* @param activityToken The Activity this Resources object should be associated with.
* @param key The key describing the parameters of the ResourcesImpl object.
* @param classLoader The classloader to use for the Resources object.
* If null, {@link ClassLoader#getSystemClassLoader()} is used.
* @return A Resources object that gets updated when
* {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
* is called.
*/
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
......
//下边是分两种情况,当activityToken(IBinder)是否为null的情况下根据key获取ResourcesImpl,
//只要这个ResourcesImpl存在,就会直接得到Resources缓存返回或者新创建一个Resources返回
if (activityToken != null) {
......
//根据key获取与之对应的ResourcesImpl缓存
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
......
// 只要根据key获取到的ResourcesImpl不为null,就根据这个ResourcesImpl去获取缓存的
//Resources,如果又这个Resources缓存,就返回,没有就创建,具体看这个方法
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
} else {
......
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
......
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
}
//当程序走到这里的时候,说明ResourcesImpl没有找到,Resources也就没有得到,那么这里就是根据
//key创建出一个ResourcesImpl来,程序第一次运行的时候肯定会首先走到这里,所以,上边的代码可以
//不用太重点的去看,接下来我们看看ResourcesImpl是如何被创建出来的,见方法createResourcesImpl
// If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
synchronized (this) {
ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
if (existingResourcesImpl != null) {
//从缓存中获取
......
resourcesImpl.getAssets().close();
resourcesImpl = existingResourcesImpl;
} else {
// 将创建的ResourcesImpl缓存起来
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
}
final Resources resources;
//在此针对activityToken是否为null分别处理,在getOrCreateResourcesForActivityLocked和getOrCreateResourcesLocked
//这两个方法中,我们重点关注,Resources不存在缓存的情况,所以,最终会看到Resourses的创建,
//见下边的方法
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
//Resources的创建,这里看到根据条件的不同有两种方式获取,一个是new CompatResources,一个是
//new Resources,进入到CompatResources类中,我们看到这个构造最终也会调用Resources的一个构造
//方法public Resources(@Nullable ClassLoader classLoader) 返回Resources,可见这个Resources是new
//出来的
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
//给Resources设置ResourcesImpl
resources.setImpl(impl);
//加入缓存
mResourceReferences.add(new WeakReference<>(resources));
getOrCreateResourcesForActivityLocked
/**
* Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
* or the class loader is different.
*/
private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
@NonNull CompatibilityInfo compatInfo) {
......
//有缓存获取缓存
Resources resources = weakResourceRef.get();
......
return resources;
......
//没有缓存创建出来然后加入缓存
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
activityResources.activityResources.add(new WeakReference<>(resources));
......
return resources;
}
createResourcesImpl,可以看到ResourcesImpl的创建依赖于这几个对象AssetManager,DisplayMetrics,Configuration,DisplayAdjustments
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
final AssetManager assets = createAssetManager(key);
if (assets == null) {
return null;
}
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
final Configuration config = generateConfig(key, dm);
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
......
return impl;
}
着重看AssetManager的创建,这个类很关键
/**
* Creates an AssetManager from the paths within the ResourcesKey.
*
* This can be overridden in tests so as to avoid creating a real AssetManager with
* real APK paths.
* @param key The key containing the resource paths to add to the AssetManager.
* @return a new AssetManager.
*/
@VisibleForTesting
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
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 (key.mResDir != null) {
// 将app中的资源路径都加入到AssetManager对象中,下边的方法都可以不看,我们重点
//关注这个方法,可以说,应用之所以能加载资源,就是通过AssetManager以及调用addAssetPath对他设置的
//资源路径
if (assets.addAssetPath(key.mResDir) == 0) {
Log.e(TAG, "failed to add asset path " + key.mResDir);
return null;
}
}
.....
return assets;
}
getDisplayMetrics,也只是new了一个DisplayMetrics
@VisibleForTesting
protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
DisplayMetrics dm = new DisplayMetrics();
final Display display = getAdjustedDisplay(displayId, da);
if (display != null) {
display.getMetrics(dm);
} else {
dm.setToDefaults();
}
return dm;
}
generateConfig,Configuration也是new出来的
private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
Configuration config;
....
config = new Configuration(getConfiguration());
....
return config;
}
Resources的创建中有一个个关键的类,就是ResourcesImpl,这个类的创建需要几个重要的信息,其中之一就是AssetManager,通过直接实例话一个AssetManager对象并给这个对象设置资源路径,这是#Resources可以获取到文件资源的基础,DisplayMetrics或者Configuration则相当于一些固定设置,AssetManager中设置的这个路径,其实就是我们将要设置的apk的路径,从这个apk中获取资源
如此一来,我们获取到了Resources,就可以自由的去获取资源文件了
那么我们再回到最初,看看Resources是如何loadDrawable的
Resources中
@NonNull
Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
throws NotFoundException {
return mResourcesImpl.loadDrawable(this, value, id, density, theme);
}
ResourcesImpl中
@Nullable
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws NotFoundException {
......
Drawable dr;
boolean needsNewDrawableAfterCache = false;
if (cs != null) {
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {
dr = new ColorDrawable(value.data);
} else {
dr = loadDrawableForCookie(wrapper, value, id, density, null);
}
......
return dr;
......
/**
* Loads a drawable from XML or resources stream.
*/
private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
int id, int density, @Nullable Resources.Theme theme) {
......
final Drawable dr;
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
try {
//如果资源是xml文件
if (file.endsWith(".xml")) {
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme);
rp.close();
} else {
//如果资源是图片资源,打开它得到流,然后解析得到drawable
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
is.close();
}
......
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
return dr;
}
经过上面的分析,皮肤切换的思路已经有了,下载apk文件,这个文件中包含有另一个皮肤的各种资源文件,通过Resources去加载这个apk中的资源,达到换肤的效果,关键代码如下
//点击从手机中一个apk中获取图片资源并且设置给ImageView显示
//获取系统的两个参数
Resources superResources = getResources();
//创建assetManger(无法直接new因为被hide了,所以用反射)
AssetManager assetManager = AssetManager.class.newInstance();
//添加资源目录(addAssetPath也是一样被hide无法直接调用)
Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
method.setAccessible(true);//如果是私有的,添上防止万一某一天他变成了私有的
method.invoke(assetManager,Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"red.skin");//注意你资源的名字要一致
//DisplayMetrics和Configuration的对象可以直接new出来,这里使用的是从getResources得到的Resources中获取,其实也是new出来的
Resources resources = new Resources(assetManager,superResources.getDisplayMetrics(),superResources.getConfiguration());
//用创建好的Resources获取资源(注意着三个参数,第一个是要获取资源的名字,我们设置的是girl,不要忘了,第二个参数代表这个资源在哪个文件夹中,第三个参数表示要获取资源的apk的包名,缺一不可)
int identifier = resources.getIdentifier("girl", "drawable", "com.example.myapplication");
if (identifier != 0){
Drawable drawable = resources.getDrawable(identifier);
mImage.setImageDrawable(drawable);
}
下面是native端AssetManager初始化的过程,为什么我们的app可以调用系统提供好的资源,以及这些资源是如何加载的,可以在这里得到答案
AssetManager的init()
public AssetManager() {
synchronized (this) {
if (DEBUG_REFS) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
init(false);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
}
}
//android_util_AssetManager.cpp
//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-res.apk
path.appendPath(kSystemAssets);
return addAssetPath(path, NULL);
}
AssetManager的addAssetPath(String path)方法
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
AutoMutex _l(mLock);
asset_path ap;
String8 realPath(path);
if (kAppZipName) {
realPath.appendPath(kAppZipName);
}
ap.type = ::getFileType(realPath.string());
if (ap.type == kFileTypeRegular) {
ap.path = realPath;
} else {
ap.path = path;
ap.type = ::getFileType(path.string());
if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
ALOGW("Asset path %s is neither a directory nor file (type=%d).",
path.string(), (int)ap.type);
return false;
}
}
// Skip if we have it already.
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;
}
}
ALOGV("In %p Asset %s path: %s", this,
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
// Check that the path has an AndroidManifest.xml
Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
kAndroidManifest, Asset::ACCESS_BUFFER, ap);
if (manifestAsset == NULL) {
// This asset path does not contain any resources.
delete manifestAsset;
return false;
}
delete manifestAsset;
mAssetPaths.add(ap);
// new paths are always added at the end
if (cookie) {
*cookie = static_cast<int32_t>(mAssetPaths.size());
}
#ifdef HAVE_ANDROID_OS
// Load overlays, if any
asset_path oap;
for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
mAssetPaths.add(oap);
}
#endif
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());
if (ap.type != kFileTypeDirectory) {
if (nextEntryIdx == 0) {
// The first item is typically the framework resources,
// which we want to avoid parsing every time.
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTable(ap.path);
if (sharedRes != NULL) {
// skip ahead the number of system overlay packages preloaded
nextEntryIdx = sharedRes->getTableCount();
}
}
if (sharedRes == NULL) {
ass = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTableAsset(ap.path);
if (ass == NULL) {
ALOGV("loading resource table %s\n", ap.path.string());
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);
}
}
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());
sharedRes = new ResTable();
sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
#ifdef HAVE_ANDROID_OS
const char* data = getenv("ANDROID_DATA");
LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
String8 overlaysListPath(data);
overlaysListPath.appendPath(kResourceCache);
overlaysListPath.appendPath("overlays.list");
addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
#endif
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 {
ALOGV("Parsing resources for %s", ap.path.string());
mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
}
onlyEmptyResources = false;
if (!shared) {
delete ass;
}
} else {
ALOGV("Installing empty resources in to table %p\n", mResources);
mResources->addEmpty(nextEntryIdx + 1);
}
if (idmap != NULL) {
delete idmap;
}
MY_TRACE_END();
return onlyEmptyResources;
}
网友评论