插件式换肤框架搭建资源加载源码分析.docx
- 文档编号:7031640
- 上传时间:2023-01-16
- 格式:DOCX
- 页数:17
- 大小:228.84KB
插件式换肤框架搭建资源加载源码分析.docx
《插件式换肤框架搭建资源加载源码分析.docx》由会员分享,可在线阅读,更多相关《插件式换肤框架搭建资源加载源码分析.docx(17页珍藏版)》请在冰豆网上搜索。
插件式换肤框架搭建资源加载源码分析
插件式换肤框架搭建-资源加载源码分析
1.概述
大部分控件我们都会使用,但是我们未必知道其资源加载的原理,目前换肤的框架比较多我们可以随随便便拿过来用,但早在几年前这些资料是比较少的,如果想做一个换肤的框架那就只能自己一点一点啃源码。
如果说我们现在不去用第三方的开源框架,要做一个换肤的功能,摆在我们面前的其实只有一个问题需要解决,那就是如何读取另外一个皮肤apk中的资源。
2.资源加载源码分析
2.1我们先来看一下ImageView的scr属性到底是怎么加载图片资源的:
android: layout_width="wrap_content" android: src="@drawable/app_icon" android: layout_height="wrap_content"/> //ImageView.java解析属性 finalTypedArraya=context.obtainStyledAttributes( attrs,R.styleable.ImageView,defStyleAttr,defStyleRes); //通过TypedArray获取图片 finalDrawabled=a.getDrawable(R.styleable.ImageView_src); if(d! =null){ setImageDrawable(d); } //TypedArray.getDrawable()方法 publicDrawablegetDrawable(@StyleableResintindex){ //省略部分代码.... //加载资源其实是通过mResources去获取的 returnmResources.loadDrawable(value,value.resourceId,mTheme); } 2.2Resource创建过程分析: 我们在Activity中也经常这样使用context.getResources().getColor(R.id.title_color),那么这个Resources实例是怎么创建的呢? 我们可以先从context的实现类ContextImpl入手 privateContextImpl(ContextImplcontainer,ActivityThreadmainThread, LoadedApkpackageInfo,IBinderactivityToken,UserHandleuser,intflags, Displaydisplay,ConfigurationoverrideConfiguration,intcreateDisplayWithId){ ...... Resourcesresources=packageInfo.getResources(mainThread); if(resources! =null){ //不会走此分支,因为6.0中还不支持多屏显示,虽然已经有不少相关代码了,7.0以及正式支持多屏操作了 if(displayId! =Display.DEFAULT_DISPLAY ||overrideConfiguration! =null ||(compatInfo! =null&&compatInfo.applicationScale ! =resources.getCompatibilityInfo().applicationScale)){ ...... } } ...... mResources=resources; } //packageInfo.getResources方法 publicResourcesgetResources(ActivityThreadmainThread){ //缓存机制,如果LoadedApk中的mResources已经初始化则直接返回, //否则通过ActivityThread创建resources对象 if(mResources==null){ mResources=mainThread.getTopLevelResources(mResDir,mSplitResDirs,mOverlayDirs, mApplicationInfo.sharedLibraryFiles,Display.DEFAULT_DISPLAY,this); } returnmResources; } 最终会来到ResourcesManager的getResources方法 public@NonNullResourcesgetResources(@NullableIBinderactivityToken, @NullableStringresDir,//app资源文件夹路径,实际上是apk文件的路径,如/data/app/包名/base.apk @NullableString[]splitResDirs,//针对一个app由多个apk组成(将原本一个apk切片为若干apk)时,每个子apk中的资源文件夹 @NullableString[]overlayDirs, @NullableString[]libDirs,//app依赖的共享jar/apk路径 intdisplayId, @NullableConfigurationoverrideConfig, @NonNullCompatibilityInfocompatInfo, @NullableClassLoaderclassLoader){ try{ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,"ResourcesManager#getResources"); //以apk路径为参数创建key finalResourcesKeykey=newResourcesKey( resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfig! =null? newConfiguration(overrideConfig): null,//Copy compatInfo); classLoader=classLoader! =null? classLoader: ClassLoader.getSystemClassLoader(); returngetOrCreateResources(activityToken,key,classLoader); }finally{ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } } private@NonNullResourcesgetOrCreateResources(@NullableIBinderactivityToken, @NonNullResourcesKeykey,@NonNullClassLoaderclassLoader){ synchronized(this){ //....... if(activityToken! =null){ //根据key从缓存里面找找ResourcesImpl ResourcesImplresourcesImpl=findResourcesImplForKeyLocked(key); if(resourcesImpl! =null){ if(DEBUG){ Slog.d(TAG,"-usingexistingimpl="+resourcesImpl); } //如果resourcesImpl有那么根据resourcesImpl和classLoader从缓存找找Resource returngetOrCreateResourcesForActivityLocked(activityToken,classLoader, resourcesImpl); } //WewillcreatetheResourcesImplobjectoutsideofholdingthislock. }else{ //....... } } //Ifwe'rehere,wedidn'tfindasuitableResourcesImpltouse,socreateonenow. //这个比较重要createResourcesImpl通过key ResourcesImplresourcesImpl=createResourcesImpl(key); synchronized(this){ //....... finalResourcesresources; if(activityToken! =null){ //根据resourcesImpl和classLoader获取Resources resources=getOrCreateResourcesForActivityLocked(activityToken,classLoader, resourcesImpl); }else{ resources=getOrCreateResourcesLocked(classLoader,resourcesImpl); } returnresources; } } private@NonNullResourcesImplcreateResourcesImpl(@NonNullResourcesKeykey){ finalDisplayAdjustmentsdaj=newDisplayAdjustments(key.mOverrideConfiguration); daj.setCompatibilityInfo(key.mCompatInfo); //创建AssetManager finalAssetManagerassets=createAssetManager(key); finalDisplayMetricsdm=getDisplayMetrics(key.mDisplayId,daj); finalConfigurationconfig=generateConfig(key,dm); //根据AssetManager创建一个ResourcesImpl其实找资源是: Resources->ResourcesImpl->AssetManager finalResourcesImplimpl=newResourcesImpl(assets,dm,config,daj); if(DEBUG){ Slog.d(TAG,"-creatingimpl="+impl+"withkey: "+key); } returnimpl; } @VisibleForTesting protected@NonNullAssetManagercreateAssetManager(@NonNullfinalResourcesKeykey){ //创建一个AssetManager对象 AssetManagerassets=newAssetManager(); //resDircanbenullifthe'android'packageiscreatinganewResourcesobject. //Thisisfine,sinceeachAssetManagerautomaticallyloadsthe'android'package //already. //将app中的资源路径都加入到AssetManager对象中 if(key.mResDir! =null){ //这个方法很重要,待会我们就是用它去加载皮肤的apk if(assets.addAssetPath(key.mResDir)==0){ thrownewResources.NotFoundException("failedtoaddassetpath"+key.mResDir); } } if(key.mLibDirs! =null){ for(finalStringlibDir: key.mLibDirs){ //仅仅选择共享依赖中的apk,因为jar中不会有资源文件 if(libDir.endsWith(".apk")){ //Avoidopeningfilesweknowdonothaveresources, //likecode-ly.jarfiles. if(assets.addAssetPathAsSharedLibrary(libDir)==0){ Log.w(TAG,"Assetpath'"+libDir+ "'doesnotexistorcontainsnoresources."); } } } } returnassets; } /** *GetsanexistingResourcesobjectiftheclassloaderandResourcesImplarethesame, *otherwisecreatesanewResourcesobject. */ private@NonNullResourcesgetOrCreateResourcesLocked(@NonNullClassLoaderclassLoader, @NonNullResourcesImplimpl){ //FindanexistingResourcesthathasthisResourcesImplset. finalintrefCount=mResourceReferences.size(); for(inti=0;i WeakReference //从软引用缓存里面找一找 Resourcesresources=weakResourceRef.get(); if(resources! =null&& Objects.equals(resources.getClassLoader(),classLoader)&& resources.getImpl()==impl){ if(DEBUG){ Slog.d(TAG,"-usingexistingref="+resources); } returnresources; } } //CreateanewResourcesreferenceandusetheexistingResourcesImplobject. //创建一个Resources,Resource有好几个构造方法,每个版本之间有稍微的差别 //有的版本是用的这一个构造方法Resources(assets,dm,config,compatInfo) Resourcesresources=newResources(classLoader); resources.setImpl(impl); //加入缓存 mResourceReferences.add(newWeakReference<>(resources)); if(DEBUG){ Slog.d(TAG,"-creatingnewref="+resources); Slog.d(TAG,"-settingref="+resources+"withimpl="+impl); } returnresources; } 【看了这么多我们大致可以总结一下Resources的创建流程了: 】 -packageInfo.getResources(mainThread)->mainThread.getTopLevelResources()->mResourcesManager.getResources()->getOrCreateResources()这里首先会找ResourcesImpl缓存如果有则会获取Resource缓存返回; -如果没有ResourcesImpl缓存,那么回去创建ResourcesImpl,ResourcesImpl的创建依赖于AssetManager; -AssetManager的创建是通过直接实例化对象调用了一个addAssetPath(path)方法把应用的apk路径添加到AssetManager,addAssetPath()方法请看源码解释。 -创建好ResourcesImpl之后会再去缓存中找Resource如果没有,那么则会创建Resource并将其缓存,创建我们看到的源码是newResources(classLoader),resources.setImpl(impl)而不同的版本可能是newResources(assets,dm,config,compatInfo)具体请看6.0源码。 3.加载皮肤资源 如果大致知道了资源的加载流程以及Resource的创建过程,现在我们要去加载另外一个apk中的资源就好办了,只需要自己创建一个Resource对象,下面这段代码网上找一大堆,如果分析过源码相信你会有更深的认识: publicclassMainActivityextendsAppCompatActivity{ @Override protectedvoidonCreate(BundlesavedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try{ ResourcessuperRes=getResources(); //创建AssetManager,但是不能直接new所以只能通过反射 AssetManagerassetManager=AssetManager.class.newInstance(); //反射获取addAssetPath方法 MethodaddAssetPathMethod=AssetManager.class.getDeclaredMethod("addAssetPath",String.class); //皮肤包的路径: 本地sdcard/plugin.skin StringskinPath=Environment.getExternalStorageDirectory().getAbsoluteFile()+File.separator+"plugin.skin"; //反射调用addAssetPath方法 addAssetPathMethod.invoke(assetManager,skinPath); //创建皮肤的Resources对象 ResourcesskinResources=newResources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration()); //通过资源名称,类型,包名获取Id intbgId=skinResources.getIdentifier("main_bg","drawable","com.hc.skin"); DrawablebgDrawable=skinResources.getDrawable(bgId); //设置背景 findViewById(R.id.activity_main).setBackgroundDrawable(bgDrawable); }catch(Exceptione){ e.printStackTrace(); } } } 4.AssetManager创建过程分析 下面的分析希望不要有强迫症,看不懂其实也不打紧因为涉及到JNI。 通过前面的分析可知,Android系统中实际对资源的管理是AssetManager类.每个Resources对象都会关联一个AssetManager对象,Resources将对资源的操作大多数委托给了AssetManager。 当然有些源码还有一层ResourcesImpl刚刚我们也看到了。 另外还会存在一个native层的AssetManager对象与Java层的这个AssetManager对象相对应,而这个native层AssetManager对象在内存的地址存储在java层的AssetManager.mObject中。 所以在java层AssetManager的jni方法中可以快速找到它对应的native层的AssetManager对象。 4.1AssetManager的init() /** *CreateanewAssetManagercontainingonlythebasicsystemassets. *Applicationswillnotgenerallyusethismethod,insteadretrievingthe *appropriateassetmanagerwith{@linkResources#getAssets}.Notfor *usebyapplications. *{@hide} */ publicAssetManager(){ synchronized(this){ if(DEBUG_REFS){ mNumRefs=0; incRefsLocked(this.hashCode()); } init(false); if(localLOGV)Log.v(TAG,"Newassetmanager: "+thi
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 插件式换肤框架搭建 资源加载源码分析 插件 式换肤 框架 搭建 资源 加载 源码 分析