1.Android xml属性资源的加载
以ImageView的src为例 看看Android是如何找到图片资源的
我们通常使用image的代码如下
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/btn_back"/>
Android是如何加载到这个资源的呢
点进src的源码 这是Android源码的定义的文件 显然这种写法和我们使用
<declare-styleable name="ImageView">
<!-- Sets a drawable as the content of this ImageView. -->
<attr name="src" format="reference|color" />
...
</declare-styleable>
很明显 这里使用了下xml自定义属性 查看ImageView源码进一步证实这一点
我们在imageview的构造方法中发现了如下代码
final TypedArray a = context.obtainStyledAttributes(
attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
final Drawable d = a.getDrawable(R.styleable.ImageView_src);//重点跟踪
if (d != null) {
setImageDrawable(d);
}
这是经典的读取自定义属性的代码 我们接着跟踪 看drawable是如何获取到的
TypeArray部分代码:
@Nullable
public Drawable getDrawable(@StyleableRes int index) {
return getDrawableForDensity(index, 0);
}
public Drawable getDrawableForDensity(@StyleableRes int index, int density) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
if (density > 0) {
// If the density is overridden, the value in the TypedArray will not reflect this.
// Do a separate lookup of the resourceId with the density override.
mResources.getValueForDensity(value.resourceId, density, value, true);
}
return mResources.loadDrawable(value, value.resourceId, density, mTheme);//重点
}
return null;
}
最终我们发现 图片资源由mResources进行解析
那么mResources是如何进行初始化的呢?
2.mResources的初始化
TypeArray中的mResources初始化不是很容易看懂,我们从其他方面入手,比如我们经常使用context.getResources()来获取string 颜色 图片等 那么 这里的getResources中的Resource是如何初始化的呢(API 28)
getResources().getDrawable(-1,null);
//ContextThemeWrapper
@Override
public Resources getResources() {
return getResourcesInternal();
}
private Resources getResourcesInternal() {
if (mResources == null) {
if (mOverrideConfiguration == null) {
mResources = super.getResources();//走这里
} else {
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
//ContextWrapper
@Override
public Resources getResources() {
return mBase.getResources();
}
// Context
public abstract Resources getResources();
// 追踪到实现类 ContextImpl
@Override
public Resources getResources() {
return mResources;
}
void setResources(Resources r) {
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
mResources = r;
}
c.setResources(createResources(mActivityToken, pi, null, displayId, null,
getDisplayAdjustments(displayId).getCompatibilityInfo()));
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);
}
return ResourcesManager.getInstance().getResources(activityToken,//关键
pi.getResDir(),
splitResDirs,
pi.getOverlayDirs(),
pi.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfig,
compatInfo,
classLoader);
}
// ResourcesManager
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);
}
}
// ResourcesMananger
// 创建Resources的有两个方法 getOrCreateResourcesForActivityLocked以及getOrCreateResourcesLocked
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
if (activityToken != null) {
...
} else {
// 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) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
}
// If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
// Add this ResourcesImpl to the cache.
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
final Resources resources;//重点 看他如何初始化
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
//1 getOrCreateResourcesLocked
private @NonNull Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) {
// Find an existing Resources that has this ResourcesImpl set.
final int refCount = mResourceReferences.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
Resources resources = weakResourceRef.get();
if (resources != null &&
Objects.equals(resources.getClassLoader(), classLoader) &&
resources.getImpl() == impl) {
if (DEBUG) {
Slog.d(TAG, "- using existing ref=" + resources);
}
return resources;
}
}
// Create a new Resources reference and use the existing ResourcesImpl object.
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);//重点
resources.setImpl(impl);
mResourceReferences.add(new WeakReference<>(resources));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
//2 getOrCreateResourcesForActivityLocked
private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl,
@NonNull CompatibilityInfo compatInfo) {
final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
activityToken);
final int refCount = activityResources.activityResources.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
Resources resources = weakResourceRef.get();
if (resources != null
&& Objects.equals(resources.getClassLoader(), classLoader)
&& resources.getImpl() == impl) {
if (DEBUG) {
Slog.d(TAG, "- using existing ref=" + resources);
}
return resources;
}
}
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);//重点
resources.setImpl(impl);
activityResources.activityResources.add(new WeakReference<>(resources));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
//两个创建resources的方法都类似 都使用了缓存机制 都是通过如下代码创建resources的
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
//而通过CompatResources创建的其实和直接用Resources创建的差不多
// CompatResources.java
public CompatResources(ClassLoader cls) {
super(cls);//super是Resources
mContext = new WeakReference<>(null);
}
// Resources.java
public Resources(@Nullable ClassLoader classLoader) {
mClassLoader = classLoader == null ? ClassLoader.getSystemClassLoader() : classLoader;
}
// 也就是无论如何 都是通过 Resources resources = new Resources(classLoader)创建的Resources
// 而实际上 Resources内部包含了ResourcesImpl对象 Resources利用ResourcesImpl来获取系统的一些资源
// 如1.获取图片
public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)
throws NotFoundException {
return getDrawableForDensity(id, 0, theme);
}
public Drawable getDrawableForDensity(@DrawableRes int id, int density, @Nullable Theme theme) {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValueForDensity(id, density, value, true);
return impl.loadDrawable(this, value, id, density, theme);
} finally {
releaseTempTypedValue(value);
}
}
// 2.获取Dimension
public float getDimension(@DimenRes int id) throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(value.data, impl.getDisplayMetrics());
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ " type #0x" + Integer.toHexString(value.type) + " is not valid");
} finally {
releaseTempTypedValue(value);
}
}
// 3.获取color
public int getColor(@ColorRes int id, @Nullable Theme theme) throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type >= TypedValue.TYPE_FIRST_INT
&& value.type <= TypedValue.TYPE_LAST_INT) {
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");
}
final ColorStateList csl = impl.loadColorStateList(this, value, id, theme);
return csl.getDefaultColor();
} finally {
releaseTempTypedValue(value);
}
}
// 4.获取String
public String getString(@StringRes int id) throws NotFoundException {
return getText(id).toString();
}
@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));
}
// 我们可以直接创建ResourcesImpl或者创建Resources后通过其内部的ResourcesImpl来访问资源 这里我像视频一样选择后者
// 其中要使得Resources和mResourcesImpl都进行初始化 最简单的调用时调用Resources的三个参数的构造方法
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
// 需要看一下assets metrics config是如何初始化的
/**
* Only for creating the System resources.
*/
private Resources() {
this(null);
final DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
final Configuration config = new Configuration();
config.setToDefaults();
mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config,
new DisplayAdjustments());
}
其中最重要的是参数AssetManager 上述例子使用的是系统的AssetManager 我们自己创建的时候需要使用自己path来创建AssetManager,具体灵感来自API 26的源码
AssetManager assets = new AssetManager();
assets.addAssetPath(resDir)// resDir apk的目录
API 28的相关代码如下
/**
* @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
* @hide
*/
@Deprecated
public int addAssetPath(String path) {
return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
}
private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
Preconditions.checkNotNull(path, "path");
synchronized (this) {
ensureOpenLocked();
final int count = mApkAssets.length;
// See if we already have it loaded.
for (int i = 0; i < count; i++) {
if (mApkAssets[i].getAssetPath().equals(path)) {
return i + 1;
}
}
final ApkAssets assets;
try {
if (overlay) {
// TODO(b/70343104): This hardcoded path will be removed once
// addAssetPathInternal is deleted.
final String idmapPath = "/data/resource-cache/"
+ path.substring(1).replace('/', '@')
+ "@idmap";
assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/);
} else {
assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib);
}
} catch (IOException e) {
return 0;
}
mApkAssets = Arrays.copyOf(mApkAssets, count + 1);
mApkAssets[count] = assets;
nativeSetApkAssets(mObject, mApkAssets, true);
invalidateCachesLocked(-1);
return count + 1;
}
}
有了以上的调查 我们就可以创建自己的Resource并取得图片 颜色 String等资源了
3.Demo 获取其他APK的资源
准备工作:
3.1.创建皮肤包
创建一个Android项目 里面仅仅放一个图片abc.png 生成一个APK 重命名为test.skin 将apk放置到手机内存根目录
/storage/emulated/0/test.skin
首先需要申请内存访问权限,之前就是因为没有申请权限 导致获取的id一直是0 结果调试了半天 才发现是没有内存的访问权限
3.2.申请内存访问权限
public class Util {
public static void checkAndRequestReadSDCardPermission(Activity activity) {
if (activity == null) {
return;
}
if (activity.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
activity.requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 0);
}
}
}
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
3.3.创建Resource和AssertManager
protected void initView() {
Util.checkAndRequestReadSDCardPermission(this);
loadImg();
}
private void loadImg() {
try {
ImageView img = findViewById(R.id.emptyImg);
// 读取本地的一个 .skin里面的资源
Resources superRes = getResources();
// 创建AssetManager
AssetManager assetManager = AssetManager.class.newInstance();
// 寻找hide的方法
Method method = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
// method.setAccessible(true); 如果是私有的
Log.e(TAG, "loadImg from : "+Environment.getExternalStorageDirectory().getAbsolutePath()+
File.separator + "test.skin");
// 反射执行方法 assetManager指向指定的皮肤包 /storage/emulated/0/test.skin
method.invoke(assetManager, Environment.getExternalStorageDirectory().getAbsolutePath()+
File.separator + "test.skin");
Resources resource = new Resources(assetManager,superRes.getDisplayMetrics(),
superRes.getConfiguration());
// 获取指定路径apk的packageName
String packageName = "";
if (this.getApplication() != null) {
Log.e(TAG, "loadImage: getApplication!=null");
if (this.getApplication().getPackageManager() != null) {
String myPath = Environment.getExternalStorageDirectory().getAbsolutePath() +
File.separator + "test.skin";
Log.e(TAG, "loadImage: myPath ==="+myPath);
packageName = this.getApplication().getPackageManager().getPackageArchiveInfo(Environment.getExternalStorageDirectory().getAbsolutePath() +
File.separator + "test.skin", PackageManager.GET_ACTIVITIES).packageName;
} else {
Log.e(TAG, "loadImage: getPackageManager==null");
}
}
// 获取资源 id
int drawableId = resource.getIdentifier("abc","drawable",packageName);
Drawable drawable = resource.getDrawable(drawableId);
img.setImageDrawable(drawable);
} catch (Exception e) {
Log.e(TAG, "loadImg: "+e.getStackTrace().toString());
e.printStackTrace();
}
}
4.AssetManager的Android部分源码(API28)
我们之前已经知道资源的加载是通过Resources类来加载的,而实际上执行方法的是Resources的内部成员ResourcesImpl,这里我们继续往下深入
以Resources的getDrawableForDensity方法为例 其内部调用了ResourcesImpl的方法
loadDrawable(this, value, id, density, theme);
@Nullable
Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id,
int density, @Nullable Resources.Theme theme)
throws NotFoundException {
...
try {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
if ((id >>> 24) == 0x1) {
final String name = getResourceName(id);//获取预加载的Android原生的资源 内部调用的也是AssetManager的方法
if (name != null) {
Log.d("PreloadDrawable", name);
}
}
}
...
// First, check whether we have a cached version of this drawable
// that was inflated against the specified theme. Skip the cache if
// we're currently preloading or we're not using the cache.
if (!mPreloading && useCache) {// 缓存机制
final Drawable cachedDrawable = caches.getInstance(key, wrapper, theme);
if (cachedDrawable != null) {
cachedDrawable.setChangingConfigurations(value.changingConfigurations);
return cachedDrawable;
}
}
// 预加载资源
// Next, check preloaded drawables. Preloaded drawables may contain
// unresolved theme attributes.
final Drawable.ConstantState cs;
if (isColorDrawable) {
cs = sPreloadedColorDrawables.get(key);
} else {
cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
}
Drawable dr;
boolean needsNewDrawableAfterCache = false;
if (cs != null) {//加载Android原生资源的case
if (TRACE_FOR_DETAILED_PRELOAD) {
// Log only framework resources
if (((id >>> 24) == 0x1) && (android.os.Process.myUid() != 0)) {
final String name = getResourceName(id);
if (name != null) {
Log.d(TAG_PRELOAD, "Hit preloaded FW drawable #"
+ Integer.toHexString(id) + " " + name);
}
}
}
dr = cs.newDrawable(wrapper);
} else if (isColorDrawable) {//加载颜色类型的drawable
dr = new ColorDrawable(value.data);
} else {// 加载真正的drawable 重点!!!
dr = loadDrawableForCookie(wrapper, value, id, density);
}
...
}
}
private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value,
int id, int density) {
...
if (file.endsWith(".xml")) {//加在xml类型的drawable
final XmlResourceParser rp = loadXmlResourceParser(
file, id, value.assetCookie, "drawable");
dr = Drawable.createFromXmlForDensity(wrapper, rp, density, null);
rp.close();
} else {// 加载png jpeg等类型的图片
final InputStream is = mAssets.openNonAsset(
value.assetCookie, file, AssetManager.ACCESS_STREAMING);
AssetInputStream ais = (AssetInputStream) is;
dr = decodeImageDrawable(ais, wrapper, value);
}
...
}
我们知道加载是通过Resources类来加载的,而实际上执行方法的是Resources的内部成员,现在看了源码 其实真正查找资源的是AssetManager
我们看看AssetManager是如何初始化的吧
查看AssetManager的构造方法 有两个
/**
* Private constructor that doesn't call ensureSystemAssets.
* Used for the creation of system assets.
*/
@SuppressWarnings("unused")
private AssetManager(boolean sentinel) {
mObject = nativeCreate();
if (DEBUG_REFS) {
mNumRefs = 0;
incRefsLocked(hashCode());
}
}
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
* appropriate asset manager with {@link Resources#getAssets}. Not for
* use by applications.
* @hide
*/
public AssetManager() {
final ApkAssets[] assets;
synchronized (sSync) {
createSystemAssetsInZygoteLocked();
assets = sSystemApkAssets;
}
mObject = nativeCreate();
if (DEBUG_REFS) {
mNumRefs = 0;
incRefsLocked(hashCode());
}
// Always set the framework resources.
setApkAssets(assets, false /*invalidateCaches*/);
}
方法的注释已经说明了区别 无参的构造方法是专门为了创建系统资源的 通过源码我们也知道无参的构造方法在createSystemAssetsInZygoteLocked中调用了包含boolean作为参数的构造方法。
createSystemAssetsInZygoteLocked方法中有个值得关注的点
apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/));
private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
这里加载了Android系统的资源apk 我猜测 小米的MIUI或者其他更改系统皮肤的手机制造商 是可以更改这个apk中的资源来达到控制UI式样的目的的
由于Android9.0中 AssetManager的源码变动较大,我能力有限,没有看懂在9.0中AssetManager如何在framework层初始化的
猜测可能的创建方式如下
nativeCreate()
// frameworks/base/core/jni/android_util_AssetManager.cpp
static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) {
// AssetManager2 needs to be protected by a lock. To avoid cache misses, we allocate the lock and
// AssetManager2 in a contiguous block (GuardedAssetManager).
return reinterpret_cast<jlong>(new GuardedAssetManager());
}
struct GuardedAssetManager : public ::AAssetManager {
Guarded<AssetManager2> guarded_assetmanager;
};
//AssetManager2.cpp
AssetManager2::AssetManager2() {
memset(&configuration_, 0, sizeof(configuration_));
}
最终创建的可能是AssetManager2
因为不是很懂c++ 再往下就分析不动了。。。
罗升阳的博客讲的比较清楚,不过他的Android版本可能是6.0版本的 Android9.0系统源码已经大变 不过还是有很大的参考价值
https://blog.csdn.net/luoshengyang/article/details/8791064
另外值得说一下的是 resources.arsc
这个是打包后的apk中的一个文件 是各种资源和id的映射,assetManager管理资源应该和这个文件关系很大 使用Android Studio打开apk并选择resources.arsc打开 可以看到详细的映射 我找到了系统的资源apk的resources.arsc 如图
打开我们之前使用皮肤包 还能看到我们放入的资源的id与图片名称的映射