加载res中的资源,都是通过getResources()获取的,但是Resources是在什么时候被创建的呢,如何通过Resources获取到对应的资源的呢?
XmlResourceParser layout = getResources().getLayout(R.layout.activity_change_skin);
int color = getResources().getColor(R.color.color_e83b36);
Drawable drawable = getResources().getDrawable(R.drawable.image_liu);
ContextThemeWrapper的getResources()
@Override
public Resources getResources() {
return getResourcesInternal();
}
ContextThemeWrapper的getResourcesInternal():
private Resources getResourcesInternal() {
if (mResources == null) {
//为null,则直接从ContextImpl类中获取;
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
//不为null,则通过ContextImpl类的createConfigurationContext()方法创建;
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
跳过中间的跳转,直接定位到ContextImpl的createConfigurationContext()方法
@Override
public Context createConfigurationContext(Configuration overrideConfiguration) {
//创建ContextImpl对象;
ContextImpl context = new ContextImpl(this, mMainThread, mPackageInfo, mSplitName,
mActivityToken, mUser, mFlags, mClassLoader);
final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
//通过createResources()方法创建Resources对象;并赋值给mResources;
context.setResources(createResources(mActivityToken, mPackageInfo, mSplitName, displayId,
overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo()));
return context;
}
ContextImpl的createResources()
private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
final String[] splitResDirs;
final ClassLoader classLoader;
try {
splitResDirs = pi.getSplitPaths(splitName);
classLoader = pi.getSplitClassLoader(splitName);
} catch (NameNotFoundException e) {
throw new RuntimeException(e);
}
//调用ResourcesManager的getResources();
return ResourcesManager.getInstance().getResources(activityToken,
pi.getResDir(),
splitResDirs,
pi.getOverlayDirs(),
pi.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfig,
compatInfo,
classLoader);
}
ResourcesManager的getResources();获取或者创建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);
}
}
getOrCreateResources():是最终获取或则创建Resources的方法,来详细看下系统 是如何创建的;我们主要看第三种情况
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
//1:activityToken不为null;
if (activityToken != null) {
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
// Clean up any dead references so they don't pile up.
ArrayUtils.unstableRemoveIf(activityResources.activityResources,
sEmptyReferencePredicate);
// Rebase the key's override config on top of the Activity's base override.
if (key.hasOverrideConfiguration()
&& !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
final Configuration temp = new Configuration(activityResources.overrideConfig);
temp.updateFrom(key.mOverrideConfiguration);
key.mOverrideConfiguration.setTo(temp);
}
//通过Key获取对应的ResourcesImpl对象;
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
} else {
//2:activityToken为null;
// Clean up any dead references so they don't pile up.
ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
// Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
}
}
//3:创建一个ResourcesImpl对象;
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
//再次通过key获取对应的ResourcesImpl对象,判断是否存在,不存在加入ArrayMap中;
synchronized (this) {
ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
if (existingResourcesImpl != null) {
resourcesImpl.getAssets().close();
resourcesImpl = existingResourcesImpl;
} else {
// Add this ResourcesImpl to the cache.
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
}
//根据activityToken是否为null,判断如何创建Resources对象;
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
createResourcesImpl():系统是如何创建ResourcesImpl对象的,主要参数:AssetManager对象;
AssetManager类;通过createAssetManager()方法,创建AssetManager对象;可以通过addXXX()方法根据path加载资源;
ResourcesImpl是资源访问的实现,包含AssetManager和与之关联的所有缓存;Resources是Resources的包装类,Resources中的方法都是通过ResourcesImpl对象来实现的;
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
//根据路径创建AssetManager对象;并且根据path获取资源;
final AssetManager assets = createAssetManager(key);
if (assets == null) {
return null;
}
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
final Configuration config = generateConfig(key, dm);
//创建ResourcesImpl对象;
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
AssetManager类中主要的方法
//通过路径添加额外的资源,可以是目录或者zip;失败则返回0;
public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}
private final int addAssetPathInternal(String path, boolean appAsLib) {
synchronized (this) {
int res = addAssetPathNative(path, appAsLib);
makeStringBlocks(mStringBlocks);
return res;
}
}
//native方法;
private native final int addAssetPathNative(String path, boolean appAsLib);
不管activityToken是否为null,调用getOrCreateResourcesForActivityLocked()还是getOrCreateResourcesLocked()都是类似的,都是遍历集合判断classLoader和impl是否相等,满足条件直接返回;否则创建对象
if (resources != null
&& Objects.equals(resources.getClassLoader(), classLoader)
&& resources.getImpl() == impl) {
return resources;
}
不满足上述条件,直接创建对象;
//创建Resources对象;
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
//调用setImpl()将ResourcesImpl对象传给Resources对象;
resources.setImpl(impl);
mResourceReferences.add(new WeakReference<>(resources));
系统是如何加载资源的Res资源的呢?
上述是如何获取或者创建Resources对象;有了Resources对象之后,如何获取对应的资源呢?
Resources中的资源文件,如何生成对应的ID的?
具体详见Android应用程序资源的编译和打包过程分析
以Resources的getText()方法为例,会调用AssetManager的getResourceText()方法;
@NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
if (res != null) {
return res;
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
AssetManager的getResourceText()
@Nullable
final CharSequence getResourceText(@StringRes int resId) {
synchronized (this) {
final TypedValue outValue = mValue;
if (getResourceValue(resId, 0, outValue, true)) {
return outValue.coerceToString();
}
return null;
}
}
getResourceValue()方法
final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
synchronized (this) {
//native方法
final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
if (block < 0) {
return false;
}
// Convert the changing configurations flags populated by native code.
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
outValue.changingConfigurations);
if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = mStringBlocks[block].get(outValue.data);
}
return true;
}
}
我们平时都是使用APP内部的资源文件,除了一键换肤,插件化开发,很少有引入外部资源文件的情况;我们如何如何APP之外的资源文件呢?
通过反射的方式创建Assetmanager,ResourcesImpl,Resources对象;
public class OutResources {
private String TAG = OutResources.class.getSimpleName();
private Resources resources;
private static OutResources outResources;
private String mOutPkgName;
private String mFilePath;
private OutResources() {
}
public static OutResources getInstance() {
if (outResources == null) {
synchronized (OutResources.class) {
if (outResources == null) {
outResources = new OutResources();
}
}
}
return outResources;
}
/**
* 创建Resources对象;
*
* Resources对外公开的构造方法
*/
void initResources(String filePath) {
this.mFilePath = filePath;
try {
if (resources == null) {
//通过反射的方式获取
AssetManager assetManager = createAssetManager();
if (assetManager == null) {
return;
}
Resources superRes = MyApplication.getContext().getResources();
resources = new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
}
Log.e(TAG, "加载外部资源文件");
} catch (Exception e) {
e.printStackTrace();
}
}
//第二种,按照系统的方式创建Resources对象
void initRes(String filePath) {
this.mFilePath = filePath;
if (resources == null) {
//创建Resources对象;
resources = (Resources) RefInvoke.createObject(Resources.class);
AssetManager assetManager = createAssetManager();
if (assetManager == null) {
return;
}
Resources superRes = MyApplication.getContext().getResources();
DisplayMetrics displayMetrics = superRes.getDisplayMetrics();
Configuration configuration = superRes.getConfiguration();
//获取DisplayAdjustments的class对象和实例;
Class disCls = null;
try {
disCls = Class.forName("android.view.DisplayAdjustments");
} catch (Exception e) {
e.printStackTrace();
}
Object dis = RefInvoke.createObject(disCls);
String className = "android.content.res.ResourcesImpl";
Class[] pareTyples = {AssetManager.class, DisplayMetrics.class, Configuration.class, disCls};
Object[] values = {assetManager, displayMetrics, configuration, dis};
//创建ResourcesImpl实例;
Object resourcesImpl = RefInvoke.createObject(className, pareTyples, values);
Class resourcesImplClass = null;
try {
resourcesImplClass = Class.forName(className);
} catch (Exception e) {
e.printStackTrace();
}
//调用Resources的setImpl()方法;
RefInvoke.invokeInstanceMethod(resources, "setImpl", resourcesImplClass, resourcesImpl);
}
Log.e(TAG, "加载外部资源文件");
}
/**
* 1)相同的资源名称,在不同的APK中,编译后对应的ID会不同;
*
* @param resId
* @return
*/
public Drawable getDrawable(Context context, int resId) {//获取图片
//Application mContext = context.getApplication();
if (resources == null) {
return ContextCompat.getDrawable(context, resId);
}
//包名+资源名
String resName = context.getResources().getResourceName(resId);
//资源名
String name = context.getResources().getResourceEntryName(resId);
//包名
String packageName = context.getResources().getResourcePackageName(resId);
//对应资源的类型;
String typeName = context.getResources().getResourceTypeName(resId);
Log.e(TAG, "resName::" + resName);
//需要获取的资源的包名;如果不存在对应的ID,获取不到包名;
//String mOutPkgName = resources.getResourcePackageName(resId);
//通过资源名称,类型获取对应的资源ID;
int outResId = resources.getIdentifier(name, typeName, mOutPkgName);
//outResId 没有这样的资源被发现;
if (outResId == 0) {
return ContextCompat.getDrawable(context, resId);
}
Log.e(TAG, "resId;;" + R.drawable.image_liu + ",,,outResId::" + outResId);
return resources.getDrawable(outResId);
}
public int getColor(Context context, @ColorRes int id) {
if (resources == null) {
return ContextCompat.getColor(context, id);
}
Resources superRes = context.getResources();
String packageName = superRes.getResourcePackageName(id);
String name = superRes.getResourceEntryName(id);
String typeName = superRes.getResourceTypeName(id);
int outResId = resources.getIdentifier(name, typeName, mOutPkgName);
if (outResId == 0) {
return ContextCompat.getColor(context, id);
}
return resources.getColor(outResId);
}
private AssetManager createAssetManager() {
try {
Class<AssetManager> assetManagerClass = AssetManager.class;
//AssetManager assetManager = (AssetManager) RefInvoke.createObject(clazz);
//调用AssetManager的getSystem()方法;
AssetManager assetManager = (AssetManager) RefInvoke.invokeStaticMethod(assetManagerClass, "getSystem");
Log.e(TAG, "filePath::" + mFilePath);
getPackageName(mFilePath);
//调用addAssetPath()加载第三方资源;
int addAssetPath = (int) RefInvoke.invokeInstanceMethod(assetManager, "addAssetPath", String.class, mFilePath);
Log.e(TAG, "addAssetPath::" + addAssetPath);
if (addAssetPath == 0) {
Log.e(TAG, "加载资源失败");
}
return assetManager;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private void getPackageName(String filePath) {
//取得PackageManager引用
PackageManager mPm = MyApplication.getContext().getPackageManager();
//“检索在包归档文件中定义的应用程序包的总体信息”,说人话,外界传入了一个apk的文件路径,这个方法,
//拿到这个apk的包信息,这个包信息包含什么?
PackageInfo mInfo = mPm.getPackageArchiveInfo(filePath, PackageManager.GET_ACTIVITIES);
//先把包名存起来
mOutPkgName = mInfo.packageName;
}
然后在点击事件中调用下面两个方法,即可加载app.zip的image_liu资源(app.zip中必须有和相同名称的资源,由apk包改为zip类型文件);
//可以放在Application中或者MainActivity;
private void initRes() {
String filePath = Environment.getExternalStorageDirectory() + File.separator + "a_test_change/app.zip";
OutResources.getInstance().initRes(filePath);
}
private void loadImage() {
imageView.setImageDrawable(OutResources.getInstance().getDrawable(act, R.drawable.image_liu));
}
以上就是Recouces的主要内容了,如有问题,请多指教,谢谢!