context.getResources()
是加载资源文件的入口 ,先来看看安卓中是怎么通过Resources加载资源文件的。
关于 Context 和 Activity 的启动流程分析参考:
http://blog.csdn.net/luoshengyang/article/details/6689748
Activity getResources 由父类的 ContextThemeWrapper 实现 :
if (mResources != null) {
return mResources;
}
if (mOverrideConfiguration == null) {
// 这里调用了 父类 的 getReources 方法
mResources = super.getResources();
return mResources;
} else {
Context resc = createConfigurationContext(mOverrideConfiguration);
mResources = resc.getResources();
return mResources;
}
查看 ContextThemeWrapper 的父类 ContextWrapper 的 getResources() 方法:
return mBase.getResources();
调用了 mBase 的 getResource 方法,主要是看mBase在哪里被赋值的,查看ActivityThread的源码:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
}
...
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
...
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
...
// 这里调用了 activty 的 attach 方法,mBase 在这里被赋值了
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window);
...
}
}
createBaseContextForActivity 方法的实现:
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
...
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.token, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
...
return baseContext;
}
这里发现 Context 的实现类是 ContextImpl,看一下里面 getResources 的实现:
public Resources getResources() {
return mResources;
}
这里的 mResources 是在 ContextImpl 的构造方法里被赋值的:
// 这里调用了 LoadedApk 的 getResources 方法
Resources resources = packageInfo.getResources(mainThread);
...
mResources = resources;
LoadedApk 的 getResources(mainThread) 方法
if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this);
}
return mResources;
这里最终调用了 mResourcesManager.getTopLevelResources
的方法,传入了mResDir,这里的 mResDir 的是什么鬼?
// 在构造方法里 设置成了 apk 的source路径
mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir;
mResourcesManager.getTopLevelResources 关键代码:
// 省略的代码是 从弱引用中获取 resource 的实例,如果找到就返回,以下是没有找到的代码
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 (libDir.endsWith(".apk")) {
// Avoid opening files we know do not have resources,
// like code-only .jar files.
if (assets.addAssetPath(libDir) == 0) {
Log.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
}
}
}
}
...
r = new Resources(assets, dm, config, compatInfo);
在看一下 resources 的 getDrawable 方法:
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme) throws NotFoundException {
...
synchronized (mAccessLock) {
value = mTmpValue;
if (value == null) {
value = new TypedValue();
} else {
mTmpValue = null;
}
getValue(id, value, true);
}
...
}
// 跳转到了 getValue 方法
public void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
// 这里的 mAssets 就是rosources构造方法传进来的AssetManager
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
...
}
所以如果我们想加载一个未安装的apk的资源文件,只要换掉 assets 里的addAssetPath 的路径就可以了。
class PluginResource(assets: AssetManager?, metrics: DisplayMetrics?, config: Configuration?) : Resources(assets, metrics, config) {
companion object {
fun getAssetManager(resource: Resources, file: File): AssetManager? {
var c: Class<*> = Class.forName("android.content.res.AssetManager");
c.declaredMethods.first {
it.name.equals("addAssetPath")
}.let {
var assetManager: AssetManager = AssetManager::class.java.newInstance()
it.invoke(assetManager, file.absolutePath)
return assetManager
}
return null
}
fun getResources(resource: Resources, file: File): PluginResource? {
var assetManager:AssetManager? = getAssetManager(resource, file)
return PluginResource(assetManager, resource.displayMetrics, resource.configuration)
}
}
}
测试加载 sd 卡的 apk 的资源文件:
try {
File apkFile = new File(Environment.getExternalStorageDirectory(), "app.apk");
PluginResource resource = PluginResource.Companion.getResources(getResources(), apkFile);
// 拿到资源的Id
DexClassLoader classLoader = new DexClassLoader(apkFile.getAbsolutePath(), getDir(apkFile.getName(), Context.MODE_PRIVATE).getAbsolutePath(), null, getClassLoader());
Class<?> rClass = classLoader.loadClass("com.xxx.demo.R$mipmap");
Field[] fields = rClass.getDeclaredFields();
for (Field field : fields) {
// R.mipmap.ic_launcher
if(field.getName().equals("ic_launcher")) {
int resId = field.getInt(rClass);
tv.setBackgroundDrawable(resource.getDrawable(resId));
}
}
} catch (Exception ex) {
ex.printStackTrace();
}