版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、插件式换肤框架搭建 - 资源加载源码分析1. 概述大部分控件我们都会使用,但是我们未必知道其资源加载的原理,目前换肤的框架比较多我们可以随随便便拿过来用,但早在几年前这些资料是比较少的,如果想做一个换肤的框架那就只能自己一点一点啃源码。 如果说我们现在不去用第三方的开源框架,要做一个换肤的功能,摆在我们面前的其实只有一个问题需要解决,那就是如何读取另外一个皮肤apk中的资源。 2. 资源加载源码分析2.1 我们先来看一下ImageView的scr属性到底是怎么加载图片资源的: / ImageView.java 解析属性 final TypedArray a = context.obtainSt
2、yledAttributes( attrs, R.styleable.ImageView, defStyleAttr, defStyleRes); / 通过TypedArray获取图片 final Drawable d = a.getDrawable(R.styleable.ImageView_src); if (d != null) setImageDrawable(d); / TypedArray.getDrawable() 方法 public Drawable getDrawable(StyleableRes int index) / 省略部分代码. / 加载资源其实是通过mResour
3、ces去获取的 return mResources.loadDrawable(value, value.resourceId, mTheme); 2.2 Resource创建过程分析: 我们在Activity中也经常这样使用context.getResources().getColor(R.id.title_color),那么这个Resources实例是怎么创建的呢?我们可以先从context的实现类ContextImpl入手private ContextImpl(ContextImpl container, ActivityThread mainThread, LoadedApk packa
4、geInfo, IBinder activityToken, UserHandle user, int flags, Display display, Configuration overrideConfiguration, int createDisplayWithId) . Resources resources = packageInfo.getResources(mainThread); if (resources != null) / 不会走此分支,因为6.0中还不支持多屏显示,虽然已经有不少相关代码了,7.0以及正式支持多屏操作了 if (displayId != Display.
5、DEFAULT_DISPLAY | overrideConfiguration != null | (compatInfo != null & compatInfo.applicationScale != resources.getCompatibilityInfo().applicationScale) . . mResources = resources;/ packageInfo.getResources 方法public Resources getResources(ActivityThread mainThread) / 缓存机制,如果LoadedApk中的mResources已经初
6、始化则直接返回, / 否则通过ActivityThread创建resources对象 if (mResources = null) mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this); return mResources;最终会来到ResourcesManager的getResources方法 public NonNull Resources ge
7、tResources(Nullable IBinder activityToken, Nullable String resDir, /app资源文件夹路径,实际上是apk文件的路径,如/data/app/包名/base.apk Nullable String splitResDirs, /针对一个app由多个apk组成(将原本一个apk切片为若干apk)时,每个子apk中的资源文件夹 Nullable String overlayDirs, Nullable String libDirs, / app依赖的共享jar/apk路径 int displayId, Nullable Configu
8、ration overrideConfig, NonNull CompatibilityInfo compatInfo, Nullable ClassLoader classLoader) try Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, ResourcesManager#getResources); / 以apk路径为参数创建key final ResourcesKey key = new ResourcesKey( resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideCo
9、nfig != 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); private NonNull Resource
10、s getOrCreateResources(Nullable IBinder activityToken, NonNull ResourcesKey key, NonNull ClassLoader classLoader) synchronized (this) / . if (activityToken != null) / 根据key从缓存里面找找 ResourcesImpl ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key); if (resourcesImpl != null) if (DEBUG) Sl
11、og.d(TAG, - using existing impl= + resourcesImpl); / 如果 resourcesImpl 有 那么根据resourcesImpl 和classLoader 从缓存找找 Resource return getOrCreateResourcesForActivityLocked(activityToken, classLoader, resourcesImpl); / We will create the ResourcesImpl object outside of holding this lock. else / . / If were he
12、re, we didnt find a suitable ResourcesImpl to use, so create one now. / 这个比较重要 createResourcesImpl 通过 key ResourcesImpl resourcesImpl = createResourcesImpl(key); synchronized (this) / . final Resources resources; if (activityToken != null) / 根据resourcesImpl和classLoader获取Resources resources = getOrCr
13、eateResourcesForActivityLocked(activityToken, classLoader, resourcesImpl); else resources = getOrCreateResourcesLocked(classLoader, resourcesImpl); return resources; private NonNull ResourcesImpl createResourcesImpl(NonNull ResourcesKey key) final DisplayAdjustments daj = new DisplayAdjustments(key.
14、mOverrideConfiguration); daj.setCompatibilityInfo(key.mCompatInfo); / 创建AssetManager final AssetManager assets = createAssetManager(key); final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj); final Configuration config = generateConfig(key, dm); / 根据AssetManager 创建一个ResourcesImpl 其实找资源是:
15、 Resources - ResourcesImpl - AssetManager final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj); if (DEBUG) Slog.d(TAG, - creating impl= + impl + with key: + key); return impl; VisibleForTesting protected NonNull AssetManager createAssetManager(NonNull final ResourcesKey key) / 创建一个A
16、ssetManager对象 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. / 将app中的资源路径都加入到AssetManager对象中 if (key.mResDir != null) / 这个方法很重要,待会我们就是
17、用它去加载皮肤的apk if (assets.addAssetPath(key.mResDir) = 0) throw new Resources.NotFoundException(failed to add asset path + key.mResDir); if (key.mLibDirs != null) for (final String libDir : key.mLibDirs) / 仅仅选择共享依赖中的apk,因为jar中不会有资源文件 if (libDir.endsWith(.apk) / Avoid opening files we know do not have re
18、sources, / like code-ly .jar files. if (assets.addAssetPathAsSharedLibrary(libDir) = 0) Log.w(TAG, Asset path + libDir + does not exist or contains no resources.); return assets; /* * Gets an existing Resources object if the class loader and ResourcesImpl are the same, * otherwise creates a new Reso
19、urces object. */ private NonNull Resources getOrCreateResourcesLocked(NonNull ClassLoader classLoader, NonNull ResourcesImpl impl) / Find an existing Resources that has this ResourcesImpl set. final int refCount = mResourceReferences.size(); for (int i = 0; i refCount; i+) WeakReference weakResource
20、Ref = 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 Resou
21、rces reference and use the existing ResourcesImpl object. / 创建一个Resources ,Resource有好几个构造方法,每个版本之间有稍微的差别 / 有的版本是用的这一个构造方法 Resources(assets, dm, config, compatInfo) Resources resources = new Resources(classLoader); resources.setImpl(impl); / 加入缓存 mResourceReferences.add(new WeakReference(resources);
22、if (DEBUG) Slog.d(TAG, - creating new ref= + resources); Slog.d(TAG, - setting ref= + resources + with impl= + impl); return resources;【看了这么多我们大致可以总结一下Resources的创建流程了:】 - packageInfo.getResources(mainThread) - mainThread.getTopLevelResources() - mResourcesManager.getResources() - getOrCreateResource
23、s() 这里首先会找ResourcesImpl缓存如果有则会获取Resource缓存返回; - 如果没有ResourcesImpl缓存,那么回去创建ResourcesImpl,ResourcesImpl的创建依赖于AssetManager ; - AssetManager的创建是通过直接实例化对象调用了一个addAssetPath(path)方法把应用的apk路径添加到AssetManager,addAssetPath()方法请看源码解释。 - 创建好ResourcesImpl之后会再去缓存中找Resource如果没有,那么则会创建Resource并将其缓存,创建我们看到的源码是new Res
24、ources(classLoader),resources.setImpl(impl) 而不同的版本可能是 new Resources(assets, dm, config, compatInfo) 具体请看6.0源码。3. 加载皮肤资源如果大致知道了资源的加载流程以及Resource的创建过程,现在我们要去加载另外一个apk中的资源就好办了,只需要自己创建一个Resource对象,下面这段代码网上找一大堆,如果分析过源码相信你会有更深的认识:public class MainActivity extends AppCompatActivity Override protected void
25、onCreate(Bundle savedInstanceState) super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try Resources superRes = getResources(); / 创建AssetManager,但是不能直接new所以只能通过反射 AssetManager assetManager = AssetManager.class.newInstance(); / 反射获取addAssetPath方法 MethodaddAssetPathMethod= Ass
26、etManager.class.getDeclaredMethod(addAssetPath,String.class); / 皮肤包的路径: 本地sdcard/plugin.skin StringskinPath= Environment.getExternalStorageDirectory().getAbsoluteFile()+ File.separator+plugin.skin; / 反射调用addAssetPath方法 addAssetPathMethod.invoke(assetManager, skinPath); / 创建皮肤的Resources对象 Resourcessk
27、inResources=new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration(); / 通过资源名称,类型,包名获取Id int bgId = skinResources.getIdentifier(main_bg,drawable,com.hc.skin); Drawable bgDrawable = skinResources.getDrawable(bgId); / 设置背景 findViewById(R.id.activity_main).setBackgroundDrawab
28、le(bgDrawable); catch (Exception e) e.printStackTrace(); 4. AssetManager创建过程分析下面的分析希望不要有强迫症,看不懂其实也不打紧因为涉及到JNI。通过前面的分析可知,Android系统中实际对资源的管理是AssetManager类.每个Resources对象都会关联一个AssetManager对象,Resources将对资源的操作大多数委托给了AssetManager。当然有些源码还有一层 ResourcesImpl 刚刚我们也看到了。 另外还会存在一个native层的AssetManager对象与Java层的这个Ass
29、etManager对象相对应,而这个native层AssetManager对象在内存的地址存储在java层的AssetManager.mObject中。所以在java层AssetManager的jni方法中可以快速找到它对应的native层的AssetManager对象。4.1 AssetManager的init() /* * Create a new AssetManager containing only the basic system assets. * Applications will not generally use this method, instead retrievin
30、g the * appropriate asset manager with link Resources#getAssets. Not for * use by applications. * hide */ public AssetManager() synchronized (this) if (DEBUG_REFS) mNumRefs = 0; incRefsLocked(this.hashCode(); init(false); if (localLOGV) Log.v(TAG, New asset manager: + this); ensureSystemAssets(); /
31、ndk的源码路径 / frameworks/base/core/jni/android_util_AssetManager.cpp / frameworks/base/libs/androidfw/AssetManager.cpp private native final void init(boolean isSystem);static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem) if (isSystem) verifySystemIdmaps(); / Asse
32、tManager.cpp AssetManager* am = new AssetManager(); if (am = NULL) jniThrowException(env, java/lang/OutOfMemoryError, ); return; am-addDefaultAssets(); ALOGV(Created AssetManager %p for Java object %pn, am, clazz); env-SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast(am);bool Asset
33、Manager:addDefaultAssets() const char* root = getenv(ANDROID_ROOT); LOG_ALWAYS_FATAL_IF(root = NULL, ANDROID_ROOT not set); String8 path(root); / framework/framework-res.apk / 初始化的时候会去加载系统的framework-res.apk资源 / 也就是说我们为什么能加载系统的资源如颜色、图片、文字等等 path.appendPath(kSystemAssets); return addAssetPath(path, NU
34、LL);4.2 AssetManager的addAssetPath(String path)方法bool AssetManager:addAssetPath(const String8& path, int32_t* cookie) asset_path ap; / 省略一些校验代码 / 判断是否已经加载过了 for (size_t i=0; imAssetPaths.size(); i+) if (mAssetPathsi.path = ap.path) if (cookie) *cookie = static_cast(i+1); return true; / 检查路径是否有一个andro
35、idmanifest . xml Asset* manifestAsset = const_cast(this)-openNonAssetInPathLocked( kAndroidManifest, Asset:ACCESS_BUFFER, ap); if (manifestAsset = NULL) / 如果不包含任何资源 delete manifestAsset; return false; delete manifestAsset; / 添加 mAssetPaths.add(ap); / 新路径总是补充到最后 if (cookie) *cookie = static_cast(mAss
36、etPaths.size(); if (mResources != NULL) appendPathToResTable(ap); return true;bool AssetManager:appendPathToResTable(const asset_path& ap) const / skip those aps that correspond to system overlays if (ap.isSystemOverlay) return true; Asset* ass = NULL; ResTable* sharedRes = NULL; bool shared = true;
37、 bool onlyEmptyResources = true; MY_TRACE_BEGIN(ap.path.string(); / 资源覆盖机制,暂不考虑 Asset* idmap = openIdmapLocked(ap); size_t nextEntryIdx = mResources-getTableCount(); ALOGV(Looking for resource asset in %sn, ap.path.string(); / 资源包路径不是一个文件夹,那就是一个apk文件了 if (ap.type != kFileTypeDirectory) / 对于app来说,第一次
38、执行时,肯定为0,因为mResources刚创建,还没对其操作 / 下面的分支 指挥在参数是系统资源包路径时,才执行, / 而且系统资源包路径是首次被解析的 / 第二次执行appendPathToResTable,nextEntryIdx就不会为0了 if (nextEntryIdx = 0) / mAssetPaths中存储的第一个资源包路径是系统资源的路径, / 即framework-res.apk的路径,它在zygote启动时已经加载了 / 可以通过mZipSet.getZipResourceTable获得其ResTable对象 sharedRes = const_cast(this)-
39、 mZipSet.getZipResourceTable(ap.path); / 对于APP来说,肯定不为NULL if (sharedRes != NULL) / 得到系统资源包路径中resources.arsc个数 nextEntryIdx = sharedRes-getTableCount(); / 当参数是mAssetPaths中除第一个以外的其他资源资源包路径, / 比如app自己的资源包路径时,走下面的逻辑 if (sharedRes = NULL) / 检查该资源包是否被其他进程加载了,这与ZipSet数据结构有关,后面在详细介绍 ass = const_cast(this)-
40、mZipSet.getZipResourceTableAsset(ap.path); / 对于app自己的资源包来说,一般都会都下面的逻辑 if (ass = NULL) ALOGV(loading resource table %sn, ap.path.string(); / 创建Asset对象,就是打开resources.arsc ass = const_cast(this)- openNonAssetInPathLocked(resources.arsc, Asset:ACCESS_BUFFER, ap); if (ass != NULL & ass != kExcludedAsset)
41、 ass = const_cast(this)- mZipSet.setZipResourceTableAsset(ap.path, ass); / 只有在zygote启动时,才会执行下面的逻辑 / 为系统资源创建 ResTable,并加入到mZipSet里。 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 t for ot
42、hers. ALOGV(Creating shared resources for %s, ap.path.string(); / 创建ResTable对象,并把前面与resources.arsc关联的Asset对象,加入到这个ResTabl中 sharedRes = new ResTable(); sharedRes-add(ass, idmap, nextEntryIdx + 1, false); sharedRes = const_cast(this)- mZipSet.setZipResourceTable(ap.path, sharedRes); else ALOGV(loading
43、 resource table %sn, ap.path.string(); ass = const_cast(is)- openNonAssetInPathLocked(resources.arsc, Asset:ACCESS_BUFFER, ap); shared = false; if (ass != NULL | sharedRes != NULL) & ass != kExcludedAsset) ALOGV(Installing resource asset %p in to table %pn, ass, mResources); / 系统资源包时 if (sharedRes != NULL) ALOGV(Copying existing resources for %s, ap.path.string(); mResources-add(sharedRes); else
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024秋八年级数学上册 第6章 一次函数6.4 课题学习 选择方案教案(新版)苏科版
- 2024秋八年级数学上册 第十五章 分式15.2 分式的运算 4分式的加减-异分母的分式相加减教学设计(新版)新人教版
- 高中语文 第五单元 散而不乱 气脉中贯 第1课 六国论教案4 新人教版选修中国古代诗歌散文鉴赏
- 2024年五年级数学下册 八 探索乐园单元概述与课时安排教案 冀教版
- 2023九年级化学下册 第十二单元 化学与生活 课题2 化学元素与人体健康教案 (新版)新人教版
- 润滑脂 轴承动态寿命试验方法(征求意见稿)
- 运输合同范本(2篇)
- 湖南专升本课件
- 景阳冈课件阅读
- 幼儿园小班音乐《怪汽车》课件
- 专利申请著录项目变更书
- 体检结论模板(共20页)
- 乳腺X线报告书写
- 公司费用请款单
- 全文《以史为鉴持续推动美丽中国建设》PPT
- 《2021国标结构专业图集资料》04G410-2 1.5mX6.0m预应力混凝土屋面板(钢筋混凝土部分)
- 三角函数高考题汇编(共12页)
- 设计方案——喷漆烘干房
- Humpty儿童跌倒评估量表
- 滑触线安装施工方案
- 绿化灌溉用水制度
评论
0/150
提交评论