文章目录

  • 一、构造函数
  • 1.1 成员变量
  • 1.2 构造函数
  • 1.3 提供功能
  • 1.3.1 getString(int id)、getIntArray(int id) 、getStringArray(int id)
  • 1.3.2 基于getValue的
  • 1.3.2.1 getDimension
  • 1.3.2.2 getDrawable
  • 1.3.2.2 getColor、getBoolean、getInteger
  • 1.3.2.3 loadXmlResourceParser加载getLayout、getAnimation、getXml
  • 1.3.4 obtainTypedArray(@ArrayRes int id)
  • 1.3.5 getMovie(int id)
  • 二、Resource的获取
  • 三、Resource的生成
  • 四、ResourceManager
  • 4.1 单例模式
  • 4.2 getTopLevelResources生成Resource
  • 参考文献


  • Resource实例
  1. Resource的实例保存在ContextImpl中,每次构建ContextImpl时,会从LoadedApk中拿到对应的Resource
  2. Loadedapk中的getResource会有成员实例mResource,命中直接返回,没命中委托Activitythread—ResourceManager生成
  3. ResourceMamager里有map缓存,命中直接返回,没命中则先新建Assetmanager,再构建Resource实例
  • 全局唯一性
  1. mResources通过一系列的缓存或者成员实例引用,实现了全局唯一。
  2. 如果通过打补丁的方式,会全局生效(small替换了resourcemanager中瓜缓存的resource的assetmanager,实现全局替换)。
  3. 但是在插件化方案中,为了避免id冲突,有的使用的是替换法,那就是所有有缓存和成员实例的地方都要替换为我们自己的resource;

一、构造函数

Resource.java

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;
    }

ResourceManager.java

参考文献