CyanogenMode主题模块解析(中)
2016-03-07 18:19
323 查看
我们在CyanogenMode主题解析模块(上) 已经知道:一个主题包从安装到被解析后,其中icons目录以overlays目录
里面的皮肤包都会通过aapt打包成一个与之对应的resource.apk,而且如果发现icons或者overlays下面皮肤包里面没有
AndroidManifest文件,则会先创建一个临时的AndroidManifest,然后再通过aapt打包这些资源。这样Android就可以快
速定位到这些资源。另外,icons被解析的时候会生成一个hash文件用来检查生成的resource.apk的合法性;而overlays
目录中的应用皮肤包在被解析的时候还会生成一个idmap文件-一个资源id映射文件(图1)。在完成了主题包的解析后,我们就
可以使用这个主题了,我们可以通过调用ThemeService:: requestThemeChange完成主题的切换。
图1
在调用requestThemeChange后,ThemeService会调用updateConfiguration来更新系统配置,如果一个Activity
没有监听主题的变化,那么这个Activity会被重启。而Activity在重新被创建的时候,它会关联一个ContextImpl对象,每
个ContextImpl都会包含一个Resources对象,我们可以通过它来管理被解析过的资源。
要了解Activity是如何通过Resouces找到指定的主题包资源的,我们至少需要了解两点:
[b]Resources是如何被创建的
[/b]
[b]Resources是如何查找资源的[/b]
第一点,Resouces是什么
1. 路径:framework/base/core/java/android/app/ContextImpl.java
[b]直接调用ContextImpl的构造函数。[/b]
此处的packageInfo是一个LoadApk对象。ContextImpl中Resource对象是通过LoadApk::getResources得到的。
2.路径:framework/base/core/java/android/app/LoadedApk.java
该方法直接调用ActivityThread::getTopLevelResources
3.路径:framework/base/core/java/android/app/ActivityThread.java
该方法直接调用ResourceManager::getTopLevelResources
4.路径:framework/base/core/java/android/app/ResourceManager.java
这个时候开始创建Resources对象,在创建Resources对象之前,我们创建了一个AssetManager对象,并且它是
Resources的一个构造参数。那么这个AssetManager到底充当了什么角色呢?纵观Resources的代码,所有查找资源的请
求最后都会交给AssetManager来处理。当然主题包资源的加载最后的重担也是交给了AssetManager。首先通过getTheme-
Config获取当前的主题配置。它的实现如下:
然后会判断所获取的ThemeConfig是否为空,如果为空,则通过ThemeConfig::getBootTheme来获取。它的实现如下:
获取ThemeConfig后,然后通过3个方法把主题信息attach到assets对象中,该asset对象最后会保存在需要返回
的Resource对象中。接下来我们看下ThemeConfig的信息是如何attach到assets中的,这里以attachThemeAssets为
例。
5.路径:framework/base/core/java/android/app/ResourceManager.java
这里分两种情况,第一种情况当前apk不是一个主题包,并且主题包中包含当前apk的皮肤时,那么就会调用assets.
addOverlayPath把当前主题包相关信息加载进去,主题信息包括上面提到过的主题包注册时产生的资源文件以及idmap文件。
第二种情况,当前apk不是一个主题包,并且当前apk的包名不是"android",以及当前主题包含"android"的皮肤时,assets.
addOverlayPath会把该主题包中的"android"皮肤包信息加载进去。这里提到的主题包信息包括如下几点:
[b]themePath,主题包的资源根目录路径
[/b]
[b]resApkPath,对应主题资源解析后生成的resources.apk路径
[/b]
[b]targetPackagePath,目标apk路径[/b]
[b]prefixPath,主题资源res目录的父目录
[/b]
[b]idmapPath,对应主题资源解析后生成的idmap路径
[/b]
注:因为resource.apk中的资源索引文件记录的是相对路径,比如主题包有个launcher的主题资源,主题资源有个图片
资源foo.png,那么它的路径应该是assets/com.android.launcher/res/drawable/foo.png。但是该资源被解析后生成的资源
索引文件中保存的是这样的: res/drawable/foo.png,所以我们需要给它加上一个前缀
我们看下这些信息是如何被整合进assets的。
6.路径:framework/base/core/java/android/content/res/AssetManager.java
调用native方法addOverlayPathNative,它对应的native层的方法为android_content_AssetManager_addOverlayPath。
7.路径:framework/base/core/jni/android_util_AssetManager.cpp
调用native层的AssetManager::addOverlayPath
8.路径:framework/base/libs/androidfw/AssetManager.cpp
首先会判断是否idmap文件的路径是否已经添加过了,如何已经添加过了,则会把它在mAssetPaths中的索引存储在
参数cookie中,如果没有添加,则会到打开并且解析指定路径的idmap文件,最后会得到targetPath以及overlayPath,然
后会检查传进来的overlayPackagePath是不是与overlayPath相等。如果相等,还会继续检查targetPath的合法性。最后
打包成一个asset_path添加到mAssetPaths当中。如何当前AssetManager中ResTable对象mResource不为空,则会
将该asset_path通过appendPathToResTable添加到其中。可见最后的信息都保存在了asset_path中,包含的信息有:
overlayPath,主题包base.apk的路径
type,主题包的类型
idmapPath,对应的主题资源解析后生成的idmap路径
resApkPath,对应的主题资源解析后生成的resources.apk路径
prefixPath
总结,我们大致了解到了一个主题包中对应的某个apk资源包的加载过程,加载完成后,我会得到一系列重要的文件
的路径,方便我们快速的找到对应的资源。至此,我完成了Resouces创建过程的分析,其中主要讲了主题包相关信息是如何
被打包进Resouces成员AssetManager对象中的。接下来我们看看第二点。
第二点:Activity是如何通过Resouces对象查找资源的?
这里我们以一张图片资源作为切入口,如果我们要加载一张名为foo.png的图片资源,并且它的id为R.drawable.foo,我们通
过res.getDrawable方法获取改图片资源的过程如下:
1.framework/base/core/java/android/content/res/Resouces.java
这里我们theme传入null,接下来会调用三个参数的getDrawable
2.framework/base/core/java/android/content/res/Resouces.java
首先会通过getValue来获取资源id对应的资源值,然后再调用loadDrawable来获取目标图片。
3.framework/base/core/java/android/content/res/Resouces.java
getValue会调用mAssets. getResourceValue方法,如果没有找到资源,则会抛出NotFoundException
4.framework/base/core/java/android/content/res/AssetManager.java
getResourceValue通过调用另外一个成员函数loadResourceValue来加载参数id所描述的资源。如果加载成功,那
么结果就会保存在参数outValue所描述的一个TypedValue对象中,并且AssetManager类的成员函数loadResourceValue
的返回值block大于等于0。这里我们要获取的类型是DRAWABLE,所以outValue.type != TypedValue.TYPE_STRING,
随后直接返回。我们看下loadResourceValue的实现:
5.framework/base/core/jni/android_util_AssetManager.cpp
我们获取到了native层的AssetManager对象后,会调用它的getResources方法来获取ResTable对象,然后通过调
用ResTable ::getResouce来获取对应id的资源值。随后会通过调用ResTable:: resolveReference来解析资源值。
6.framework/base/libs/androidfw/AssetManager.cpp
我们假设一开始mResources为空,则会新建一个实例赋值给mResource。随后会调用appendPathToResTable来
将前面加载到mAssetPaths中的asset_path全部遍历添加到mResouce当中。
7.framework/base/libs/androidfw/AssetManager.cpp
首先会解析idmap文件得到一个Asset* idmap对象,然后遍历到的会是系统资源资源,此时entryIdx为0,并且
sharedRes为null,在通过openNonAssetInPathLocked解析完framework-res.apk里面的资源索引表后,结果会保存在
sharedRes中,最后通过调用mResource::add方法添加到mResource中。而当entryIdx不为0时,且已经遍历到了我们
添加的主题包信息时,随后会解析对应的资源索引文件,最后也会通过mResource::add方法添加进去。
8.framework/base/libs/androidfw/AssetManager.cpp
add一个Asset对象时,会对该Asset对象所描述的resources.arsc文件的内容进行解析,结果就是得到一个
系列的Package信息。每一个Package又包含了一个资源类型字符串资源池和一个资源项名称字符串资源池,以及一系列的
资源类型规范数据块和一系列的资源项数据块。每一个资源包里面的所有Pacakge形成一个PackageGroup,保存在变量rt
所指向的一个ResTable对象的成员变量mPackageGroups中。如果这个包是一个主题包,那么就在解析该主题包的时候,
首先会找到该主题包对应的PackageGroup,并且将主题包信息存储在对象Type的成员idmapEntries中,最后被添加到
targetGroup的TypeList中。
至此mResources所指向的一个ResTable对象就包含了当前应用程序所使用的资源包的所有信息,该ResTable对象
最后就会返回给调用者来使用。我们回到mResource:: getResource。看看主题资源是如何被查找到的?
9.framework/base/libs/androidfw/ResouceTypes.cpp
performMappinp为true,前面mResource的idmapEntries添加过主题包数据,所以idmapEntries.hasEntries()
为true,此时参数id如果能够被映射主题包的资源id,且当前遍历的是typelist中最后一个Type对象,那么就直接返回该
主题包的Entry,如果主题包的Type不是最后一个添加到targetGroup当中,那么就会继续寻找最符合此id的资源。
当我们的id通过idmap重定向到了主题包中,后面的过程就是通过返回的Entry来完成资源的获取了。至于如何通过,
如何通过Entry包装的信息去找到最匹配的资源,这里就不详述了。
总结,Activity启动会通过资源管理器来获取最合适的资源文件。我们的目标就是把主题包资源交由系统的资源管理器来
管理。主题包一旦托管给了资源管理器,那Activity资源加载的问题也就迎刃而解了。
这里我们了解到,如果新的主题资源被解析了后,我只需要重新启动Activity就行了。那么Activity是何时被重启的?
Launcher的图标又是如何加载的?主题包的其他模块(如:开机动画,音频等)又是如何加载的?这些我们留到下一篇文章再介
绍。
里面的皮肤包都会通过aapt打包成一个与之对应的resource.apk,而且如果发现icons或者overlays下面皮肤包里面没有
AndroidManifest文件,则会先创建一个临时的AndroidManifest,然后再通过aapt打包这些资源。这样Android就可以快
速定位到这些资源。另外,icons被解析的时候会生成一个hash文件用来检查生成的resource.apk的合法性;而overlays
目录中的应用皮肤包在被解析的时候还会生成一个idmap文件-一个资源id映射文件(图1)。在完成了主题包的解析后,我们就
可以使用这个主题了,我们可以通过调用ThemeService:: requestThemeChange完成主题的切换。
图1
在调用requestThemeChange后,ThemeService会调用updateConfiguration来更新系统配置,如果一个Activity
没有监听主题的变化,那么这个Activity会被重启。而Activity在重新被创建的时候,它会关联一个ContextImpl对象,每
个ContextImpl都会包含一个Resources对象,我们可以通过它来管理被解析过的资源。
要了解Activity是如何通过Resouces找到指定的主题包资源的,我们至少需要了解两点:
[b]Resources是如何被创建的
[/b]
[b]Resources是如何查找资源的[/b]
第一点,Resouces是什么
1. 路径:framework/base/core/java/android/app/ContextImpl.java
class ContextImpl extends Context { ... static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); return new ContextImpl(null, mainThread, packageInfo, null, null, false, null, null, null); } ... }
[b]直接调用ContextImpl的构造函数。[/b]
class ContextImpl extends Context{ ... private ContextImpl(ContextImpl container, ActivityThread mainThread, LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted, Display display, Configuration overrideConfiguration, String themePackageName) { mOuterContext = this; ... Resources resources = packageInfo.getResources(mainThread); ... mResources = resources; ... } ... }
此处的packageInfo是一个LoadApk对象。ContextImpl中Resource对象是通过LoadApk::getResources得到的。
2.路径:framework/base/core/java/android/app/LoadedApk.java
public final class LoadedApk { ... public Resources getResources(ActivityThread mainThread) { if (mResources == null) { mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this, mainThread.getSystemContext(), mPackageName); } return mResources; } ... }
该方法直接调用ActivityThread::getTopLevelResources
3.路径:framework/base/core/java/android/app/ActivityThread.java
public final class ActivityThread { ... Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, Configuration overrideConfiguration, LoadedApk pkgInfo, Context context, String pkgName) { return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs, displayId, pkgName, overrideConfiguration, pkgInfo.getCompatibilityInfo(), null, context, pkgInfo.getApplicationInfo().isThemeable); } ... }
该方法直接调用ResourceManager::getTopLevelResources
4.路径:framework/base/core/java/android/app/ResourceManager.java
public class ResourcesManager { ... public Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, String[] libDirs, int displayId, String packageName, Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token, Context context, boolean isThemeable) { final float scale = compatInfo.applicationScale; ThemeConfig themeConfig = getThemeConfig(); ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, scale, isThemeable, themeConfig, token); Resources r; synchronized (this) { // Resources is app scale dependent. if (false) { Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale); } WeakReference<Resources> wr = mActiveResources.get(key); r = wr != null ? wr.get() : null; //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); if (r != null && r.getAssets().isUpToDate()) { if (false) { Slog.w(TAG, "Returning cached resources " + r + " " + resDir + ": appScale=" + r.getCompatibilityInfo().applicationScale); } return r; } } //if (r != null) { // Slog.w(TAG, "Throwing away out-of-date resources!!!! " // + r + " " + resDir); //} AssetManager assets = new AssetManager(); assets.setAppName(packageName); assets.setThemeSupport(isThemeable); // 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 (resDir != null) { if (assets.addAssetPath(resDir) == 0) { return null; } } if (splitResDirs != null) { for (String splitResDir : splitResDirs) { if (assets.addAssetPath(splitResDir) == 0) { return null; } } } if (overlayDirs != null) { for (String idmapPath : overlayDirs) { assets.addOverlayPath(idmapPath, null, null, null, null); } } if (libDirs != null) { for (String libDir : l 13f37 ibDirs) { if (assets.addAssetPath(libDir) == 0) { Slog.w(TAG, "Asset path '" + libDir + "' does not exist or contains no resources."); } } } //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics dm = getDisplayMetricsLocked(displayId); Configuration config; boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); final boolean hasOverrideConfig = key.hasOverrideConfiguration(); if (!isDefaultDisplay || hasOverrideConfig) { config = new Configuration(getConfiguration()); if (!isDefaultDisplay) { applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config); } if (hasOverrideConfig) { config.updateFrom(key.mOverrideConfiguration); } } else { config = getConfiguration(); } boolean iconsAttached = false; /* Attach theme information to the resulting AssetManager when appropriate. */ if (config != null && !context.getPackageManager().isSafeMode()) { if (themeConfig == null) { try { themeConfig = ThemeConfig.getBootTheme(context.getContentResolver()); } catch (Exception e) { Slog.d(TAG, "ThemeConfig.getBootTheme failed, falling back to system theme", e); themeConfig = ThemeConfig.getSystemTheme(); } } if (isThemeable) { if (themeConfig != null) { attachThemeAssets(assets, themeConfig); attachCommonAssets(assets, themeConfig); iconsAttached = attachIconAssets(assets, themeConfig); } } else if (themeConfig != null && !ThemeConfig.SYSTEM_DEFAULT.equals(themeConfig.getFontPkgName())) { // use system fonts if not themeable and a theme font is currently in use Typeface.recreateDefaults(true); } } r = new Resources(assets, dm, config, compatInfo, token); if (iconsAttached) setActivityIcons(r); if (false) { Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale); } synchronized (this) { WeakReference<Resources> wr = mActiveResources.get(key); Resources existing = wr != null ? wr.get() : null; if (existing != null && existing.getAssets().isUpToDate()) { // Someone else already created the resources while we were // unlocked; go ahead and use theirs. r.getAssets().close(); return existing; } // XXX need to remove entries when weak references go away mActiveResources.put(key, new WeakReference<Resources>(r)); return r; } } ... }
这个时候开始创建Resources对象,在创建Resources对象之前,我们创建了一个AssetManager对象,并且它是
Resources的一个构造参数。那么这个AssetManager到底充当了什么角色呢?纵观Resources的代码,所有查找资源的请
求最后都会交给AssetManager来处理。当然主题包资源的加载最后的重担也是交给了AssetManager。首先通过getTheme-
Config获取当前的主题配置。它的实现如下:
private ThemeConfig getThemeConfig() { final Configuration config = getConfiguration(); return config != null ? config.themeConfig : null; } public Configuration getConfiguration() { return mResConfiguration; }
然后会判断所获取的ThemeConfig是否为空,如果为空,则通过ThemeConfig::getBootTheme来获取。它的实现如下:
public static ThemeConfig getBootTheme(ContentResolver resolver) { return getBootThemeForUser(resolver, UserHandle.getCallingUserId()); } public static ThemeConfig getBootThemeForUser(ContentResolver resolver, int userHandle) { ThemeConfig bootTheme = mSystemConfig; try { String json = Settings.Secure.getStringForUser(resolver, Configuration.THEME_PKG_CONFIGURATION_PERSISTENCE_PROPERTY, userHandle); bootTheme = ThemeConfig.fromJson(json); // Handle upgrade Case: Previously the theme configuration was in separate fields if (bootTheme == null) { String overlayPkgName = Settings.Secure.getStringForUser(resolver, Configuration.THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY, userHandle); String iconPackPkgName = Settings.Secure.getStringForUser(resolver, Configuration.THEME_ICONPACK_PACKAGE_NAME_PERSISTENCE_PROPERTY, userHandle); String fontPkgName = Settings.Secure.getStringForUser(resolver, Configuration.THEME_FONT_PACKAGE_NAME_PERSISTENCE_PROPERTY, userHandle); Builder builder = new Builder(); builder.defaultOverlay(overlayPkgName); builder.defaultIcon(iconPackPkgName); builder.defaultFont(fontPkgName); bootTheme = builder.build(); } } catch (SecurityException e) { Log.w(TAG, "Could not get boot theme", e); } return bootTheme; }
获取ThemeConfig后,然后通过3个方法把主题信息attach到assets对象中,该asset对象最后会保存在需要返回
的Resource对象中。接下来我们看下ThemeConfig的信息是如何attach到assets中的,这里以attachThemeAssets为
例。
5.路径:framework/base/core/java/android/app/ResourceManager.java
private boolean attachThemeAssets(AssetManager assets, ThemeConfig theme) { PackageInfo piTheme = null; PackageInfo piTarget = null; PackageInfo piAndroid = null; // Some apps run in process of another app (eg keyguard/systemUI) so we must get the // package name from the res tables. The 0th base package name will be the android group. // The 1st base package name will be the app group if one is attached. Check if it is there // first or else the system will crash! String basePackageName = null; String resourcePackageName = null; int count = assets.getBasePackageCount(); if (count > NUM_DEFAULT_ASSETS) { basePackageName = assets.getBasePackageName(NUM_DEFAULT_ASSETS); resourcePackageName = assets.getBaseResourcePackageName(NUM_DEFAULT_ASSETS); } else if (count == NUM_DEFAULT_ASSETS) { basePackageName = assets.getBasePackageName(0); } else { return false; } try { piTheme = getPackageManager().getPackageInfo( theme.getOverlayPkgNameForApp(basePackageName), 0, UserHandle.getCallingUserId()); piTarget = getPackageManager().getPackageInfo( basePackageName, 0, UserHandle.getCallingUserId()); // Handle special case where a system app (ex trebuchet) may have had its pkg name // renamed during an upgrade. basePackageName would be the manifest value which will // fail on getPackageInfo(). resource pkg is assumed to have the original name if (piTarget == null && resourcePackageName != null) { piTarget = getPackageManager().getPackageInfo(resourcePackageName, 0, UserHandle.getCallingUserId()); } piAndroid = getPackageManager().getPackageInfo("android", 0, UserHandle.getCallingUserId()); } catch (RemoteException e) { } if (piTheme == null || piTheme.applicationInfo == null || piTarget == null || piTarget.applicationInfo == null || piAndroid == null || piAndroid.applicationInfo == null || piTheme.mOverlayTargets == null) { return false; } String themePackageName = piTheme.packageName; String themePath = piTheme.applicationInfo.publicSourceDir; if (!piTarget.isThemeApk && piTheme.mOverlayTargets.contains(basePackageName)) { String targetPackagePath = piTarget.applicationInfo.sourceDir; String prefixPath = ThemeUtils.getOverlayPathToTarget(basePackageName); String resCachePath = ThemeUtils.getTargetCacheDir(piTarget.packageName, piTheme); String resApkPath = resCachePath + "/resources.apk"; String idmapPath = ThemeUtils.getIdmapPath(piTarget.packageName, piTheme.packageName); int cookie = assets.addOverlayPath(idmapPath, themePath, resApkPath, targetPackagePath, prefixPath); if (cookie != 0) { assets.setThemePackageName(themePackageName); assets.addThemeCookie(cookie); } } if (!piTarget.isThemeApk && !"android".equals(basePackageName) && piTheme.mOverlayTargets.contains("android")) { String resCachePath= ThemeUtils.getTargetCacheDir(piAndroid.packageName, piTheme); String prefixPath = ThemeUtils.getOverlayPathToTarget(piAndroid.packageName); String targetPackagePath = piAndroid.applicationInfo.publicSourceDir; String resApkPath = resCachePath + "/resources.apk"; String idmapPath = ThemeUtils.getIdmapPath("android", piTheme.packageName); int cookie = assets.addOverlayPath(idmapPath, themePath, resApkPath, targetPackagePath, prefixPath); if (cookie != 0) { assets.setThemePackageName(themePackageName); assets.addThemeCookie(cookie); } } return true; }
这里分两种情况,第一种情况当前apk不是一个主题包,并且主题包中包含当前apk的皮肤时,那么就会调用assets.
addOverlayPath把当前主题包相关信息加载进去,主题信息包括上面提到过的主题包注册时产生的资源文件以及idmap文件。
第二种情况,当前apk不是一个主题包,并且当前apk的包名不是"android",以及当前主题包含"android"的皮肤时,assets.
addOverlayPath会把该主题包中的"android"皮肤包信息加载进去。这里提到的主题包信息包括如下几点:
[b]themePath,主题包的资源根目录路径
[/b]
[b]resApkPath,对应主题资源解析后生成的resources.apk路径
[/b]
[b]targetPackagePath,目标apk路径[/b]
[b]prefixPath,主题资源res目录的父目录
[/b]
[b]idmapPath,对应主题资源解析后生成的idmap路径
[/b]
注:因为resource.apk中的资源索引文件记录的是相对路径,比如主题包有个launcher的主题资源,主题资源有个图片
资源foo.png,那么它的路径应该是assets/com.android.launcher/res/drawable/foo.png。但是该资源被解析后生成的资源
索引文件中保存的是这样的: res/drawable/foo.png,所以我们需要给它加上一个前缀
我们看下这些信息是如何被整合进assets的。
6.路径:framework/base/core/java/android/content/res/AssetManager.java
public final class AssetManager implements AutoCloseable { ... public final int addOverlayPath(String idmapPath, String themeApkPath, String resApkPath, String targetPkgPath, String prefixPath) { synchronized (this) { return addOverlayPathNative(idmapPath, themeApkPath, resApkPath, targetPkgPath, prefixPath); } } ... }
调用native方法addOverlayPathNative,它对应的native层的方法为android_content_AssetManager_addOverlayPath。
7.路径:framework/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz, jstring idmapPath, jstring packagePath, jstring resApkPath, jstring targetPkgPath, jstring prefixPath) { ... AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return 0; } int32_t cookie; bool res = am->addOverlayPath( String8(idmapPath8.c_str()), String8(packagePath8.c_str()), &cookie, String8(resApkPath8.c_str()), String8(targetPkgPath8.c_str()), String8(prefixPath8.c_str())); return (res) ? (jint)cookie : 0; }
调用native层的AssetManager::addOverlayPath
8.路径:framework/base/libs/androidfw/AssetManager.cpp
bool AssetManager::addOverlayPath(const String8& idmapPath, const String8& overlayPackagePath, int32_t* cookie, const String8& resApkPath, const String8& targetPkgPath, const String8& prefixPath) { AutoMutex _l(mLock); ALOGV("overlayApkPath: %s, idmap Path: %s, resApkPath %s, targetPkgPath: %s", overlayPackagePath.string(), idmapPath.string(), resApkPath.string(), targetPkgPath.string()); for (size_t i = 0; i < mAssetPaths.size(); ++i) { if (mAssetPaths[i].idmap == idmapPath) { *cookie = static_cast<int32_t>(i + 1); return true; } } Asset* idmap = NULL; if ((idmap = openAssetFromFileLocked(idmapPath, Asset::ACCESS_BUFFER)) == NULL) { ALOGW("failed to open idmap file %s\n", idmapPath.string()); return false; } String8 targetPath; String8 overlayPath; if (!ResTable::getIdmapInfo(idmap->getBuffer(false), idmap->getLength(), NULL, NULL, NULL, &targetPath, &overlayPath)) { ALOGW("failed to read idmap file %s\n", idmapPath.string()); delete idmap; return false; } delete idmap; if (overlayPath != overlayPackagePath) { ALOGW("idmap file %s inconcistent: expected path %s does not match actual path %s\n", idmapPath.string(), overlayPackagePath.string(), overlayPath.string()); return false; } if (access(targetPath.string(), R_OK) != 0) { ALOGW("failed to access file %s: %s\n", targetPath.string(), strerror(errno)); return false; } if (access(idmapPath.string(), R_OK) != 0) { ALOGW("failed to access file %s: %s\n", idmapPath.string(), strerror(errno)); return false; } if (access(overlayPath.string(), R_OK) != 0) { ALOGW("failed to access file %s: %s\n", overlayPath.string(), strerror(errno)); return false; } asset_path oap; oap.path = overlayPath; oap.type = ::getFileType(overlayPath.string()); oap.idmap = idmapPath; oap.resApkPath = resApkPath; #if 0 ALOGD("Overlay added: targetPath=%s overlayPath=%s idmapPath=%s\n", targetPath.string(), overlayPath.string(), idmapPath.string()); #endif oap.prefixPath = prefixPath; //ex: assets/com.foo.bar mAssetPaths.add(oap); *cookie = static_cast<int32_t>(mAssetPaths.size()); if (mResources != NULL) { size_t index = mAssetPaths.size() - 1; appendPathToResTable(oap, &index); } return true; }
首先会判断是否idmap文件的路径是否已经添加过了,如何已经添加过了,则会把它在mAssetPaths中的索引存储在
参数cookie中,如果没有添加,则会到打开并且解析指定路径的idmap文件,最后会得到targetPath以及overlayPath,然
后会检查传进来的overlayPackagePath是不是与overlayPath相等。如果相等,还会继续检查targetPath的合法性。最后
打包成一个asset_path添加到mAssetPaths当中。如何当前AssetManager中ResTable对象mResource不为空,则会
将该asset_path通过appendPathToResTable添加到其中。可见最后的信息都保存在了asset_path中,包含的信息有:
overlayPath,主题包base.apk的路径
type,主题包的类型
idmapPath,对应的主题资源解析后生成的idmap路径
resApkPath,对应的主题资源解析后生成的resources.apk路径
prefixPath
总结,我们大致了解到了一个主题包中对应的某个apk资源包的加载过程,加载完成后,我会得到一系列重要的文件
的路径,方便我们快速的找到对应的资源。至此,我完成了Resouces创建过程的分析,其中主要讲了主题包相关信息是如何
被打包进Resouces成员AssetManager对象中的。接下来我们看看第二点。
第二点:Activity是如何通过Resouces对象查找资源的?
这里我们以一张图片资源作为切入口,如果我们要加载一张名为foo.png的图片资源,并且它的id为R.drawable.foo,我们通
过res.getDrawable方法获取改图片资源的过程如下:
1.framework/base/core/java/android/content/res/Resouces.java
public Drawable getDrawable(int id, @Nullable Theme theme) throws NotFoundException { return getDrawable(id, theme, true); }
这里我们theme传入null,接下来会调用三个参数的getDrawable
2.framework/base/core/java/android/content/res/Resouces.java
public Drawable getDrawable(int id, @Nullable Theme theme, boolean supportComposedIcons) throws NotFoundException { //Check if an icon is themed PackageItemInfo info = mIcons != null ? mIcons.get(id) : null; if (info != null && info.themedIcon != 0) { id = info.themedIcon; } TypedValue value; synchronized (mAccessLock) { value = mTmpValue; if (value == null) { value = new TypedValue(); } else { mTmpValue = null; } getValue(id, value, true, supportComposedIcons); } Drawable res = null; try { res = loadDrawable(value, id, theme); } catch (NotFoundException e) { // The below statement will be true if we were trying to load a composed icon. // Since we received a NotFoundException, try to load the original if this // condition is true, otherwise throw the original exception. if (supportComposedIcons && mComposedIconInfo != null && info != null && info.themedIcon == 0) { Log.e(TAG, "Failed to retrieve composed icon.", e); getValue(id, value, true, false); res = loadDrawable(value, id, theme); } else { throw e; } } synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } return res; }
首先会通过getValue来获取资源id对应的资源值,然后再调用loadDrawable来获取目标图片。
3.framework/base/core/java/android/content/res/Resouces.java
public void getValue(int id, TypedValue outValue, boolean resolveRefs, boolean supportComposedIcons) throws NotFoundException { //Check if an icon was themed PackageItemInfo info = mIcons != null ? mIcons.get(id) : null; if (info != null && info.themedIcon != 0) { id = info.themedIcon; } boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs); if (found) { if (supportComposedIcons && IconPackHelper.shouldComposeIcon(mComposedIconInfo) && info != null && info.themedIcon == 0) { Drawable dr = loadDrawable(outValue, id, null); IconCustomizer.getValue(this, id, outValue, dr); } return; } throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)); }
getValue会调用mAssets. getResourceValue方法,如果没有找到资源,则会抛出NotFoundException
4.framework/base/core/java/android/content/res/AssetManager.java
final boolean getResourceValue(int ident, int density, TypedValue outValue, boolean resolveRefs) { int block = loadResourceValue(ident, (short) density, outValue, resolveRefs); if (block >= 0) { if (outValue.type != TypedValue.TYPE_STRING) { return true; } outValue.string = mStringBlocks[block].get(outValue.data); return true; } return false; }
getResourceValue通过调用另外一个成员函数loadResourceValue来加载参数id所描述的资源。如果加载成功,那
么结果就会保存在参数outValue所描述的一个TypedValue对象中,并且AssetManager类的成员函数loadResourceValue
的返回值block大于等于0。这里我们要获取的类型是DRAWABLE,所以outValue.type != TypedValue.TYPE_STRING,
随后直接返回。我们看下loadResourceValue的实现:
5.framework/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz, jint ident, jshort density, jobject outValue, jboolean resolve) { if (outValue == NULL) { jniThrowNullPointerException(env, "outValue"); return 0; } AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return 0; } const ResTable& res(am->getResources()); Res_value value; ResTable_config config; uint32_t typeSpecFlags; ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config); #if THROW_ON_BAD_ID if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; } #endif uint32_t ref = ident; if (resolve) { block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config); #if THROW_ON_BAD_ID if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; } #endif } if (block >= 0) { return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config); } return static_cast<jint>(block); }
我们获取到了native层的AssetManager对象后,会调用它的getResources方法来获取ResTable对象,然后通过调
用ResTable ::getResouce来获取对应id的资源值。随后会通过调用ResTable:: resolveReference来解析资源值。
6.framework/base/libs/androidfw/AssetManager.cpp
const ResTable& AssetManager::getResources(bool required) const { const ResTable* rt = getResTable(required); return *rt; } const ResTable* AssetManager::getResTable(bool required) const { ResTable* rt = mResources; if (rt) { return rt; } // Iterate through all asset packages, collecting resources from each. AutoMutex _l(mLock); if (mResources != NULL) { return mResources; } if (required) { LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); } if (mCacheMode != CACHE_OFF && !mCacheValid) { const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); } mResources = new ResTable(); updateResourceParamsLocked(); bool onlyEmptyResources = true; const size_t N = mAssetPaths.size(); for (size_t i=0; i<N; i++) { bool empty = appendPathToResTable(mAssetPaths.itemAt(i), &i); onlyEmptyResources = onlyEmptyResources && empty; } if (required && onlyEmptyResources) { ALOGW("Unable to find resources file resources.arsc"); delete mResources; mResources = NULL; } return mResources; }
我们假设一开始mResources为空,则会新建一个实例赋值给mResource。随后会调用appendPathToResTable来
将前面加载到mAssetPaths中的asset_path全部遍历添加到mResouce当中。
7.framework/base/libs/androidfw/AssetManager.cpp
bool AssetManager::appendPathToResTable(const asset_path& ap, size_t* entryIdx) const { Asset* ass = NULL; ResTable* sharedRes = NULL; bool shared = true; bool onlyEmptyResources = true; MY_TRACE_BEGIN(ap.path.string()); Asset* idmap = openIdmapLocked(ap); ALOGV("Looking for resource asset in '%s'\n", ap.path.string()); if (!ap.resApkPath.isEmpty()) { // Avoid using prefix path in this case since the compiled resApk will simply have resources.arsc in it ass = const_cast<AssetManager*>(this)->openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap, false); shared = false; } else if (ap.type != kFileTypeDirectory) { if (*entryIdx == 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 *entryIdx += sharedRes->getTableCount() - 1; } } 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 (*entryIdx == 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, *entryIdx + 1, false, ap.pkgIdOverride); #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, *entryIdx); #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, *entryIdx + 1, !shared, ap.pkgIdOverride); } onlyEmptyResources = false; if (!shared) { delete ass; } } else { ALOGV("Installing empty resources in to table %p\n", mResources); mResources->addEmpty(*entryIdx + 1); } if (idmap != NULL) { delete idmap; } MY_TRACE_END(); return onlyEmptyResources; }
首先会解析idmap文件得到一个Asset* idmap对象,然后遍历到的会是系统资源资源,此时entryIdx为0,并且
sharedRes为null,在通过openNonAssetInPathLocked解析完framework-res.apk里面的资源索引表后,结果会保存在
sharedRes中,最后通过调用mResource::add方法添加到mResource中。而当entryIdx不为0时,且已经遍历到了我们
添加的主题包信息时,随后会解析对应的资源索引文件,最后也会通过mResource::add方法添加进去。
8.framework/base/libs/androidfw/AssetManager.cpp
status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData, const uint32_t pkgIdOverride) { const void* data = asset->getBuffer(true); if (data == NULL) { ALOGW("Unable to get buffer of resource asset file"); return UNKNOWN_ERROR; } size_t idmapSize = 0; const void* idmapData = NULL; if (idmapAsset != NULL) { idmapData = idmapAsset->getBuffer(true); if (idmapData == NULL) { ALOGW("Unable to get buffer of idmap asset file"); return UNKNOWN_ERROR; } idmapSize = static_cast<size_t>(idmapAsset->getLength()); } return addInternal(data, static_cast<size_t>(asset->getLength()), idmapData, idmapSize, cookie, copyData, pkgIdOverride); } status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize, const int32_t cookie, bool copyData, const uint32_t pkgIdOverride) { … if (parsePackage((ResTable_package*)chunk, header, idOverride) != NO_ERROR) { return mError; } … } status_t ResTable::parsePackage(const ResTable_package* const pkg, const Header* const header, const uint32_t pkgIdOverride) { … if (header->resourceIDMap != NULL) { status_t err = parseIdmap(header->resourceIDMap, header->resourceIDMapSize, &targetPackageId, &idmapEntries); if (err != NO_ERROR) { ALOGW("Overlay is broken"); return (mError=err); } } if (targetGroup != NULL) { ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id); if (idmapIndex >= 0) { typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1; TypeList& typeList = targetGroup->types.editItemAt(typeIndex); Type* t = new Type(header, package, newEntryCount); t->idmapEntries = idmapEntries[idmapIndex]; t->typeSpec = typeSpec; t->typeSpecFlags = (const uint32_t*)( ((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize)); typeList.add(t); } } … }
add一个Asset对象时,会对该Asset对象所描述的resources.arsc文件的内容进行解析,结果就是得到一个
系列的Package信息。每一个Package又包含了一个资源类型字符串资源池和一个资源项名称字符串资源池,以及一系列的
资源类型规范数据块和一系列的资源项数据块。每一个资源包里面的所有Pacakge形成一个PackageGroup,保存在变量rt
所指向的一个ResTable对象的成员变量mPackageGroups中。如果这个包是一个主题包,那么就在解析该主题包的时候,
首先会找到该主题包对应的PackageGroup,并且将主题包信息存储在对象Type的成员idmapEntries中,最后被添加到
targetGroup的TypeList中。
至此mResources所指向的一个ResTable对象就包含了当前应用程序所使用的资源包的所有信息,该ResTable对象
最后就会返回给调用者来使用。我们回到mResource:: getResource。看看主题资源是如何被查找到的?
9.framework/base/libs/androidfw/ResouceTypes.cpp
status_t ResTable::getEntry( const PackageGroup* packageGroup, int typeIndex, int entryIndex, const ResTable_config* config, Entry* outEntry, const bool performMapping) const { const TypeList& typeList = packageGroup->types[typeIndex]; if (typeList.isEmpty()) { ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex); return BAD_TYPE; } const ResTable_type* bestType = NULL; uint32_t bestOffset = ResTable_type::NO_ENTRY; const Package* bestPackage = NULL; uint32_t specFlags = 0; uint8_t actualTypeIndex = typeIndex; ResTable_config bestConfig; memset(&bestConfig, 0, sizeof(bestConfig)); bool currentTypeIsOverlay = false; // Iterate over the Types of each package. const size_t typeCount = typeList.size(); for (size_t i = 0; i < typeCount; i++) { const Type* const typeSpec = typeList[i]; int realEntryIndex = entryIndex; int realTypeIndex = typeIndex; currentTypeIsOverlay = false; // Runtime overlay packages provide a mapping of app resource // ID to package resource ID. if (performMapping && typeSpec->idmapEntries.hasEntries()) { uint16_t overlayEntryIndex; if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) { // No such mapping exists continue; } realEntryIndex = overlayEntryIndex; realTypeIndex = typeSpec->idmapEntries.overlayTypeId() - 1; currentTypeIsOverlay = true; } if (static_cast<size_t>(realEntryIndex) >= typeSpec->entryCount) { ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)", Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex), entryIndex, static_cast<int>(typeSpec->entryCount)); // We should normally abort here, but some legacy apps declare // resources in the 'android' package (old bug in AAPT). continue; } // Aggregate all the flags for each package that defines this entry. if (typeSpec->typeSpecFlags != NULL) { specFlags |= dtohl(typeSpec->typeSpecFlags[realEntryIndex]); } else { specFlags = -1; } const size_t numConfigs = typeSpec->configs.size(); for (size_t c = 0; c < numConfigs; c++) { const ResTable_type* const thisType = typeSpec->configs[c]; if (thisType == NULL) { continue; } ResTable_config thisConfig; thisConfig.copyFromDtoH(thisType->config); // Check to make sure this one is valid for the current parameters. if (config != NULL && !thisConfig.match(*config)) { continue; } // Check if there is the desired entry in this type. const uint8_t* const end = reinterpret_cast<const uint8_t*>(thisType) + dtohl(thisType->header.size); const uint32_t* const eindex = reinterpret_cast<const uint32_t*>( reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize)); uint32_t thisOffset = dtohl(eindex[realEntryIndex]); if (thisOffset == ResTable_type::NO_ENTRY) { // There is no entry for this index and configuration. continue; } if (bestType != NULL) { // Check if this one is less specific than the last found. If so, // we will skip it. We check starting with things we most care // about to those we least care about. if (!currentTypeIsOverlay && !thisConfig.isBetterThan(bestConfig, config)) { if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) { continue; } } } bestType = thisType; bestOffset = thisOffset; bestConfig = thisConfig; bestPackage = typeSpec->package; actualTypeIndex = realTypeIndex; // If no config was specified, any type will do, so skip if (config == NULL) { break; } } } if (bestType == NULL) { return BAD_INDEX; } bestOffset += dtohl(bestType->entriesStart); if (bestOffset > (dtohl(bestType->header.size)-sizeof(ResTable_entry))) { ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x", bestOffset, dtohl(bestType->header.size)); return BAD_TYPE; } if ((bestOffset & 0x3) != 0) { ALOGW("ResTable_entry at 0x%x is not on an integer boundary", bestOffset); return BAD_TYPE; } const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>( reinterpret_cast<const uint8_t*>(bestType) + bestOffset); if (dtohs(entry->size) < sizeof(*entry)) { ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size)); return BAD_TYPE; } if (outEntry != NULL) { outEntry->entry = entry; outEntry->config = bestConfig; outEntry->type = bestType; outEntry->specFlags = specFlags; outEntry->package = bestPackage; outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset); outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index)); outEntry->isFromOverlay = currentTypeIsOverlay; } return NO_ERROR; }
performMappinp为true,前面mResource的idmapEntries添加过主题包数据,所以idmapEntries.hasEntries()
为true,此时参数id如果能够被映射主题包的资源id,且当前遍历的是typelist中最后一个Type对象,那么就直接返回该
主题包的Entry,如果主题包的Type不是最后一个添加到targetGroup当中,那么就会继续寻找最符合此id的资源。
当我们的id通过idmap重定向到了主题包中,后面的过程就是通过返回的Entry来完成资源的获取了。至于如何通过,
如何通过Entry包装的信息去找到最匹配的资源,这里就不详述了。
总结,Activity启动会通过资源管理器来获取最合适的资源文件。我们的目标就是把主题包资源交由系统的资源管理器来
管理。主题包一旦托管给了资源管理器,那Activity资源加载的问题也就迎刃而解了。
这里我们了解到,如果新的主题资源被解析了后,我只需要重新启动Activity就行了。那么Activity是何时被重启的?
Launcher的图标又是如何加载的?主题包的其他模块(如:开机动画,音频等)又是如何加载的?这些我们留到下一篇文章再介
绍。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories