您现在的位置是:首页 > 文章详情

10.源码阅读(插件式换肤-安卓Resources加载资源的过程-android api 26)

日期:2018-04-03点击:309

我们知道,每一个View的子类都可以设置backgroud,那么这个背景是如何加载出来的呢?

找到View的构造方法

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { ...... case com.android.internal.R.styleable.View_background: background = a.getDrawable(attr); break; ...... } 
@Nullable public Drawable getDrawable(@StyleableRes int index) { return getDrawableForDensity(index, 0); } @Nullable public Drawable getDrawableForDensity(@StyleableRes int index, int density) { ...... return mResources.loadDrawable(value, value.resourceId, density, mTheme); ...... } 

看到这一行

return mResources.loadDrawable(value, value.resourceId, density, mTheme); 

进入Resources中

@NonNull Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme) throws NotFoundException { return mResourcesImpl.loadDrawable(this, value, id, density, theme); } 

可以看到,背景最终是被Resources中的ResourcesImpl加载得到Drawable的,ResourcesImpl在Resources构造中创建出来

@Deprecated public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { this(null); mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments()); } private Resources() { ...... mResourcesImpl = new ResourcesImpl(AssetManager.getSystem(), metrics, config, new DisplayAdjustments()); } 

我们再来看Resources是如何创建的,平常我们获取一些资源文件的时候,会这样获取Resources

context.getResources() 

我们知道Context是抽象类,所以直接到Context的子类ContextImpl中去找

@Override public Resources getResources() { return mResources; } 

那么这个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())); c.setResources(createResources(mActivityToken, pi, null, displayId, null, getDisplayAdjustments(displayId).getCompatibilityInfo())); context.setResources(packageInfo.getResources()); context.setResources(ResourcesManager.getInstance().getResources( mActivityToken, mPackageInfo.getResDir(), paths, mPackageInfo.getOverlayDirs(), mPackageInfo.getApplicationInfo().sharedLibraryFiles, displayId, null, mPackageInfo.getCompatibilityInfo(), classLoader)); context.setResources(resourcesManager.createBaseActivityResources(activityToken, packageInfo.getResDir(), splitDirs, packageInfo.getOverlayDirs(), packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, compatInfo, classLoader)); 

这样的方法有好几个,而且我们找不到其他地方给mResources赋值的地方,初步可以判断,Resources就是这样创建的,顺着这几个方法去看,你会发现,尽管setResources方法有好几种形式,但最后都会进入到ResourcesManger这个类中,这个方法的注释可以看看,Resources是会被缓存的,一个Resources的生命周期和这个Activity同等,当classloader改变,Resources也会改变

/** * Gets or creates a new Resources object associated with the IBinder token. References returned * by this method live as long as the Activity, meaning they can be cached and used by the * Activity even after a configuration change. If any other parameter is changed * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object * is updated and handed back to the caller. However, changing the class loader will result in a * new Resources object. * <p/> * If activityToken is null, a cached Resources object will be returned if it matches the * input parameters. Otherwise a new Resources object that satisfies these parameters is * returned. * * @param activityToken Represents an Activity. If null, global resources are assumed. * @param resDir The base resource path. Can be null (only framework resources will be loaded). * @param splitResDirs An array of split resource paths. Can be null. * @param overlayDirs An array of overlay paths. Can be null. * @param libDirs An array of resource library paths. Can be null. * @param displayId The ID of the display for which to create the resources. * @param overrideConfig The configuration to apply on top of the base configuration. Can be * null. Mostly used with Activities that are in multi-window which may override width and * height properties from the base config. * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}. * @param classLoader The class loader to use when inflating Resources. If null, the * {@link ClassLoader#getSystemClassLoader()} is used. * @return a Resources object from which to access 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); } } 
/** * Gets an existing Resources object set with a ResourcesImpl object matching the given key, * or creates one if it doesn't exist. * * @param activityToken The Activity this Resources object should be associated with. * @param key The key describing the parameters of the ResourcesImpl object. * @param classLoader The classloader to use for the Resources object. * If null, {@link ClassLoader#getSystemClassLoader()} is used. * @return A Resources object that gets updated when * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)} * is called. */ private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken, @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) { synchronized (this) { ...... //下边是分两种情况,当activityToken(IBinder)是否为null的情况下根据key获取ResourcesImpl, //只要这个ResourcesImpl存在,就会直接得到Resources缓存返回或者新创建一个Resources返回 if (activityToken != null) { ...... //根据key获取与之对应的ResourcesImpl缓存 ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); if (resourcesImpl != null) { ...... // 只要根据key获取到的ResourcesImpl不为null,就根据这个ResourcesImpl去获取缓存的 //Resources,如果又这个Resources缓存,就返回,没有就创建,具体看这个方法 return getOrCreateResourcesForActivityLocked(activityToken, classLoader, resourcesImpl, key.mCompatInfo); } // We will create the ResourcesImpl object outside of holding this lock. } else { ...... ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); if (resourcesImpl != null) { ...... return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); } } //当程序走到这里的时候,说明ResourcesImpl没有找到,Resources也就没有得到,那么这里就是根据 //key创建出一个ResourcesImpl来,程序第一次运行的时候肯定会首先走到这里,所以,上边的代码可以 //不用太重点的去看,接下来我们看看ResourcesImpl是如何被创建出来的,见方法createResourcesImpl // 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; } synchronized (this) { ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key); if (existingResourcesImpl != null) { //从缓存中获取 ...... resourcesImpl.getAssets().close(); resourcesImpl = existingResourcesImpl; } else { // 将创建的ResourcesImpl缓存起来 mResourceImpls.put(key, new WeakReference<>(resourcesImpl)); } final Resources resources; //在此针对activityToken是否为null分别处理,在getOrCreateResourcesForActivityLocked和getOrCreateResourcesLocked //这两个方法中,我们重点关注,Resources不存在缓存的情况,所以,最终会看到Resourses的创建, //见下边的方法 if (activityToken != null) { resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader, resourcesImpl, key.mCompatInfo); } else { resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo); } return resources; } //Resources的创建,这里看到根据条件的不同有两种方式获取,一个是new CompatResources,一个是 //new Resources,进入到CompatResources类中,我们看到这个构造最终也会调用Resources的一个构造 //方法public Resources(@Nullable ClassLoader classLoader) 返回Resources,可见这个Resources是new //出来的 Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) : new Resources(classLoader); //给Resources设置ResourcesImpl resources.setImpl(impl); //加入缓存 mResourceReferences.add(new WeakReference<>(resources)); 

getOrCreateResourcesForActivityLocked

/** * Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist * or the class loader is different. */ private @NonNull Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken, @NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl, @NonNull CompatibilityInfo compatInfo) { ...... //有缓存获取缓存 Resources resources = weakResourceRef.get(); ...... return resources; ...... //没有缓存创建出来然后加入缓存 Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader) : new Resources(classLoader); resources.setImpl(impl); activityResources.activityResources.add(new WeakReference<>(resources)); ...... return resources; } 

createResourcesImpl,可以看到ResourcesImpl的创建依赖于这几个对象AssetManager,DisplayMetrics,Configuration,DisplayAdjustments

private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) { final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration); daj.setCompatibilityInfo(key.mCompatInfo); final AssetManager assets = createAssetManager(key); if (assets == null) { return null; } final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj); final Configuration config = generateConfig(key, dm); final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj); ...... return impl; } 

着重看AssetManager的创建,这个类很关键

/** * Creates an AssetManager from the paths within the ResourcesKey. * * This can be overridden in tests so as to avoid creating a real AssetManager with * real APK paths. * @param key The key containing the resource paths to add to the AssetManager. * @return a new AssetManager. */ @VisibleForTesting protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) { 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 (key.mResDir != null) { // 将app中的资源路径都加入到AssetManager对象中,下边的方法都可以不看,我们重点 //关注这个方法,可以说,应用之所以能加载资源,就是通过AssetManager以及调用addAssetPath对他设置的 //资源路径 if (assets.addAssetPath(key.mResDir) == 0) { Log.e(TAG, "failed to add asset path " + key.mResDir); return null; } } ..... return assets; } 

getDisplayMetrics,也只是new了一个DisplayMetrics

@VisibleForTesting protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) { DisplayMetrics dm = new DisplayMetrics(); final Display display = getAdjustedDisplay(displayId, da); if (display != null) { display.getMetrics(dm); } else { dm.setToDefaults(); } return dm; } 

generateConfig,Configuration也是new出来的

private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) { Configuration config; .... config = new Configuration(getConfiguration()); .... return config; } 

Resources的创建中有一个个关键的类,就是ResourcesImpl,这个类的创建需要几个重要的信息,其中之一就是AssetManager,通过直接实例话一个AssetManager对象并给这个对象设置资源路径,这是#Resources可以获取到文件资源的基础,DisplayMetrics或者Configuration则相当于一些固定设置,AssetManager中设置的这个路径,其实就是我们将要设置的apk的路径,从这个apk中获取资源

如此一来,我们获取到了Resources,就可以自由的去获取资源文件了

那么我们再回到最初,看看Resources是如何loadDrawable的

Resources中

@NonNull Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme) throws NotFoundException { return mResourcesImpl.loadDrawable(this, value, id, density, theme); } 

ResourcesImpl中

@Nullable Drawable loadDrawable(@NonNull Resources wrapper, @NonNull TypedValue value, int id, int density, @Nullable Resources.Theme theme) throws NotFoundException { ...... Drawable dr; boolean needsNewDrawableAfterCache = false; if (cs != null) { dr = cs.newDrawable(wrapper); } else if (isColorDrawable) { dr = new ColorDrawable(value.data); } else { dr = loadDrawableForCookie(wrapper, value, id, density, null); } ...... return dr; ...... 
/** * Loads a drawable from XML or resources stream. */ private Drawable loadDrawableForCookie(@NonNull Resources wrapper, @NonNull TypedValue value, int id, int density, @Nullable Resources.Theme theme) { ...... final Drawable dr; Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); try { //如果资源是xml文件 if (file.endsWith(".xml")) { final XmlResourceParser rp = loadXmlResourceParser( file, id, value.assetCookie, "drawable"); dr = Drawable.createFromXmlForDensity(wrapper, rp, density, theme); rp.close(); } else { //如果资源是图片资源,打开它得到流,然后解析得到drawable final InputStream is = mAssets.openNonAsset( value.assetCookie, file, AssetManager.ACCESS_STREAMING); dr = Drawable.createFromResourceStream(wrapper, value, is, file, null); is.close(); } ...... Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); return dr; } 

经过上面的分析,皮肤切换的思路已经有了,下载apk文件,这个文件中包含有另一个皮肤的各种资源文件,通过Resources去加载这个apk中的资源,达到换肤的效果,关键代码如下

 //点击从手机中一个apk中获取图片资源并且设置给ImageView显示 //获取系统的两个参数 Resources superResources = getResources(); //创建assetManger(无法直接new因为被hide了,所以用反射) AssetManager assetManager = AssetManager.class.newInstance(); //添加资源目录(addAssetPath也是一样被hide无法直接调用) Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class); method.setAccessible(true);//如果是私有的,添上防止万一某一天他变成了私有的 method.invoke(assetManager,Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"red.skin");//注意你资源的名字要一致 //DisplayMetrics和Configuration的对象可以直接new出来,这里使用的是从getResources得到的Resources中获取,其实也是new出来的 Resources resources = new Resources(assetManager,superResources.getDisplayMetrics(),superResources.getConfiguration()); //用创建好的Resources获取资源(注意着三个参数,第一个是要获取资源的名字,我们设置的是girl,不要忘了,第二个参数代表这个资源在哪个文件夹中,第三个参数表示要获取资源的apk的包名,缺一不可) int identifier = resources.getIdentifier("girl", "drawable", "com.example.myapplication"); if (identifier != 0){ Drawable drawable = resources.getDrawable(identifier); mImage.setImageDrawable(drawable); } 

下面是native端AssetManager初始化的过程,为什么我们的app可以调用系统提供好的资源,以及这些资源是如何加载的,可以在这里得到答案

AssetManager的init()

public AssetManager() { synchronized (this) { if (DEBUG_REFS) { mNumRefs = 0; incRefsLocked(this.hashCode()); } init(false); if (localLOGV) Log.v(TAG, "New asset manager: " + this); ensureSystemAssets(); } } //android_util_AssetManager.cpp //AssetManager.cpp private native final void init(boolean isSystem); 
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem) { if (isSystem) { verifySystemIdmaps(); } // AssetManager.cpp AssetManager* am = new AssetManager(); if (am == NULL) { jniThrowException(env, "java/lang/OutOfMemoryError", ""); return; } am->addDefaultAssets(); ALOGV("Created AssetManager %p for Java object %p\n", am, clazz); env->SetLongField(clazz, gAssetManagerOffsets.mObject,reinterpret_cast<jlong>(am)); } bool AssetManager::addDefaultAssets() { const char* root = getenv("ANDROID_ROOT"); LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set"); String8 path(root); // 初始化的时候加载系统的framework-res.apk path.appendPath(kSystemAssets); return addAssetPath(path, NULL); } 

AssetManager的addAssetPath(String path)方法

bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) { AutoMutex _l(mLock); asset_path ap; String8 realPath(path); if (kAppZipName) { realPath.appendPath(kAppZipName); } ap.type = ::getFileType(realPath.string()); if (ap.type == kFileTypeRegular) { ap.path = realPath; } else { ap.path = path; ap.type = ::getFileType(path.string()); if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) { ALOGW("Asset path %s is neither a directory nor file (type=%d).", path.string(), (int)ap.type); return false; } } // Skip if we have it already. for (size_t i=0; i<mAssetPaths.size(); i++) { if (mAssetPaths[i].path == ap.path) { if (cookie) { *cookie = static_cast<int32_t>(i+1); } return true; } } ALOGV("In %p Asset %s path: %s", this, ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); // Check that the path has an AndroidManifest.xml Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked( kAndroidManifest, Asset::ACCESS_BUFFER, ap); if (manifestAsset == NULL) { // This asset path does not contain any resources. delete manifestAsset; return false; } delete manifestAsset; mAssetPaths.add(ap); // new paths are always added at the end if (cookie) { *cookie = static_cast<int32_t>(mAssetPaths.size()); } #ifdef HAVE_ANDROID_OS // Load overlays, if any asset_path oap; for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) { mAssetPaths.add(oap); } #endif if (mResources != NULL) { appendPathToResTable(ap); } return true; } bool AssetManager::appendPathToResTable(const asset_path& ap) const { // skip those ap's that correspond to system overlays if (ap.isSystemOverlay) { return true; } Asset* ass = NULL; ResTable* sharedRes = NULL; bool shared = true; bool onlyEmptyResources = true; MY_TRACE_BEGIN(ap.path.string()); Asset* idmap = openIdmapLocked(ap); size_t nextEntryIdx = mResources->getTableCount(); ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); if (ap.type != kFileTypeDirectory) { if (nextEntryIdx == 0) { // The first item is typically the framework resources, // which we want to avoid parsing every time. sharedRes = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTable(ap.path); if (sharedRes != NULL) { // skip ahead the number of system overlay packages preloaded nextEntryIdx = sharedRes->getTableCount(); } } if (sharedRes == NULL) { ass = const_cast<AssetManager*>(this)-> mZipSet.getZipResourceTableAsset(ap.path); if (ass == NULL) { ALOGV("loading resource table %s\n", ap.path.string()); ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); if (ass != NULL && ass != kExcludedAsset) { ass = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTableAsset(ap.path, ass); } } if (nextEntryIdx == 0 && ass != NULL) { // If this is the first resource table in the asset // manager, then we are going to cache it so that we // can quickly copy it out for others. ALOGV("Creating shared resources for %s", ap.path.string()); sharedRes = new ResTable(); sharedRes->add(ass, idmap, nextEntryIdx + 1, false); #ifdef HAVE_ANDROID_OS const char* data = getenv("ANDROID_DATA"); LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set"); String8 overlaysListPath(data); overlaysListPath.appendPath(kResourceCache); overlaysListPath.appendPath("overlays.list"); addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx); #endif sharedRes = const_cast<AssetManager*>(this)-> mZipSet.setZipResourceTable(ap.path, sharedRes); } } } else { ALOGV("loading resource table %s\n", ap.path.string()); ass = const_cast<AssetManager*>(this)-> openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap); shared = false; } if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); if (sharedRes != NULL) { ALOGV("Copying existing resources for %s", ap.path.string()); mResources->add(sharedRes); } else { ALOGV("Parsing resources for %s", ap.path.string()); mResources->add(ass, idmap, nextEntryIdx + 1, !shared); } onlyEmptyResources = false; if (!shared) { delete ass; } } else { ALOGV("Installing empty resources in to table %p\n", mResources); mResources->addEmpty(nextEntryIdx + 1); } if (idmap != NULL) { delete idmap; } MY_TRACE_END(); return onlyEmptyResources; } 
原文链接:https://yq.aliyun.com/articles/657364
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章