概述
其实在应用程序资源的编译和打包之后就生成了一个资源索引表文件resources.arsc,这个应用程序资源会被打包到APK文件中。
Android应用程序在运行过程中,通过一个称为Resource来获取资源,但实际上Resource内部又是通过AssetManager的资源管理器来读取打包在APK文件中的资源文件。那么AssetManager如何与应用关联在一起,它又是如何找到应用的资源?
如上文所说,获取资源的操作实际上是由ContextImpl来完成的,Activity、Service等组件的getResources函数最终会转发给ContextImpl类型的mBase字段。也就是调用ContextImpl的getResources函数,而这个Resource在ContextImpl关联到Activity之前就会初始化Resource对象。
#ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
ComponentName component = r.intent.getComponent();
if (component == null) {
component = r.intent.resolveActivity(
mInitialApplication.getPackageManager());
r.intent.setComponent(component);
}
if (r.activityInfo.targetActivity != null) {
component = new ComponentName(r.activityInfo.packageName,
r.activityInfo.targetActivity);
}
//创建ContextImpl
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
//1.创建Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
//2.创建Appliaciton
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
if (localLOGV) Slog.v(
TAG, r + ": app=" + app
+ ", appName=" + app.getPackageName()
+ ", pkg=" + r.packageInfo.getPackageName()
+ ", comp=" + r.intent.getComponent().toShortString()
+ ", dir=" + r.packageInfo.getAppDir());
if (activity != null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (r.overrideConfig != null) {
config.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
Window window = null;
if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
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);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
checkAndBlockForNetworkAccess();
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
r.stopped = true;
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
if (!r.activity.mFinished) {
if (r.isPersistable()) {
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
if (!r.activity.mFinished) {
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnPostCreate(activity, r.state,
r.persistentState);
} else {
mInstrumentation.callActivityOnPostCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPostCreate()");
}
}
}
r.paused = true;
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
}
}
return activity;
}
#ActivityThread
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
final int displayId;
try {
displayId = ActivityManager.getService().getActivityDisplayId(r.token);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
//创建ContextImpl对象
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
// For debugging purposes, if the activity's package name contains the value of
// the "debug.use-second-display" system property as a substring, then show
// its content on a secondary display if there is one.
String pkgName = SystemProperties.get("debug.second-display.pkg");
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != Display.DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getResources());
appContext = (ContextImpl) appContext.createDisplayContext(display);
break;
}
}
}
return appContext;
}
在上述performLaunchActivity函数中,首先创建了Activity与Application,然后通过调用createBaseContextForActivity 创建了一个ContextImpl对象。而在createBaseContextForActivity 函数中又调用了ContextImpl类的ContextImpl.createActivityContext 静态函数,源码如下:
#ContextImpl
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
String[] splitDirs = packageInfo.getSplitResDirs();
ClassLoader classLoader = packageInfo.getClassLoader();
if (packageInfo.getApplicationInfo().requestsIsolatedSplitLoading()) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "SplitDependencies");
try {
classLoader = packageInfo.getSplitClassLoader(activityInfo.splitName);
splitDirs = packageInfo.getSplitPaths(activityInfo.splitName);
} catch (NameNotFoundException e) {
// Nothing above us can handle a NameNotFoundException, better crash.
throw new RuntimeException(e);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader);
// Clamp display ID to DEFAULT_DISPLAY if it is INVALID_DISPLAY.
displayId = (displayId != Display.INVALID_DISPLAY) ? displayId : Display.DEFAULT_DISPLAY;
final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY)
? packageInfo.getCompatibilityInfo()
: CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
// Create the base resources for which all configuration contexts for this Activity
// will be rebased upon.
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(),
splitDirs,
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
packageInfo.getPackageName(),
overrideConfiguration,
compatInfo,
classLoader,
context,//0105 temp
packageInfo.getApplicationInfo().isThemeable));//porting theme
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}
#ContextImpl
private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
@NonNull LoadedApk packageInfo, @Nullable String splitName,
@Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
@Nullable ClassLoader classLoader) {
mOuterContext = this;
// If creator didn't specify which storage to use, use the default
// location for application.
if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE
| Context.CONTEXT_DEVICE_PROTECTED_STORAGE)) == 0) {
final File dataDir = packageInfo.getDataDirFile();
if (Objects.equals(dataDir, packageInfo.getCredentialProtectedDataDirFile())) {
flags |= Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
} else if (Objects.equals(dataDir, packageInfo.getDeviceProtectedDataDirFile())) {
flags |= Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
}
}
mMainThread = mainThread;
mActivityToken = activityToken;
mFlags = flags;
if (user == null) {
user = Process.myUserHandle();
}
mUser = user;
//获取包信息
mPackageInfo = packageInfo;
mSplitName = splitName;
mClassLoader = classLoader;
//获取资源管理器
mResourcesManager = ResourcesManager.getInstance();
if (container != null) {
mBasePackageName = container.mBasePackageName;
mOpPackageName = container.mOpPackageName;
setResources(container.mResources);
mDisplay = container.mDisplay;
} else {
mBasePackageName = packageInfo.mPackageName;
ApplicationInfo ainfo = packageInfo.getApplicationInfo();
if (ainfo.uid == Process.SYSTEM_UID && ainfo.uid != Process.myUid()) {
// Special case: system components allow themselves to be loaded in to other
// processes. For purposes of app ops, we must then consider the context as
// belonging to the package of this process, not the system itself, otherwise
// the package+uid verifications in app ops will fail.
mOpPackageName = ActivityThread.currentPackageName();
} else {
mOpPackageName = mBasePackageName;
}
}
mContentResolver = new ApplicationContentResolver(this, mainThread, user);
}
createActivityContext函数中最终调用了ContextImpl的构造函数,在该构造函数中会初始化该进程的各个字段,例如资源、包信息、屏幕适配等。这里只关心与资源相关的代码。在通过mPackageInfo得到对应的资源之后,会调用ResoucesManager的getTopLevelResources来根据设置配置等相关信息获取对应的资源,也就是资源的适配。getTopLevelResources
/**
* Creates base resources for an Activity. Calls to
* {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
* CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
* configurations merged with the one specified here.
*(为活动创建基础资源。 使用相同的activityToken调用{@link #getResources(IBinder,String,String [],String [],String [],int,Configuration,CompatibilityInfo,ClassLoader)}将使其覆盖配置与此处指定的配置合并。)
* @param activityToken Represents an Activity. //表示活动
* @param resDir The base resource path. Can be null (only framework resources will be loaded).(基本资源路径。 可以为null(仅加载ramework resources资源)。)
* @param splitResDirs An array of split resource paths. Can be null. 分割资源路径的数组。 可以为null。
* @param overlayDirs An array of overlay paths. Can be null. (一系列叠加路径。 可以为null。)
* @param libDirs An array of resource library paths. Can be null.(一组资源库路径。 可以为null。)
* @param displayId The ID of the display for which to create the resources.(要为其创建资源的显示的ID。)
* @param overrideConfig The configuration to apply on top of the base configuration. Can be
* null. This provides the base override for this Activity.(要在基本配置之上应用的配置。 可以为null。 这为此Activity提供了基本覆盖。)
* @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
* {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.(要使用的兼容性设置。 不能为空。 默认使用的是{@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO})
* @param classLoader The class loader to use when inflating Resources. If null, the
* {@link ClassLoader#getSystemClassLoader()} is used.(在加载资源时使用的类加载器。 如果为null,则使用{@link ClassLoader#getSystemClassLoader()}。)
* @return a Resources object from which to access resources. (返回一个可访问的资源的对象)
*/
public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable String packageName,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader,
@Nullable Context context,
boolean isThemeable) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
"ResourcesManager#createBaseActivityResources");
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo,
isThemeable,
getThemeConfig());
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
if (DEBUG) {
Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
+ " with key=" + key);
}
synchronized (this) {
// Force the creation of an ActivityResourcesStruct.(强制创建ActivityResourcesStruct。)
getOrCreateActivityResourcesStructLocked(activityToken);
}
// Update any existing Activity Resources references.(更新任何现有的Activity Resources引用。)
updateResourcesForActivity(activityToken, overrideConfig, displayId,
false /* movedToDifferentDisplay */, packageName, context, isThemeable);
// Now request an actual Resources object.(现在请求一个实际的Resources对象。)
return getOrCreateResources(activityToken, key, classLoader,
packageName, context, isThemeable);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
#ResourcesManager
private ActivityResources getOrCreateActivityResourcesStructLocked(
@NonNull IBinder activityToken) {
ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
if (activityResources == null) {
activityResources = new ActivityResources();
mActivityResourceReferences.put(activityToken, activityResources);
}
return activityResources;
}
/**
* Updates an Activity's Resources object with overrideConfig. The Resources object
* that was previously returned by
* {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
* CompatibilityInfo, ClassLoader)} is
* still valid and will have the updated configuration.
(使用overrideConfig更新Activity的Resources对象。 先前由{@link #getResources(IBinder,String,String [],String [],String [],int,Configuration,CompatibilityInfo,ClassLoader)}返回的Resources对象仍然有效,并且将具有更新的配置。)
* @param activityToken The Activity token. //表示活动
* @param overrideConfig The configuration override to update. 配置覆盖更新。
* @param displayId Id of the display where activity currently resides.
* @param movedToDifferentDisplay Indicates if the activity was moved to different display.
*/
public void updateResourcesForActivity(@NonNull IBinder activityToken,
@Nullable Configuration overrideConfig, int displayId,
boolean movedToDifferentDisplay,
@Nullable String packageName, @Nullable Context context, boolean isThemeable) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
"ResourcesManager#updateResourcesForActivity");
synchronized (this) {
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
if (Objects.equals(activityResources.overrideConfig, overrideConfig)
&& !movedToDifferentDisplay) {
// They are the same and no change of display id, no work to do.
return;
}
// Grab a copy of the old configuration so we can create the delta's of each
// Resources object associated with this Activity.
final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
// Update the Activity's base override.
if (overrideConfig != null) {
activityResources.overrideConfig.setTo(overrideConfig);
} else {
activityResources.overrideConfig.unset();
}
if (DEBUG) {
Throwable here = new Throwable();
here.fillInStackTrace();
Slog.d(TAG, "updating resources override for activity=" + activityToken
+ " from oldConfig="
+ Configuration.resourceQualifierString(oldConfig)
+ " to newConfig="
+ Configuration.resourceQualifierString(
activityResources.overrideConfig) + " displayId=" + displayId,
here);
}
final boolean activityHasOverrideConfig =
!activityResources.overrideConfig.equals(Configuration.EMPTY);
// Rebase each Resources associated with this Activity.
final int refCount = activityResources.activityResources.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResRef = activityResources.activityResources.get(
i);
Resources resources = weakResRef.get();
if (resources == null) {
continue;
}
// Extract the ResourcesKey that was last used to create the Resources for this
// activity.
final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
if (oldKey == null) {
Slog.e(TAG, "can't find ResourcesKey for resources impl="
+ resources.getImpl());
continue;
}
// Build the new override configuration for this ResourcesKey.
final Configuration rebasedOverrideConfig = new Configuration();
if (overrideConfig != null) {
rebasedOverrideConfig.setTo(overrideConfig);
}
if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
// Generate a delta between the old base Activity override configuration and
// the actual final override configuration that was used to figure out the
// real delta this Resources object wanted.
Configuration overrideOverrideConfig = Configuration.generateDelta(
oldConfig, oldKey.mOverrideConfiguration);
rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
}
// Create the new ResourcesKey with the rebased override config.
//首先会以APK路径、屏幕设备id、配置等构件一个资源key,根据这个key得到ActivityThread类的mActivityResources(HashMap类型)中查询是否已经加载过该APK的资源,如果含有缓存则直接使用缓存。这个mActivityResources维护了当亲应用程序中加载的每一个APK文件及其对应的Resources对象的对应关系。如果没有缓存,那么就会新创建一个,并且保存在mActiveResources中。
//在没有资源缓存的情况下,ActivityThread会新建一个AssetManager对象,并且调用AssetManager对象的addAssetPath函数来将resDir作为它的资源目录,这个resDir就是APK文件的绝对路径。创建一个新的AssetManager对象之后,就会将这个AssetManager对象作为Resource构造函数的第一个参数来构建一个新的Resources对象会以前面所创建的ResourcesKey对象为键值缓存在mActivities所描述的一个HashMap中,以便重复使用该资源时无需重复创建。
final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
oldKey.mSplitResDirs,
oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
rebasedOverrideConfig, oldKey.mCompatInfo,
oldKey.mIsThemeable,oldKey.mThemeConfig);
if (DEBUG) {
Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
+ " to newKey=" + newKey + ", displayId=" + displayId);
}
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
if (resourcesImpl == null) {
resourcesImpl = createResourcesImpl(newKey, packageName, context, isThemeable);
if (resourcesImpl != null) {
mResourceImpls.put(newKey, new WeakReference<>(resourcesImpl));
}
}
if (resourcesImpl != null && resourcesImpl != resources.getImpl()) {
// Set the ResourcesImpl, updating it for all users of this Resources
// object.
resources.setImpl(resourcesImpl);
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
/**
* Gets an existing Resources object set with a ResourcesImpl object matching the given key,
* or creates one if it doesn't exist.
*(获取与给定键匹配的ResourcesImpl对象的现有Resources对象集,如果不存在则创建一个。)
* @param activityToken The Activity this Resources object should be associated with.(应与此Resources对象关联的Activity。)
* @param key The key describing the parameters of the ResourcesImpl object.(描述ResourcesImpl对象参数的键。)
* @param classLoader The classloader to use for the Resources object.
* If null, {@link ClassLoader#getSystemClassLoader()} is used.(用于Resources对象的类加载器。
如果为null,则使用{@link ClassLoader#getSystemClassLoader()}。)
* @return A Resources object that gets updated when
* {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
* is called.(调用{@link #applyConfigurationToResourcesLocked(Configuration,CompatibilityInfo)}时更新的Resources对象)
*/
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader,
@Nullable String packageName, @Nullable Context context, boolean isThemeable) {
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, packageName, context, isThemeable);
if (resourcesImpl == null) {
return null;
}
synchronized (this) {
ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
if (existingResourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
+ " new impl=" + resourcesImpl);
}
resourcesImpl.getAssets().close();
resourcesImpl = existingResourcesImpl;
} else {
// 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;
}
}
private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key,
@Nullable String packageName, @Nullable Context context, boolean isThemeable) {
final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);
daj.setCompatibilityInfo(key.mCompatInfo);
//还未加载过,则构建AssetManager对象
final AssetManager assets = createAssetManager(key);
if (assets == null) {
return null;
}
assets.setAppName(packageName);
assets.setThemeSupport(isThemeable);
//获取屏幕分辨率
final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);
//获取设置的配置
final Configuration config = generateConfig(key, dm);
boolean iconsAttached = false;
//适当时将主题信息附加到生成的AssetManager。
/* Attach theme information to the resulting AssetManager when appropriate. */
if (config != null && context != null && !context.getPackageManager().isSafeMode()
&& !packageName.equals("android")
&& !packageName.equals("com.android.providers.settings")
&& !packageName.equals("org.gome.mksettings")
&& !packageName.equals("system")) {
//panjuan add 0529 for GM22APF-765 begin
//获取主题信息
ThemeConfig themeConfig = key.mThemeConfig;
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();
}
}//panjuan add 0529 for GM22APF-765 end
if (isThemeable) {
if (themeConfig != null) {
if (key.mOverlayDirs != null && key.mOverlayDirs.length > 0) {//panjuan for Optimizing startup time.
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);
}*/
if("com.gau.go.launcherex".equals(packageName)) {
config.themeConfig = null;
} else {
config.themeConfig = themeConfig;
}
}
//4.创建资源,参数1为AssertManager
final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);
if (iconsAttached) setActivityIcons(impl);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
/**
* Protected so that tests can override and returns something a fixed value.
*/()
@VisibleForTesting
protected @NonNull DisplayMetrics getDisplayMetrics(int displayId, DisplayAdjustments da) {
DisplayMetrics dm = new DisplayMetrics();
final Display display = getAdjustedDisplay(displayId, da);
if (display != null) {
display.getMetrics(dm);
} else {
dm.setToDefaults();
}
return dm;
}
/**
* Returns an adjusted {@link Display} object based on the inputs or null if display isn't
* available.
*(根据输入返回调整后的{@link Display}对象,如果不显示,则返回null。)
* @param displayId display Id.
* @param resources The {@link Resources} backing the display adjustments.
*/
public Display getAdjustedDisplay(final int displayId, Resources resources) {
synchronized (this) {
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
if (dm == null) {
// may be null early in system startup
return null;
}
return dm.getCompatibleDisplay(displayId, resources);
}
}
/**
* Creates an AssetManager from the paths within the ResourcesKey.
*(从ResourcesKey中的路径创建AssetManager。)
* This can be overridden in tests so as to avoid creating a real AssetManager with
* real APK paths.
* @param key The key containing the resource paths to add to the AssetManager.
* @return a new AssetManager.
*/
@VisibleForTesting
protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
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.
if (key.mResDir != null) {
if (assets.addAssetPath(key.mResDir) == 0) {
Log.e(TAG, "failed to add asset path " + key.mResDir);
return null;
}
}
if (key.mSplitResDirs != null) {
for (final String splitResDir : key.mSplitResDirs) {
if (assets.addAssetPath(splitResDir) == 0) {
Log.e(TAG, "failed to add split asset path " + splitResDir);
return null;
}
}
}
if (key.mOverlayDirs != null) {
for (final String idmapPath : key.mOverlayDirs) {
Log.d(TAG, "idmapPath:" + idmapPath);
//assets.addOverlayPath(idmapPath, null, null, null, null);//panjuan for theme overlay 0302 remove for 8.0
}
}
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.
if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
Log.w(TAG, "Asset path '" + libDir +
"' does not exist or contains no resources.");
}
}
}
}
return assets;
}
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
* appropriate asset manager with {@link Resources#getAssets}. Not for
* use by applications.
(创建仅包含基本系统asserts的新AssetManager。应用程序通常不会使用此方法,而是检索适当的assert manager{@link Resources#getAssets}。 不是为了由应用程序使用。)
* {@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();
}
}
private static void ensureSystemAssets() {
synchronized (sSync) {
if (sSystem == null) {
//系统资源的AssertManager对象
AssetManager system = new AssetManager(true);
system.makeStringBlocks(null);
sSystem = system;
}
}
}
private AssetManager(boolean isSystem) {
if (DEBUG_REFS) {
synchronized (this) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
}
init(true);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
}
private native final void init(boolean isSystem);
在AssertManager的构造函数中,首先会调用init函数进行初始化,
然后调用ensureSystemAssets函数来加载系统资源,这些系统资源
存储在mSystem对象中,mSystem也是AssetManager类型。需要注意的是
init函数并不是一个Java函数,而是一个Native层的方法,它的实现在
android_util_AssetManager.cpp文件中,具体代码如下:
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
{
///M: Fix ANR in webview_zygote, Avoid non-root process fork child idmap, it is no need.
if (isSystem && getuid() == 0) {
verifySystemIdmaps();
}
AssetManager* am = new AssetManager();//创建Native层的AssertManager
if (am == NULL) {
jniThrowException(env, "java/lang/OutOfMemoryError", "");
return;
}
am->addDefaultAssets();//添加默认的资源
ALOGV("Created AssetManager %p for Java object %pn", am, clazz);
//将Native层的AssertManager对象存储到Java层的AssertManager的mObject字段中
env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
}
在init函数中首先创建一个Native层的AssertManager对象,然后添加了默认的的系统资源,最后将这个AssertManager对象转换为整型并且传递给Java层的AssertManager的mObject字段,这样Java层就相当于存有了一个Native层AssertManager的句柄。这里我们关注的是addDefaultAsserts函数。
static const char* kSystemAssets = "framework/framework-res.apk";
bool AssetManager::addDefaultAssets()
{
const char* root = getenv("ANDROID_ROOT");
LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
///M:add for CIP feature @{
String8 pathCip(root);
pathCip.appendPath(kCIPSystemAssets);
if(access(pathCip, W_OK) == 0) {
ALOGW("AssetManager-->addDefaultAssets CIP path exsit!");
bool isOK1 = addAssetPath(pathCip, NULL, false, true);
if(!isOK1){
ALOGW("AssetManager-->addDefaultAssets CIP path isok1 is false");
}
String8 pathCip2(root);
pathCip2.appendPath(kCIPMediatekAssets);
bool isOK2 = addAssetPath(pathCip2, NULL, false, true);
if(!isOK2){
ALOGW("AssetManager-->addDefaultAssets CIP path isok2 is false");
}
return isOK1;
} else {
//ALOGD("AssetManager-->addDefaultAssets CIP path not exsit!");
//构建系统路径
String8 path(root);
//系统路径+framework资源APK路径,拼接成完整路径。
path.appendPath(kSystemAssets);
///M:add the new resource path into default path,so all the app can reference,@{
bool isOK1 = addAssetPath(path, NULL, false /* appAsLib */, true /* isSystemAsset */);
String8 path2(kMediatekAssets);
bool isOK2 = addAssetPath(path2, NULL, false, false);
if(!isOK2){
ALOGW("AssetManager-->addDefaultAssets isok2 is false");
}
String8 path3(root);
path3.appendPath(kGomeAssets);
bool isOK3 =addAssetPath(path3, NULL);
if(!isOK3){
ALOGW("AssetManager-->addDefaultAssets isok3 is false");
}else{
ALOGW("AssetManager-->addDefaultAssets isok3 is true");
}
return isOK1;
///@}
}
///@}
}
代码比较简单,就是拼接系统资源路径,最好将该路径传递给addAssetPath函数中,实际上就是将系统资源路径添加到AssetManager中。
bool AssetManager::addAssetPath(
const String8& path, int32_t* cookie, bool appAsLib, bool isSystemAsset) {
AutoMutex _l(mLock);
asset_path ap;
String8 realPath(path);
if (kAppZipName) {
realPath.appendPath(kAppZipName);
}
//判断path路径是否存在以及是否是文件类型,如果路径不合法则直接返回false
ap.type = ::getFileType(realPath.string());
if (ap.type == kFileTypeRegular) {
ap.path = realPath;
} else {
ap.path = path;
ap.type = ::getFileType(path.string());
if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
ALOGW("Asset path %s is neither a directory nor file (type=%d).",
path.string(), (int)ap.type);
return false;
}
}
// Skip if we have it already.
for (size_t i=0; i<mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == ap.path) {
if (cookie) {
*cookie = static_cast<int32_t>(i+1);
}
return true;
}
}
ALOGV("In %p Asset %s path: %s", this,
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
ap.isSystemAsset = isSystemAsset;
//添加到资源路径列表中
mAssetPaths.add(ap);
if (mResources != NULL) {
size_t index = mAssetPaths.size() - 1;
appendPathToResTable(ap, &index, appAsLib);
}
// new paths are always added at the end
if (cookie) {
*cookie = static_cast<int32_t>(mAssetPaths.size());
}
#ifdef __ANDROID__
// Load overlays, if any
asset_path oap;
for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
oap.isSystemAsset = isSystemAsset;
mAssetPaths.add(oap);
}
#endif
return true;
}
此时,在ActivityThread的getTopLevelResources 函数中的new AssetManager的过程就执行完毕了,然后继续执行AssetManager对象的addAssetPath函数。
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
*(向asset manager添加一组额外asset。 这可以是目录或ZIP文件。 不适用于应用程序。 返回已添加asset的cookie,或者失败时返回0。)
* {@hide}
*/
public final int addAssetPath(String path) {
return addAssetPathInternal(path, false);
}
private final int addAssetPathInternal(String path, boolean appAsLib) {
synchronized (this) {
int res = addAssetPathNative(path, appAsLib);
makeStringBlocks(mStringBlocks);
return res;
}
}
private native final int addAssetPathNative(String path, boolean appAsLib);
Java层的AssetManager的addAssetPath函数中实际上调用到是Native层的addAssetPathNative函数。需要注意的是,这个path函数必须是一个目录或者是一个zip压缩文件(APK本质上就是一个zip文件)路径。这个addAssetPathNative 函数在Native层的函数为android_content_AssetManager_addAssetPath , 该函数定义在android_util_AssetManager.cpp中。
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
jstring path, jboolean appAsLib)
{
ScopedUtfChars path8(env, path);
if (path8.c_str() == NULL) {
return 0;
}
//将Java AssetManager对象中的mObject字段转换为AssetManager指针。
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
int32_t cookie;
//将新的路径添加到资源列表中
bool res = am->addAssetPath(String8(path8.c_str()), &cookie, appAsLib);
return (res) ? static_cast<jint>(cookie) : 0;
}
// this guy is exported to other jni routines
AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj)
{
jlong amHandle = env->GetLongField(obj, gAssetManagerOffsets.mObject);
AssetManager* am = reinterpret_cast<AssetManager*>(amHandle);
if (am != NULL) {
return am;
}
jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
return NULL;
}
在android_content_AssetManager_addAssetPath 函数中会调用assetManagerForJavaObject函数先将从java层的AssetManager对象中获取到mObject字段,该字段存储了由Native层AssetManager指针转换而来的整型值。此时需要通过这个整型值逆向地转换为Native层的AsssetManager对象。然后将APK路径添加到资源路径中,这样也就含有了应用本身是资源。
我们再来分析这个过程,首先在Java层的AssetManager构造函数中调用了init函数初始化系统资源,创建与初始化完毕之后又调用了Java层AssetManager对象的addAssetPath函数将应用APK的路径资源传递给Native层的AssetManager,使得应用资源也被添加到AssetManager中,这样资源路径就构建完毕了。
#ResourcesImpl
/**
* Creates a new ResourcesImpl object with CompatibilityInfo.
*(使用CompatibilityInfo创建一个新的ResourcesImpl对象。)
* @param assets Previously created AssetManager.
* @param metrics Current display metrics to consider when
* selecting/computing resource values.
* @param config Desired device configuration to consider when
* selecting/computing resource values (optional).
* @param displayAdjustments this resource's Display override and compatibility info.
* Must not be null.
*/
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
//更新配置
updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
//mAssets.ensureStringBlocks();
assets.recreateStringBlocks();
}
Resource类的构造函数首先将参数assets所指向的一个AssetManager对象保存在成员变量mAssets中,我们获取资源就是通过这个AssetManager对象进行操作的。接下来调用updateConfiguration 函数来配置设备信息,最后调用AssetManager的成员函数recreateStringBlocks来创建字符串资源池。
#ResourcesImpl
public void updateConfiguration(Configuration config, DisplayMetrics metrics,
CompatibilityInfo compat) {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesImpl#updateConfiguration");
try {
synchronized (mAccessLock) {
if (false) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is "
+ mConfiguration + " old compat is "
+ mDisplayAdjustments.getCompatibilityInfo());
Slog.i(TAG, "**** Updating config of " + this + ": new config is "
+ config + " new compat is " + compat);
}
if (compat != null) {
mDisplayAdjustments.setCompatibilityInfo(compat);
}
//屏幕相关的信息
if (metrics != null) {
mMetrics.setTo(metrics);
}
// NOTE: We should re-arrange this code to create a Display
// with the CompatibilityInfo that is used everywhere we deal
// with the display in relation to this app, rather than
// doing the conversion here. This impl should be okay because
// we make sure to return a compatible display in the places
// where there are public APIs to retrieve the display... but
// it would be cleaner and more maintainable to just be
// consistently dealing with a compatible display everywhere in
// the framework.
mDisplayAdjustments.getCompatibilityInfo().applyToDisplayMetrics(mMetrics);
final @Config int configChanges = calcConfigChanges(config);
// If even after the update there are no Locales set, grab the default locales.
LocaleList locales = mConfiguration.getLocales();
if (locales.isEmpty()) {
locales = LocaleList.getDefault();
mConfiguration.setLocales(locales);
}
if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
if (locales.size() > 1) {
// The LocaleList has changed. We must query the AssetManager's available
// Locales and figure out the best matching Locale in the new LocaleList.
String[] availableLocales = mAssets.getNonSystemLocales();
if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
// No app defined locales, so grab the system locales.
availableLocales = mAssets.getLocales();
if (LocaleList.isPseudoLocalesOnly(availableLocales)) {
availableLocales = null;
}
}
if (availableLocales != null) {
final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
availableLocales);
if (bestLocale != null && bestLocale != locales.get(0)) {
mConfiguration.setLocales(new LocaleList(bestLocale, locales));
}
}
}
}
//设备DPI
if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
mMetrics.densityDpi = mConfiguration.densityDpi;
mMetrics.density =
mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
}
// Protect against an unset fontScale.
mMetrics.scaledDensity = mMetrics.density *
(mConfiguration.fontScale != 0 ? mConfiguration.fontScale : 1.0f);
//屏幕宽高
final int width, height;
if (mMetrics.widthPixels >= mMetrics.heightPixels) {
width = mMetrics.widthPixels;
height = mMetrics.heightPixels;
} else {
//noinspection SuspiciousNameCombination
width = mMetrics.heightPixels;
//noinspection SuspiciousNameCombination
height = mMetrics.widthPixels;
}
final int keyboardHidden;
if (mConfiguration.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
&& mConfiguration.hardKeyboardHidden
== Configuration.HARDKEYBOARDHIDDEN_YES) {
keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT;
} else {
keyboardHidden = mConfiguration.keyboardHidden;
}
//根据配置初始化AssetManager
mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
mConfiguration.orientation,
mConfiguration.touchscreen,
mConfiguration.densityDpi, mConfiguration.keyboard,
keyboardHidden, mConfiguration.navigation, width, height,
mConfiguration.smallestScreenWidthDp,
mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
mConfiguration.screenLayout, mConfiguration.uiMode,
mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
if (DEBUG_CONFIG) {
Slog.i(TAG, "**** Updating config of " + this + ": final config is "
+ mConfiguration + " final compat is "
+ mDisplayAdjustments.getCompatibilityInfo());
}
/// M: Boost cache on system @{
mMtkBoostDrawableCache.onConfigurationChange(configChanges);
/// @}
mDrawableCache.onConfigurationChange(configChanges);
mColorDrawableCache.onConfigurationChange(configChanges);
mComplexColorCache.onConfigurationChange(configChanges);
mAnimatorCache.onConfigurationChange(configChanges);
mStateListAnimatorCache.onConfigurationChange(configChanges);
flushLayoutCache();
}
synchronized (sSync) {
if (mPluralRule != null) {
mPluralRule = PluralRules.forLocale(mConfiguration.getLocales().get(0));
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
ResourcesManager的createResourcesImpl函数中将初始化好AssetManager、设备配置等作为参数来构造Java层的ResourceImpl对象。
Resources类的成员函数updateConfiguration首先是根据参数config和metrics来更新设备的当前配置信息,例如屏幕大小和密码、国家(地区)和语言、键盘配置情况等。接着再调用成员变量mAssets所指向的一个Java层的AssetManager对象的成员函数setConfiguration来将这些配置信息设置到与之关联的C++层的AssetManager对象中。这样一来,在我们通过Resources来获取资源时,Native层就会根据这个配置信息寻找最合适的资源返回,从而达到多屏幕适配的效果。
/**
* Creates the top level resources for the given package. Will return an existing
* Resources if one has already been created.
*/(创建给定包的顶级资源。 如果已经创建了一个现有资源,将返回现有资源。)
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId,
LoadedApk pkgInfo, Context context, String pkgName) {
return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
displayId, pkgName, null, pkgInfo.getCompatibilityInfo(),
pkgInfo.getClassLoader(), context, pkgInfo.getApplicationInfo().isThemeable);
}
总结:
经过上述分析过程,可以得出一个结论:应用的资源存储在APK中,一个AssetManager可以管理多个路径的资源文件,Resources通过AssetManager获取资源。那就是我们可以通过AsseetManager的addAssetPath方法添加APK路径以达到一些资源替换或者换肤的效果,使得插件Activity等组件可以通过资源类R来访问应用资源。
参考《Android源码设计模式》
最后
以上就是无聊睫毛膏为你收集整理的Android资源的加载与匹配的全部内容,希望文章能够帮你解决Android资源的加载与匹配所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复