大家思考一下,如果我们要去给我们的app换肤,肯定需要用到插件化的加载方式,
去加载第三方的apk包,所以我们需要知道frameWork层是怎么去加载整个app的资源的。
-
我们先看看把打包完成的一个apk解压缩出来会生成以下文件
image.png
- assets:资源文件
- lib:so库,ndk开发的代码库
- META-INF:一系列的签名文件,apk发布就需要签名文件来签名,不然发布不了、手机也安装不了
- 大量的dex文件:java编译生成的java文件(包括R.java文件)。
其中resources.arsc就是我们本文要将的重点,它的储存结构类似于数据库文件,主要用于表示整个app里所有资源的对应关系
![](https://img.haomeiwen.com/i12474664/c9adc126146297e5.png)
例如我们要找一个color资源,Name表示这个xml文件的名字,default表示这个xml文件的所在的位置,还有ID信息是不是很像我们的数据库表信息。
实际上,当我们去加载一个apk包的时候,会有一个类去负责加载resources.arsc文件中的信息,所以我们本文的重点就是分析下,在源码中resources.arsc文件中的信息是怎么实现加载的。
和第一篇文章Android换肤(一)源码分析View的创建流程一样,我们直接从ActivityThread开始。
- 1、ActivityThread#handleBindApplication
了解过Activity创建、启动流程源码的小伙伴肯定知道,在application和activity的所有生命周期调用中,都会先调用instrumentation的相应方法。
例如:callActivityOnCreate,callApplicationOnCreate,newActivity,callActivityOnNewIntent
对于每一个android app来说,它的总入口都是ActivityThread#main. 每一个应用的进程都有一个ActivityThread对象,而每一个ActivityThread对象都有一个Instrumentation mInstrumentation;成员变量。mInstrumentation的初始化ActivityThread#handleBindApplication方法里。
private void handleBindApplication(AppBindData data) {
。。。此处省略很多代码。。。
mInstrumentation = new Instrumentation();
。。。此处省略很多代码。。。
Application app;
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
app = data.info.makeApplication(data.restrictedBackupMode, null);
// Propagate autofill compat state
app.setAutofillOptions(data.autofillOptions);
// Propagate Content Capture options
app.setContentCaptureOptions(data.contentCaptureOptions);
mInitialApplication = app;
// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
}
}
// Do this after providers, since instrumentation tests generally start their
// test thread at this point, and we don't want that racing.
try {
mInstrumentation.onCreate(data.instrumentationArgs);
}
catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
} finally {
// If the app targets < O-MR1, or doesn't change the thread policy
// during startup, clobber the policy to maintain behavior of b/36951662
if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
|| StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
StrictMode.setThreadPolicy(savedPolicy);
}
}
}
我们看到了这里主要是新建了一个mInstrumentation成员对象,然后又通过 app = data.info.makeApplication(data.restrictedBackupMode, null);
创建了一个Application,再调用mInstrumentation的callApplicationOnCreate(app)方法调用了Application的OnCreate方法。
所以我们先进入makeApplication看看Application是怎么创建的,
- 2、LoadedApk#makeApplication
@UnsupportedAppUsage
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
if (instrumentation != null) {
try {
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!instrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}
// Rewrite the R 'constants' for all library apks.
SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
final int N = packageIdentifiers.size();
for (int i = 0; i < N; i++) {
final int id = packageIdentifiers.keyAt(i);
if (id == 0x01 || id == 0x7f) {
continue;
}
rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return app;
}
先通过这句代码 ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);创建一个app的上下文appContext,所以我们再进入createAppContext看看是怎么创建的```
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo,
String opPackageName) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0,
null, opPackageName);
context.setResources(packageInfo.getResources());
return context;
}
看到这句代码context.setResources(packageInfo.getResources());,字面意思就是从包信息中获取资源,并且设置给上下文context。看到这里终于看到了我们本文的重点,所以我们再进去,看看到底是怎么获取资源的呢?进入getResources方法.
- 3、LoadedApk#getResources
@UnsupportedAppUsage
public Resources getResources() {
if (mResources == null) {
final String[] splitPaths;
try {
splitPaths = getSplitPaths(null);
} catch (NameNotFoundException e) {
// This should never fail.
throw new AssertionError("null split not found");
}
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
getClassLoader());
}
return mResources;
}
我们看到通过 mResources = ResourcesManager.getInstance().getResources进行mResources的初始化,所以再进入ResourcesManager#getResources方法
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);
}
}
再进入getOrCreateResources方法
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
if (DEBUG) {
Throwable here = new Throwable();
here.fillInStackTrace();
Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
}
if (activityToken != null) {
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
// Clean up any dead references so they don't pile up.
ArrayUtils.unstableRemoveIf(activityResources.activityResources,
sEmptyReferencePredicate);
// Rebase the key's override config on top of the Activity's base override.
if (key.hasOverrideConfiguration()
&& !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
final Configuration temp = new Configuration(activityResources.overrideConfig);
temp.updateFrom(key.mOverrideConfiguration);
key.mOverrideConfiguration.setTo(temp);
}
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
// We will create the ResourcesImpl object outside of holding this lock.
} 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;
}
}
主要看到这句代码 ResourcesImpl resourcesImpl =createResourcesImpl(key);
看看它的资源是怎么创建的
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);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
再进入 final AssetManager assets = createAssetManager(key);
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
final AssetManager.Builder builder = new AssetManager.Builder();
// 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) {
try {
builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,
false /*overlay*/));
} catch (IOException e) {
Log.e(TAG, "failed to add asset path " + key.mResDir);
return null;
}
}
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
try {
builder.addApkAssets(loadApkAssets(splitResDir, false /*sharedLib*/,
false /*overlay*/));
} catch (IOException e) {
Log.e(TAG, "failed to add split asset path " + splitResDir);
return null;
}
}
}
if (key.mOverlayDirs != null) {
for (final String idmapPath : key.mOverlayDirs) {
try {
builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
true /*overlay*/));
} catch (IOException e) {
Log.w(TAG, "failed to add overlay path " + idmapPath);
// continue.
}
}
}
if (key.mLibDirs != null) {
for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) {
// Avoid opening files we know do not have resources,
// like code-only .jar files.
try {
builder.addApkAssets(loadApkAssets(libDir, true /*sharedLib*/,
false /*overlay*/));
} catch (IOException e) {
Log.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
// continue.
}
}
}
}
return builder.build();
}
看到 builder.addApkAssets(loadApkAssets(key.mResDir, false /sharedLib/,
false /overlay/));这句代码,
private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
throws IOException {
final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
ApkAssets apkAssets = null;
if (mLoadedApkAssets != null) {
apkAssets = mLoadedApkAssets.get(newKey);
if (apkAssets != null) {
return apkAssets;
}
}
// Optimistically check if this ApkAssets exists somewhere else.
final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
if (apkAssetsRef != null) {
apkAssets = apkAssetsRef.get();
if (apkAssets != null) {
if (mLoadedApkAssets != null) {
mLoadedApkAssets.put(newKey, apkAssets);
}
return apkAssets;
} else {
// Clean up the reference.
mCachedApkAssets.remove(newKey);
}
}
// We must load this from disk.
if (overlay) {
apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path),
false /*system*/);
} else {
apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
}
if (mLoadedApkAssets != null) {
mLoadedApkAssets.put(newKey, apkAssets);
}
mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
return apkAssets;
}
mLoadedApkAssets里储存的就是我们文章开头讲的资源文件的键-值关系,源码里使用LruCache的方式保存的,具体还想看细节的小伙伴可以再深入ApkAssets类来看看资源文件是怎么加载进来,我这里透露一点,其实就是通过底层native方法读文件的流来实现的。我们这篇文章主要找到apk资源加载的点,以便我们实现换肤的流程,所以就适可而止了。源码看到这里我们应该有思路了,要实现换皮肤,就是要把我们插件apk的资源插入到我们宿主apk的mLoadedApkAssets里面就可以了。
用下面的关系图总结一下第三步流程中涉及的主要类的关系图
![](https://img.haomeiwen.com/i12474664/12bc052a93ce91c2.png)
其实,我们平时加载一个color
![](https://img.haomeiwen.com/i12474664/26863183698543c5.png)
![](https://img.haomeiwen.com/i12474664/5015aafadd7e1032.png)
![](https://img.haomeiwen.com/i12474664/3b140b13e1414945.png)
加载一个Drawable
![](https://img.haomeiwen.com/i12474664/4454cbdb234b2b43.png)
![](https://img.haomeiwen.com/i12474664/aa079e84e55fe39e.png)
![](https://img.haomeiwen.com/i12474664/7c7e7414ea6dd428.png)
实际上最终加载资源都是在类AssetManager里,
我们查看AssetManager里的一些主要方法
![](https://img.haomeiwen.com/i12474664/ffdbb448c113eb17.png)
有根据资源名字获取资源id的,根据资源id换取资源包名的。这些就是我们换肤中要用到的一些主要方法。
换肤思路:例如在我们的主app中有一个名字为bottom_item的控件,我们可以先根据名字获取这个控件的id,在插件app里有一个和主app相同名字的控件,但是他的颜色是不一样的,我们就可以根据名字获取插件里这个控件的id,把插件里这个控件的id赋值给主app,就实现了换肤的效果。
到此,换肤的基础知识讲完了,下篇文章我们开始进入实战。
网友评论