Android 换肤之资源(Resources)加载(一)
本篇文章已授权微信大众号 guolin_blog (郭霖)独家发布[2022-1-5]
本系列方案3篇:
- Android 换肤之资源(Resources)加载(一) — 本篇
- setContentView() / LayoutInflater源码分析(二)
- 换肤框架搭建(三)
看完本篇你能够学会什么?
-
Resources在什么时分被解析并加载的
- Application#Resources
- Activity#Resources
-
drawable 怎么加载出来的
-
创立自己的Resources加载自己的资源
-
制造皮肤包”皮肤包”
-
加载“皮肤包”中的资源
tips:源码依据android-30
阅读源码后本篇完结的效果:
效果很简单,2个按钮
- 换肤
- 还原
效果很简单,要点是换肤的时分是加载“皮肤包”中的资源
Resources在什么时分被解析并加载的
Application#Resources
众所周知,java程序都是由main办法开端的,所以咱们就从ActivityThread#main()办法开端阅读源码
在ActivityThread#main()办法中,咱们常常会提到一些关于Looper,handler的逻辑代码,本篇不展开说Looper
#ActivityThread.java
publicstaticvoidmain(String[]args) {
....
// looper
Looper.prepareMainLooper();
// szj 创立 activityThread
ActivityThreadthread=newActivityThread();
thread.attach(false,startSeq);
.....
Looper.loop();
thrownewRuntimeException("Main thread loop unexpectedly exited");
}
本篇要点不是Looper, 来看看 thread.attach(false, startSeq); 办法
#ActivityThread.java
private void attach(boolean system, long startSeq) {
if (!system) { // app进程创立Application
...
//
final IActivityManager mgr = ActivityManager.getService();
try {
// 履行这儿,创立Application
// 经过AMS 调用 ActivityManagerService#attachApplication()
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}else { // system进程创立application
try {
// 很要害的一个类,用来分发activity生命周期
mInstrumentation = new Instrumentation();
mInstrumentation.basicInit(this);
// szj 创立Application Context
ContextImpl context = ContextImpl.createAppContext(
this, getSystemContext().mPackageInfo);
// szj 反射创立 application
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
// 履行application的onCreate() 办法
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}
}
}
这儿需求分析if 分支中的内容,
在if分支中会经过AMS 履行到 ActivityManagerService#attachApplication()
# ActivityManagerService.java
@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {
synchronized (this) {
...
// 绑定application
attachApplicationLocked(thread, callingPid, callingUid, startSeq);
...
}
}
# ActivityManagerService.java
private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
.....
if (app.isolatedEntryPoint != null) {
...
} else if (instr2 != null) {
// 经过ActivityThread#bindApplication() 来创立并绑定application
thread.bindApplication(processName, appInfo, providerList,
instr2.mClass,
profilerInfo, instr2.mArguments,
instr2.mWatcher,
instr2.mUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
app.compat, getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
app.mDisabledCompatChanges);
} else {
....
}
}
# ActivityThread.java
@Override
public final void bindApplication(String processName, ApplicationInfo appInfo,
ProviderInfoList providerList, ComponentName instrumentationName,
ProfilerInfo profilerInfo, Bundle instrumentationArgs,
IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
String buildSerial, AutofillOptions autofillOptions,
ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges) {
// 将传递过来的音讯保存起来
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providerList.getList();
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableBinderTracking = enableBinderTracking;
data.trackAllocation = trackAllocation;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
data.buildSerial = buildSerial;
data.autofillOptions = autofillOptions;
data.contentCaptureOptions = contentCaptureOptions;
data.disabledCompatChanges = disabledCompatChanges;
// 发送handler
sendMessage(H.BIND_APPLICATION, data);
}
# ActivityThread#H.java
class H extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case BIND_APPLICATION:
// 终究音讯在handler中获取
AppBindData data = (AppBindData)msg.obj;
/// 在这儿处理application
handleBindApplication(data);
break;
}
}
}
# ActivityThread.java
private void handleBindApplication(AppBindData data) {
...
Application app;
try {
// 在这儿创立application
app = data.info.makeApplication(data.restrictedBackupMode, null);
}
}
# ActivityThread.java
// 创立application
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
// 创立Context
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
NetworkSecurityConfigProvider.handleNewApplication(appContext);
// 反射创立application
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
// 这儿不会马上履行,由于默认传入的Instrumentation == null
// 当分发activity(performLaunchActivity)时分
// 才会调用 Application app = r.packageInfo.makeApplication(false, mInstrumentation); 来分发Application#onCreate()
if (instrumentation != null) {
try {
// 经过 instrumentation 来调用 Application#onCreate()办法
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
...
}
}
}
- 经过ContextImpl.createAppContext() 创立Context
- 经过反射创立application
- 创立好application后会调用 Application#onCreate()办法
接着履行ContextImpl.createAppContext()
终究会走到LoadedApk#getResources() 上
然后会从LoadedApk#getResources() 履行到 ResourcesManager#getResources()
终究在ResourcesManager中创立Resources
这段源码咱们知道:
-
在程序运行到main办法的时分,咱们会在
ActivtyThread.#attach()
中创立Context,创立Application,并且履行Application#onCreate() -
然后会履行到
LoadedApk.getResources()
去解析获取Resources()- LoadedApk.java 从类名咱们就知道这个类是用来对apk信息解析的
-
终究解析Resources的任务交给了
ResourcesManager#createResources()
Application源码参考自:
- 第一篇
- 第二篇
好了,读到这儿就能够了,来看看Activity#Resources是怎么解析并加载的
Activity#Resources
源码分析从 ActivityThread#performLaunchActivity()开端
为什么要从这儿开端? 写完换肤之后开端framework系列,到时分详细聊~
#ActivityThread.java
privateActivityperformLaunchActivity(ActivityClientRecordr,IntentcustomIntent) {
....省略部分代码
// szj 创立 activity 的上下文
ContextImplappContext=createBaseContextForActivity(r);
Activityactivity=null;
try{
java.lang.ClassLoadercl=appContext.getClassLoader();
// 经过反射创立 activity 的实例
activity=mInstrumentation.newActivity(
cl,component.getClassName(),r.intent);
}catch(Exceptione) {
.....
}
try{
if(activity!=null) {
// szj 创立 PhoneWindow,设置windowManager等操作
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,r.configCallback,
r.assistToken);
activity.mCalled=false;
// szj 分发 onCreate() 事情
if(r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity,r.state,r.persistentState);
}else{
mInstrumentation.callActivityOnCreate(activity,r.state);
}
// 判别是否调用super.onCreate() 办法
if(!activity.mCalled) {
thrownewSuperNotCalledException(
"Activity "+r.intent.getComponent().toShortString()+
" did not call through to super.onCreate()");
}
}
...
} catch(Exceptione) {
...
}
returnactivity;
}
在performLaunchActivity()这段代码中有几个要点:
- createBaseContextForActivity() 创立ContextImpl
- mInstrumentation.newActivity(,,,); 经过反射创立Activity实例
- 然后会调用Activity#attach() 办法绑定window等操作
- 绑定了window之后会当即调用Activity#onCreate()进行页面初始化
本篇要点是Context,其他的先不关注,先来看看createBaseContextForActivity() 代码
#ContextImpl.java
@UnsupportedAppUsage
staticContextImplcreateActivityContext(ActivityThreadmainThread,
LoadedApkpackageInfo,ActivityInfoactivityInfo,IBinderactivityToken,intdisplayId,
ConfigurationoverrideConfiguration) {
....
/// szj创立Context
ContextImplcontext=newContextImpl(null,mainThread,packageInfo,null,
activityInfo.splitName,activityToken,null,0,classLoader,null);
...
finalResourcesManagerresourcesManager=ResourcesManager.getInstance();
/// szj 经过ResourcesManager创立Resources
context.setResources(resourcesManager.createBaseTokenResources(activityToken,
packageInfo.getResDir(),
....));
returncontext;
}
终究会调用到 ResourcesManager.getInstance().createBaseTokenResources() 办法
终究
- activity创立Resurces
- application创立Resurces
都是调用到ResourcesManager#createResources()
来创立Resources
这儿还用到了一个类:ResourcesKey 这个类主要效果便是来存储数据,以及做一些校验等
ResourcesManager#createResources()源码分析
#ResourcesManager.java
private@NullableResourcescreateResources(@NullableIBinderactivityToken,
@NonNullResourcesKeykey,@NonNullClassLoaderclassLoader) {
synchronized(this) {
//szj 从缓存中找 ResourcesImpl 假如不存在就创立
代码1: ResourcesImplresourcesImpl=findOrCreateResourcesImplForKeyLocked(key);
if(resourcesImpl==null) {
returnnull;
}
if(activityToken!=null) {
// 创立Resources
returncreateResourcesForActivityLocked(activityToken,classLoader,
resourcesImpl,key.mCompatInfo);
}else{
// 直接创立Resources目标
returncreateResourcesLocked(classLoader,resourcesImpl,key.mCompatInfo);
}
}
}
先来看findOrCreateResourcesImplForKeyLocked(key);
#ResourcesManager.java
private@NullableResourcesImplfindOrCreateResourcesImplForKeyLocked(
@NonNullResourcesKeykey) {
// szj查找与ResourcesImpl匹配的缓存资源
ResourcesImplimpl=findResourcesImplForKeyLocked(key);
if(impl==null) {
// szj 创立ResourcesImpl
impl=createResourcesImpl(key);
if(impl!=null) {
// 加入到缓存中
mResourceImpls.put(key,newWeakReference<>(impl));
}
}
returnimpl;
}
这段代码很简单,做了一些缓存,经过createResourcesImpl() 创立了ResourcesImpl
#ResourcesManager.java
private@NullableResourcesImplcreateResourcesImpl(@NonNullResourcesKeykey) {
finalDisplayAdjustmentsdaj=newDisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
// szj创立 AssetManager
finalAssetManagerassets=createAssetManager(key);
if(assets==null) {
returnnull;
}
finalDisplayMetricsdm=getDisplayMetrics(key.mDisplayId,daj);
finalConfigurationconfig=generateConfig(key,dm);
// 依据assetManager 创立一个ResourceImpl
// 其实找资源是 Resources -> ResourcesImpl -> AssetManager
finalResourcesImplimpl=newResourcesImpl(assets,dm,config,daj);
...
returnimpl;
}
要害点又来了:
创立ResourcesImpl需求4个参数:
-
参数一: AssetManager 详细资源办理
(重要)
-
参数二: DisplayMetrics 屏幕的一些封装
- 经过getResources().getDisplayMetrics().density 获取过屏幕的密度
- 经过getResources().getDisplayMetrics().widthPixels 获取过屏幕的宽度等
-
参数三: Configuration 一些配置信息[对本篇来说不重要]
-
参数四: DisplayAdjustments 资源的兼容性等 [对本篇来说不重要]
createAssetManager办法:
#ResourcesManager.java
protected@NullableAssetManagercreateAssetManager(@NonNullfinalResourcesKeykey) {
// szj 创立AssetManager目标
finalAssetManager.Builderbuilder=newAssetManager.Builder();
// key.mResDir 便是apk在手机内存中的的完好途径
if(key.mResDir!=null) {
try{
builder.addApkAssets(loadApkAssets(key.mResDir,false,false));
}catch(IOExceptione) {
returnnull;
}
}
....
if(key.mLibDirs!=null) {
/// 循环lib中的资源
for(finalStringlibDir:key.mLibDirs) {
// .apk
/// 只要.apk文件中才有资源,所以只要有资源的当地
if(libDir.endsWith(".apk")) {
try{
builder.addApkAssets(loadApkAssets(libDir,true/*sharedLib*/,
false/*overlay*/));
}catch(IOExceptione) {
}
}
}
}
...
returnbuilder.build();
}
这段代码经过Builder规划模式,将多个资源文件下的资源都保存起来
多个资源指的是一个项目中的多个lib
来看看单个资源是怎么加载的的(loadApkAssets):
#ResourcesManager.java
// path 表明当时apk在手机中的的完好途径
private@NonNullApkAssetsloadApkAssets(Stringpath,booleansharedLib,booleanoverlay)
throwsIOException{
....
// We must load this from disk.
/// 从磁盘加载apk资源
if(overlay) {
apkAssets=ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path),0/*flags*/);
}else{
apkAssets=ApkAssets.loadFromPath(path,sharedLib?ApkAssets.PROPERTY_DYNAMIC:0);
}
....
returnapkAssets;
}
终究经过静态办法创立ApkAssets:
#ApkAssets.java
publicstatic@NonNullApkAssetsloadOverlayFromPath(@NonNullStringidmapPath,
@PropertyFlagsintflags)throwsIOException{
returnnewApkAssets(FORMAT_IDMAP,idmapPath,flags,null/* assets */);
}
publicstatic@NonNullApkAssetsloadFromPath(@NonNullStringpath,@PropertyFlagsintflags)
throwsIOException{
returnnewApkAssets(FORMAT_APK,path,flags,null/* assets */);
}
创立ApkAssets的时分便是经过
- 一个变量来符号当时是什么文件
- 并且保存文件途径
这个变量一共有4种类型:
- FORMAT_APK 符号为apk文件
- FORMAT_IDMAP 符号为idmap文件
- FORMAT_ARSC 符号为 resources.arsc文件
- FORMAT_DIR 符号为是一个目录
默认都是符号为apk文件,由于默认加载的便是.apk文件
这儿着重提一下 resources.arsc 文件
这个文件是打包的时分自动生成的,会寄存一些资源下的信息,例如图中的id等等,悉数资源都能够在这儿面找到!
OK,回到主题,这儿就不扯了
当解析了apk之后,就会调用 AssetManager.Builder#build()办法
#ResourcesManager.java
protected@NullableAssetManagercreateAssetManager(@NonNullfinalResourcesKeykey) {
finalAssetManager.Builderbuilder=newAssetManager.Builder();
if(key.mResDir!=null) {
try{
/// 上面代码将apk途径都解析好了
builder.addApkAssets(loadApkAssets(key.mResDir,false,false));
}catch(IOExceptione) {
returnnull;
}
}
...
// 现在履行build()
returnbuilder.build();
}
#AssetManager.Builder.java
publicAssetManagerbuild() {
....
finalApkAssets[]apkAssets=newApkAssets[totalApkAssetCount];
....
finalAssetManagerassetManager=newAssetManager(false/*sentinel*/);
// 终究交给 nativeSetApkAssets() 来办理
AssetManager.nativeSetApkAssets(assetManager.mObject,apkAssets,
false/*invalidateCaches*/);
assetManager.mLoaders=mLoaders.isEmpty()?null
:mLoaders.toArray(newResourcesLoader[0]);
returnassetManager;
}
终究经过AssetManager.Builder 来创立了AssetManager
并且由ApkAssets保存了apk的一些信息,例如途径,文件类型等
终究创立好AssetManager交给ResourcesImpl来办理
#ResourcesManager.java
private@NullableResourcesImplcreateResourcesImpl(@NonNullResourcesKeykey) {
finalDisplayAdjustmentsdaj=newDisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
/// 方才经过AssetManager.Builder() 来创立的AssetManager
finalAssetManagerassets=createAssetManager(key);
if(assets==null) {
returnnull;
}
// 交给ResourcesImpl 来办理
finalResourcesImplimpl=newResourcesImpl(assets,dm,config,daj);
returnimpl;
}
在退回到最外层:
#ResourcesManager.java
private@NullableResourcescreateResources(@NullableIBinderactivityToken,
@NonNullResourcesKeykey,@NonNullClassLoaderclassLoader) {
synchronized(this) {
/// 方才走的这创立的ResourcesImpl
ResourcesImplresourcesImpl=findOrCreateResourcesImplForKeyLocked(key);
if(resourcesImpl==null) {
returnnull;
}
if(activityToken!=null) {
// 创立Resources
returncreateResourcesForActivityLocked(activityToken,classLoader,
resourcesImpl,key.mCompatInfo);
}else{
// 直接创立Resources目标
returncreateResourcesLocked(classLoader,resourcesImpl,key.mCompatInfo);
}
}
}
经过findOrCreateResourcesImplForKeyLocked() 中找或许创立 ResourcesImpl
终究将ResourcesImpl交给Resources来办理
走到这儿Resources就创立好了
这儿有许多角色来捋一下:
- ResourcesManager 用来创立Resources
- ResourcesImpl 用来创立AssetManager,Resources的详细完结,用来详细读取资源
- AssetManager 办理apk,解析app/多个lib 下的资源
- ApkAssets 用来记录apk信息
- Resources 用来办理ResourcesImpl
drawable 怎么加载出来的
相信咱们在开发中常常写这种代码,这一小节来看看他是怎么加载出来的
#Context.java
publicfinalDrawablegetDrawable(@DrawableResintid) {
returngetResources().getDrawable(id,getTheme());
}
#Resources.java
publicDrawablegetDrawable(@DrawableResintid,@NullableThemetheme)
throwsNotFoundException{
returngetDrawableForDensity(id,0,theme);
}
publicDrawablegetDrawableForDensity(@DrawableResintid,intdensity,@NullableThemetheme) {
finalTypedValuevalue=obtainTempTypedValue();
try{
...
returnloadDrawable(value,id,density,theme);
}finally{
releaseTempTypedValue(value);
}
}
DrawableloadDrawable(@NonNullTypedValuevalue,intid,intdensity,@NullableThemetheme)
throwsNotFoundException{
/// 终究经过ResourcesImpl 来加载drawable
returnmResourcesImpl.loadDrawable(this,value,id,density,theme);
}
#ResourcesImpl.java
DrawableloadDrawable(@NonNullResourceswrapper,@NonNullTypedValuevalue,intid,
intdensity,@NullableResources.Themetheme)
throwsNotFoundException{
....
Drawabledr;
if(cs!=null) {
....
}elseif(isColorDrawable) {
dr=newColorDrawable(value.data);
}else{
// szj走这儿
dr=loadDrawableForCookie(wrapper,value,id,density);
}
}
#ResourcesImpl.java
privateDrawableloadDrawableForCookie(@NonNullResourceswrapper,@NonNullTypedValuevalue,
intid,intdensity) {
....
try{
....
try{
// 判别drawable是否是xml
if(file.endsWith(".xml")) {
finalStringtypeName=getResourceTypeName(id);
/// 判别是否是色彩
if(typeName!=null&&typeName.equals("color")) {
/// 是色彩
dr=loadColorOrXmlDrawable(wrapper,value,id,density,file);
}else{
// 加载xml
dr=loadXmlDrawable(wrapper,value,id,density,file);
}
}else{
// 是图片
// szj mAssets = AssetManager()
// 翻开这张图片
// 终究获取到的是stream
finalInputStreamis=mAssets.openNonAsset(
value.assetCookie,file,AssetManager.ACCESS_STREAMING);
finalAssetInputStreamais=(AssetInputStream)is;
dr=decodeImageDrawable(ais,wrapper,value);
}
}
...
}catch(Exception|StackOverflowErrore) {
...
throwrnf;
returndr;
}
- 加载色彩:
#ResourcesImpl.java
privateDrawableloadColorOrXmlDrawable(@NonNullResourceswrapper,@NonNullTypedValuevalue,
intid,intdensity,Stringfile) {
try{
/// 加载色彩
ColorStateListcsl=loadColorStateList(wrapper,value,id,null);
returnnewColorStateListDrawable(csl);
}catch(NotFoundExceptionoriginalException) {
// 假如报错就测验当作xml中的drawable加载
try{
returnloadXmlDrawable(wrapper,value,id,density,file);
}catch(Exceptionignored) {
// If fallback also fails, throw the original exception
throworiginalException;
}
}
}
- 加载xml中的drawable
#ResourcesImpl.java
privateDrawableloadXmlDrawable(@NonNullResourceswrapper,@NonNullTypedValuevalue,
intid,intdensity,Stringfile)
throwsIOException,XmlPullParserException{
try(
XmlResourceParserrp=
loadXmlResourceParser(file,id,value.assetCookie,"drawable")
) {
returnDrawable.createFromXmlForDensity(wrapper,rp,density,null);
}
}
- 是图片,经过AssetManager来翻开图片,获取到输入流,并转换为图片
#ResourcesImpl.java
finalDrawabledr;
finalInputStreamis=mAssets.openNonAsset(
value.assetCookie,file,AssetManager.ACCESS_STREAMING);
finalAssetInputStreamais=(AssetInputStream)is;
dr=decodeImageDrawable(ais,wrapper,value);
/// 将输入流的内容转换为drawable
privateDrawabledecodeImageDrawable(@NonNullAssetInputStreamais,
@NonNullResourceswrapper,@NonNullTypedValuevalue) {
ImageDecoder.Sourcesrc=newImageDecoder.AssetInputStreamSource(ais,
wrapper,value);
try{
returnImageDecoder.decodeDrawable(src, (decoder,info,s)->{
decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
});
}catch(IOExceptionioe) {
returnnull;
}
}
再来一波小结:
Resources其实做的工作很有限,基本便是操控ResourcesImpl来控制AssetManager来获取资源
AssetManager会经过ApkAssets来存储apk信息,包括途径,类型等
然后AssetManager会经过apk的地址, 找到详细apk的文件,调用nativeSetApkAssets() 去解析apk中的详细资源
当咱们加载一个drawable的时分
Resources会调用ResourcesImpl#loadDrawable() 来加载图片
然后会判别加载的drawable是一张图片,还是自定义的xml,或许drawable是一个色彩
- 假如是图片,就经过AssetManager#openNonAsset()来解析资源图片,获取到intputStream流,来解码成drawable
- 假如是xml,那么就经过XmlResourceParser来解析,终究生成drawable [这儿面还有些细节,都是些if判别,就没看了]
- 假如是色彩,和xml相似,也是一点点解析
创立自己的Resources加载本地资源
正常咱们加载资源是经过getResources().getDrawable() 来加载
现在想完结的是,用我自己的Resources,来加载咱们自己的资源
那么首要就要获取到当时程序在手机内存中的途径
getApplicationContext().getPackageResourcePath()
由于这是个躲藏文件夹,所以只能从这儿看,在手机上是找不到的..
接下来创立一个AssetManager,用来解析apk中的资源等
在源码中,是经过AssetManager.Builder来构建AssetManager, 但是Builder类被躲藏掉了
并且构造办法都被躲藏掉了,所以只能经过反射来构建AssetManager
构建AssetManager时,需求经过AssetManager#nativeSetApkAssets() 来解析apk中的资源
这儿咱们选择反射 addAssetPath() 办法
经过addAssetPath调用 addAssetPathInternal 终究调用到nativeSetApkAssets()
这儿只需求传入一个apk在手机的途径即可
这儿需求留意的是不能直接反射addAssetPathInternal(),能够看到图中addAssetPathInternal()左侧有一把锁,反射不了.
当时代码:
try(
// 创立AssetManager
AssetManagerassetManager=AssetManager.class.newInstance()
) {
// 反射调用 创立AssetManager#addAssetPath
Methodmethod=AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
// 获取到当时apk在手机中的途径
Stringpath=getApplicationContext().getPackageResourcePath();
Log.i("szjPath",path);
/// 反射履行办法
method.invoke(assetManager,path);
// 创立自己的Resources
Resourcesresources=newResources(assetManager,createDisplayMetrics(),createConfiguration());
// 依据id来获取图片
Drawabledrawable=resources.getDrawable(R.drawable.ic_launcher_background,null);
// 设置图片
mImageView.setImageDrawable(drawable);
}catch(Exceptione) {
e.printStackTrace();
}
// 这些关于屏幕的就用本来的就能够
publicDisplayMetricscreateDisplayMetrics() {
returngetResources().getDisplayMetrics();
}
publicConfigurationcreateConfiguration() {
returngetResources().getConfiguration();
}
这样一来,就能够用咱们自己的Resources来获取本身的资源了!
效果没啥好说的,便是一上来就加载
接下来咱们测验加载另一个apk中的资源
首要咱们需求一个有一个apk让咱们来加载,便是一般说的“皮肤包”
制造“皮肤包”
皮肤包便是一个只要资源文件的apk
能够新建一个项目,然后寄存对应的资源即可
也能够在同目录下将lib改为application,为了好保管,咱们就使用这种办法
- 直接创立module
- 创立lib
- 直接输入姓名创当即可
- 将lib修改为application,并添加applicationId, 并且添加同名资源(制造皮肤包)
- 生成“皮肤包”(skin-pack-making-debug.apk)
此时,皮肤包咱们就制造好了,skin-pack-making-debug.apk,咱们将它放入到手机内存中测验加载一下
使用皮肤包
为了测试方便,咱们直接将“皮肤包”放入到根目录即可
adb push apk途径 根目录
adb shell
ls sdcard
加载皮肤包中的apk
publicstaticfinalStringPATH=Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"skin-pack-making-debug.apk";
try{
AssetManagerassetManager=AssetManager.class.newInstance();
@SuppressLint("DiscouragedPrivateApi")
Methodmethod=AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
method.setAccessible(true);
/// 反射履行办法
method.invoke(assetManager,PATH);
// 创立自己的Resources
Resourcesresources=newResources(assetManager,createDisplayMetrics(),createConfiguration());
/*
* getIdentifier 依据姓名拿id
* name: 资源名
* defType: 资源类型
* defPackage: 所在包名
* return:假如返回0则表明没有找到
*/
/// 加载drawable
intdrawableId=resources.getIdentifier("shark","drawable","com.skin.skin_pack_making");
// 加载string
intstringId=resources.getIdentifier("hello_skin","string","com.skin.skin_pack_making");
// 加载color
intcolorId=resources.getIdentifier("global_background","color","com.skin.skin_pack_making");
mImageView.setImageDrawable(resources.getDrawable(drawableId,null));
mTextView.setText(resources.getString(stringId));
mTextView.setBackgroundColor(resources.getColor(colorId,null));
}catch(Exceptione) {
e.printStackTrace();
showDialog("出错了"+e.getMessage());
}
需求留意的是,这儿得经过姓名来获取id
当咱们加载一个drawable,id,color或许string的时分,在加载的时分都会替换成id
各个apk生成的id肯定是各不相同的,所以咱们找的是皮肤包中的资源id,
最后再来看看今天完结的效果:
请下载level-simple分支:完好代码
git clone -b level-simple gitee.com/lanyangyang…
原创不易,您的点赞便是对我最大的支持!
下一篇:android setContentView() / LayoutInflater 源码解析
热门文章:
- android MD 进阶[五] CoordinatorLayout 从源码到实战..
- android View生命周期
- android MD进阶[四] NestedScrollView 从源码到实战..
- android 浅析RecyclerView回收复用机制及实战(仿探探效果)
- Android进阶 -事情抵触与解决方案大揭秘