-
Resource实例
- Resource的实例保存在ContextImpl中,每次构建ContextImpl时,会从LoadedApk中拿到对应的Resource
- Loadedapk中的getResource会有成员实例mResource,命中直接返回,没命中委托Activitythread—ResourceManager生成
- ResourceMamager里有map缓存,命中直接返回,没命中则先新建Assetmanager,再构建Resource实例
-
全局唯一性
- mResources通过一系列的缓存或者成员实例引用,实现了全局唯一。
- 如果通过打补丁的方式,会全局生效(small替换了resourcemanager中瓜缓存的resource的assetmanager,实现全局替换)。
- 但是在插件化方案中,为了避免id冲突,有的使用的是替换法,那就是所有有缓存和成员实例的地方都要替换为我们自己的resource;
一、构造函数
1.1 成员变量
- 重要成员变量
- AssetManager mAssets; 构建时需要传入
public class Resources {
private static final LongSparseArray<ConstantState>[] sPreloadedDrawables;
private static final LongSparseArray<ConstantState> sPreloadedColorDrawables
= new LongSparseArray<ConstantState>();
private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists
= new LongSparseArray<ColorStateList>();
// Pool of TypedArrays targeted to this Resources object.
final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<TypedArray>(5);
// Used by BridgeResources in layoutlib
static Resources mSystem = null;
private static boolean sPreloaded;
private static int sPreloadedDensity;
// These are protected by mAccessLock.
private final Object mAccessLock = new Object();
private final Configuration mTmpConfig = new Configuration();
private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mDrawableCache =
new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache =
new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache =
new LongSparseArray<WeakReference<ColorStateList>>();
private TypedValue mTmpValue = new TypedValue();
private boolean mPreloading;
private TypedArray mCachedStyledAttributes = null;
private int mLastCachedXmlBlockIndex = -1;
private final int[] mCachedXmlBlockIds = { 0, 0, 0, 0 };
private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[4];
final AssetManager mAssets;
final DisplayMetrics mMetrics = new DisplayMetrics();
private final Configuration mConfiguration = new Configuration();
private NativePluralRules mPluralRule;
private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
@SuppressWarnings("unused")
private WeakReference<IBinder> mToken;
static {
sPreloadedDrawables = new LongSparseArray[2];
sPreloadedDrawables[0] = new LongSparseArray<ConstantState>();
sPreloadedDrawables[1] = new LongSparseArray<ConstantState>();
}
}
1.2 构造函数
- 几种构造函数,主要是获取到了AssetManager
/**
* 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();
}
/**
* Return a global shared Resources object that provides access to only
* system resources (no application resources), and is not configured for
* the current screen (can not use dimension units, does not change based
* on orientation, etc).
*/
public static Resources getSystem() {
synchronized (sSync) {
Resources ret = mSystem;
if (ret == null) {
ret = new Resources();
mSystem = ret;
}
return ret;
}
}
private Resources() {
mAssets = AssetManager.getSystem();
// NOTE: Intentionally leaving this uninitialized (all values set
// to zero), so that anyone who tries to do something that requires
// metrics will get a very wrong value.
mConfiguration.setToDefaults();
mMetrics.setToDefaults();
updateConfiguration(null, null);
mAssets.ensureStringBlocks();
}
1.3 提供功能
- getResources().getXXX只能用于获取应用本身的资源
- Resources.getSystem() 可以在任何地方进行使用,但是有一个局限,只能获取系统本身的资源
其实就是利用AssetManager完成各种资源加载
1.3.1 getString(int id)、getIntArray(int id) 、getStringArray(int id)
public String getString(int id) throws NotFoundException {
CharSequence res = getText(id);
if (res != null) {
return res.toString();
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
public CharSequence getText(int id) throws NotFoundException {
CharSequence res = mAssets.getResourceText(id);
if (res != null) {
return res;
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
public CharSequence getText(int id, CharSequence def) {
CharSequence res = id != 0 ? mAssets.getResourceText(id) : null;
return res != null ? res : def;
}
public String[] getStringArray(int id) throws NotFoundException {
String[] res = mAssets.getResourceStringArray(id);
if (res != null) {
return res;
}
throw new NotFoundException("String array resource ID #0x"
+ Integer.toHexString(id));
}
public int[] getIntArray(int id) throws NotFoundException {
int[] res = mAssets.getArrayIntResource(id);
if (res != null) {
return res;
}
throw new NotFoundException("Int array resource ID #0x"
+ Integer.toHexString(id));
}
1.3.2 基于getValue的
public void getValue(int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x"
+ Integer.toHexString(id));
}
1.3.2.1 getDimension
public float getDimension(int id) throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
mTmpValue = value = new TypedValue();
}
getValue(id, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(value.data, mMetrics);
}
throw new NotFoundException(
"Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ Integer.toHexString(value.type) + " is not valid");
}
}
1.3.2.2 getDrawable
public Drawable getDrawable(int id) throws NotFoundException {
final Drawable d = getDrawable(id, null);
if (d.canApplyTheme()) {
Log.w(TAG, "Drawable " + getResourceName(id) + " has unresolved theme "
+ "attributes! Consider using Resources.getDrawable(int, Theme) or "
+ "Context.getDrawable(int).", new RuntimeException());
}
return d;
}
public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
if (value == null) {
value = new TypedValue();
} else {
mTmpValue = null;
}
getValue(id, value, true);
}
final Drawable res = loadDrawable(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
return res;
}
1.3.2.2 getColor、getBoolean、getInteger
public int getColor(int id) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
if (value == null) {
value = new TypedValue();
}
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
mTmpValue = value;
return value.data;
} else if (value.type != TypedValue.TYPE_STRING) {
throw new NotFoundException(
"Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ Integer.toHexString(value.type) + " is not valid");
}
mTmpValue = null;
}
ColorStateList csl = loadColorStateList(value, id);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
}
}
return csl.getDefaultColor();
}
public boolean getBoolean(int id) throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
mTmpValue = value = new TypedValue();
}
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
return value.data != 0;
}
throw new NotFoundException(
"Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ Integer.toHexString(value.type) + " is not valid");
}
}
public int getInteger(int id) throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
mTmpValue = value = new TypedValue();
}
getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
return value.data;
}
throw new NotFoundException(
"Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ Integer.toHexString(value.type) + " is not valid");
}
}
1.3.2.3 loadXmlResourceParser加载getLayout、getAnimation、getXml
@NonNull
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
public XmlResourceParser getAnimation(int id) throws NotFoundException {
return loadXmlResourceParser(id, "anim");
}
public XmlResourceParser getXml(int id) throws NotFoundException {
return loadXmlResourceParser(id, "xml");
}
XmlResourceParser loadXmlResourceParser(int id, String type)
throws NotFoundException {
synchronized (mAccessLock) {
TypedValue value = mTmpValue;
if (value == null) {
mTmpValue = value = new TypedValue();
}
getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException(
"Resource ID #0x" + Integer.toHexString(id) + " type #0x"
+ Integer.toHexString(value.type) + " is not valid");
}
}
1.3.4 obtainTypedArray(@ArrayRes int id)
public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException {
int len = mAssets.getArraySize(id);
if (len < 0) {
throw new NotFoundException("Array resource ID #0x"
+ Integer.toHexString(id));
}
TypedArray array = TypedArray.obtain(this, len);
array.mLength = mAssets.retrieveArray(id, array.mData);
array.mIndices[0] = 0;
return array;
}
1.3.5 getMovie(int id)
public Movie getMovie(int id) throws NotFoundException {
InputStream is = openRawResource(id);
Movie movie = Movie.decodeStream(is);
try {
is.close();
}
catch (java.io.IOException e) {
// don't care, since the return value is valid
}
return movie;
}
二、Resource的获取
- Resource的实例保存在ContextImpl中,每次构建ContextImpl时会从LoadApk中拿到对应的resource
- 因此,getResources()用在有context的地方,没有context的地方和静态类中是不能用的
class ContextImpl extends Context {
private ContextImpl(ContextImpl container, ActivityThread mainThread,
LoadedApk packageInfo, IBinder activityToken, UserHandle user, int flags,
Display display, Configuration overrideConfiguration, int createDisplayWithId) {
mOuterContext = this;
mMainThread = mainThread;
mActivityToken = activityToken;
mRestricted = restricted;
mPackageInfo = packageInfo;
mResourcesManager = ResourcesManager.getInstance();
mDisplay = display;
//...
//从LoadApk创建Resources 实例
Resources resources = packageInfo.getResources(mainThread);
//...
mResources = resources;
//...
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}
}
三、Resource的生成
我们看LoadedApk中的getResources(ActivityThread mainThread)
- 如果存在mResources则直接返回
- 没有话,调用ActivityThread#getTopLevelResources新建一个
public final class LoadedApk {
public Resources getResources(ActivityThread mainThread) {
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
}
return mResources;
}
public AssetManager getAssets(ActivityThread mainThread) {
return getResources(mainThread).getAssets();
}
}
- ActivityThread#getTopLevelResources
public final class ActivityThread {
/**
* Creates the top level resources for the given package.
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId, Configuration overrideConfiguration,
LoadedApk pkgInfo) {
return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null);
}
ActivityThread() {
mResourcesManager = ResourcesManager.getInstance();
}
}
最终还是使用了mResourcesManager去新建
四、ResourceManager
4.1 单例模式
public final class ResourceManager {
private ResourceManager() {
}
private final static ResourceManager sThis = new ResourceManager();
public static ResourceManager getInstance() {
return sThis;
}
}
4.2 getTopLevelResources生成Resource
- 持有一个Map缓存实例mActiveResources
- getTopLevelResources
- 基于传入的资源地址构建ResourcesKey
- 判断mActiveResources是否有对应缓存
- 有的话直接返回
- 没有的话,新建一个
- 新建AssetManager实例 assets = new AssetManager();
- 遍历资源目录列表,调用
- String resDir: assets.addAssetPath(resDir)
- String[] splitResDirs:assets.addAssetPath(splitResDir)
- String[] overlayDirs:assets.addOverlayPath(idmapPath);
- String[] libDirs:assets.addAssetPath(libDir)
- 构建DisplayMetrics、Configuration
- 调用构造函数,新建r = new Resources(assets, dm, config, compatInfo, token);
- 存入mActiveResources缓存
final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources
= new ArrayMap<ResourcesKey, WeakReference<Resources> >();
public Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, 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();
// 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 (assets.addAssetPath(libDir) == 0) {
Slog.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
}
}
}
//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;
}
}
public DisplayMetrics getDisplayMetricsLocked(int displayId) {
return getDisplayMetricsLocked(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
}
public DisplayMetrics getDisplayMetricsLocked(int displayId, DisplayAdjustments daj) {
boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(daj) : null;
if (dm != null) {
return dm;
}
dm = new DisplayMetrics();
DisplayManagerGlobal displayManager = DisplayManagerGlobal.getInstance();
if (displayManager == null) {
// may be null early in system startup
dm.setToDefaults();
return dm;
}
if (isDefaultDisplay) {
mDefaultDisplayMetrics.put(daj, dm);
}
Display d = displayManager.getCompatibleDisplay(displayId, daj);
if (d != null) {
d.getMetrics(dm);
} else {
// Display no longer exists
// FIXME: This would not be a problem if we kept the Display object around
// instead of using the raw display id everywhere. The Display object caches
// its information even after the display has been removed.
dm.setToDefaults();
}
//Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h="
// + metrics.heightPixels + " den=" + metrics.density
// + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi);
return dm;
}
public Configuration getConfiguration() {
return mResConfiguration;
}