Resources 资源加载及引入外部资源

Resources 资源加载

加载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():

  1. mResources为null,根据mOverrideConfiguration来判断是从ContextImpl类中获取还是创建之后再获取;
  2. mResources不为null,则直接返回;
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的方法,来详细看下系统 是如何创建的;我们主要看第三种情况

  1. activityToken不为空,则通过key获取ResourcesImpl对象,然后通过getOrCreateResourcesForActivityLocked()方法获取或者创建一个Resources对象;
  2. activityToken为空,则通过key获取ResourcesImpl对象,然后getOrCreateResourcesLocked()获取或者创建一个Resources对象
  3. 如果不存在key对应的ResourcesImpl对象,则通过createResourcesImpl()创建ResourcesImpl对象,再根据activityToken是否为null,调用对应的方法,创建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和与之关联的所有缓存;ResourcesResources的包装类,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资源的呢?

  • 创建AssetManager对象,调用addXXXPath()方法,读取对应路径下的资源文件;并添加到ArrayMap对象mResourceImpls中;然后创建ResourcesImpl对象;
  • ClassLoader对象作为参数创建Resources对象,并setImpl()设置ResourcesImpl对象;

上述是如何获取或者创建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之外的资源文件呢?

通过反射的方式创建AssetmanagerResourcesImplResources对象;

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对象;
     * <p>
     * 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的主要内容了,如有问题,请多指教,谢谢!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值