Android资源加载流程
从使用到原理
使用
首先来看一个从资源string获取字符串的使用
public class ResourceActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
public void getString(){
String appName = getResources().getString(R.string.app_name);
}
}
深入到getResources()分析
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
//.....省略
@Override
public Resources getResources() {
if (mResources == null && VectorEnabledTintResources.shouldBeUsed()) {
mResources = new VectorEnabledTintResources(this, super.getResources());
}
return mResources == null ? super.getResources() : mResources;
}
//.....省略
}
不管mResources是否为null其实都会调用super.getResources()方法,而super.getResources()对应的父类就是ContextThemeWrapper
public class ContextThemeWrapper extends ContextWrapper {
@Override
public Resources getResources() {
return getResourcesInternal();
}
private Resources getResourcesInternal() {
//首次调用mResources为null,初始化
if (mResources == null) {
//如果没有重写了配置,调用父类获取
//重写配置的条件
//(sdk in 21-25 || 黑夜模式切换 )&& Manifest没有处理UI模式 && sdk>=17 && 没有调用attachBaseContext && Activity继承ContextThemeWrapper
if (mOverrideConfiguration == null) {
mResources = super.getResources();
} else {
//重写了配置,就使用新配置信息获取
final Context resContext = createConfigurationContext(mOverrideConfiguration);
mResources = resContext.getResources();
}
}
return mResources;
}
}
public class ContextWrapper extends Context {
@Override
public Resources getResources() {
//当前的mBase就是ContextImpl
return mBase.getResources();
}
}
mBase是什么时候设置的
ActivityThread#performLaunchActivity()中在每启动一个新Activity的时候都会创建对应的ContextImpl
ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//···省略非关键代码
//创建activity
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
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 {
//获取创建Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
//···省略无关代码
if (activity != null) {
//···省略无关代码
appContext.setOuterContext(activity);
//将appContext(ContextImpl)设置进入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,
r.assistToken);
//···省略无关代码
}
return activity;
}
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
·····省略无关代码
//创建Activity的ContextImpl,传入对应的显示id和设备配置
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
······省略无关代码
return appContext;
}
ContextImpl.java
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
//真正创建Activity ContextImpl的地方
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
activityToken, null, 0, classLoader, null);
······省略
//创建资源管理器
final ResourcesManager resourcesManager = ResourcesManager.getInstance();
// ResourcesManager类创建Activity的全部配置,并设置给ContextImpl
context.setResources(resourcesManager.createBaseActivityResources(activityToken,
packageInfo.getResDir(), //基本资源路径
splitDirs,//分割的资源路径
packageInfo.getOverlayDirs(),//覆盖路径
packageInfo.getApplicationInfo().sharedLibraryFiles,//app库文件
displayId,
overrideConfiguration,
compatInfo,
classLoader));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
}
ResourcesManager.java
public @Nullable Resources createBaseActivityResources(@NonNull IBinder activityToken,//Activity
@Nullable String resDir,//基本资源路径。 可以为null(仅将加载framwork资源)。
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
@Nullable ClassLoader classLoader) {
try {
//使用当前的路径和设备信息,构建去查询资源的ResourcesKey
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
if (DEBUG) {
Slog.d(TAG, "createBaseActivityResources activity=" + activityToken
+ " with key=" + key);
}
synchronized (this) {
// 创建一个ActivityResourcesStruct
getOrCreateActivityResourcesStructLocked(activityToken);
}
// 更新任一存在Activity的Resources引用
updateResourcesForActivity(activityToken, overrideConfig, displayId,
false /* movedToDifferentDisplay */);
// 这里实际去请求对应的Resources对象
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
//获取带有与给定键匹配的ResourcesImpl对象的现有Resources对象集,如果不存在则创建一个。
private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,
@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
synchronized (this) {
if (activityToken != null) {//Activity对应Resources
//获取Activity对应的ActivityResources
final ActivityResources activityResources =
getOrCreateActivityResourcesStructLocked(activityToken);
// 清理所有无效的引用,以免它们堆积。
ArrayUtils.unstableRemoveIf(activityResources.activityResources,
sEmptyReferencePredicate);
//使用key在缓存中找到对应的ResourcesImpl实现
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
//存在就返回
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
}
} else {
//缓存中获取非Activity对应Resources
// 不依赖于一个Activity,找到具有合适的ResourcesImpl共享资源
ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
return getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
}
// 如果我们在这里,我们找不到适合使用的ResourcesImpl,所以现在创建一个。
// 在这里面会创建对应的AssetManager
ResourcesImpl resourcesImpl = createResourcesImpl(key);
if (resourcesImpl == null) {
return null;
}
// 添加ResourcesImpl进入缓存
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
//然后根据是否是Activity创建对应的Resources,因为Service也会有ContextImpl
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl, key.mCompatInfo);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl, key.mCompatInfo);
}
return resources;
}
}
//ResourcesManager是一个单例模式,所有很多地方都会直接调用getResources来查找对应的Resources
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");
//构建查找的key
final ResourcesKey key = new ResourcesKey(
resDir,
splitResDirs,
overlayDirs,
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
//获取对应的Resources
return getOrCreateResources(activityToken, key, classLoader);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
接下来聚焦到ContextImpl
class ContextImpl extends Context {
@Override
public Resources getResources() {
//实际获取成员变量的mResources,上面讲到的setResources方法设置的
return mResources;
}
void setResources(Resources r) {
if (r instanceof CompatResources) {
((CompatResources) r).setContext(this);
}
mResources = r;
}
}
获取String资源
上面看了怎么设置Resources,接下来将会看如何从strings.xml获取到String
public class Resources {
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
//创建ResourcesImpl对象
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
public String getString(@StringRes int id) throws NotFoundException {
//调用getText(id)获取
return getText(id).toString();
}
@NonNull public CharSequence getText(@StringRes int id) throws NotFoundException {
//调用mResourcesImpl的getAssets获取到AssetManager
//然后调用getResourceText获取
CharSequence res = mResourcesImpl.getAssets().getResourceText(id);
if (res != null) {
return res;
}
throw new NotFoundException("String resource ID #0x"
+ Integer.toHexString(id));
}
}
获取String,实际上是在调用AssetManager的getResourceText方法,下面进入getResourceText(id)
public final class AssetManager implements AutoCloseable {
@UnsupportedAppUsage
@Nullable CharSequence getResourceText(@StringRes int resId) {
synchronized (this) {
//一个动态id的数据容器,用于保存资源值
final TypedValue outValue = mValue;
//根据资源resId获取值
if (getResourceValue(resId, 0, outValue, true)) {
return outValue.coerceToString();
}
return null;
}
}
boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
Preconditions.checkNotNull(outValue, "outValue");
synchronized (this) {
//确保不破坏本机实现。 AssetManager可能已关闭,但是对其的引用仍然存在,因此不会破坏本机实现。
ensureValidLocked();
//调用native方法获取资源值
//mObject是指向本地native实现的指针
final int cookie = nativeGetResourceValue(
mObject, resId, (short) densityDpi, outValue, resolveRefs);
if (cookie <= 0) {
return false;
}
// Convert the changing configurations flags populated by native code.
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
outValue.changingConfigurations);
//如果是字符串类型,给输出string赋值
if (outValue.type == TypedValue.TYPE_STRING) {
outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
}
return true;
}
}
}
通过AssetManager.getResourceText(id),内部实际调用了native的方法去获取。
/frameworks/base/core/jni/android_util_AssetManager.cpp
using ApkAssetsCookie = int32_t;
enum : ApkAssetsCookie {
kInvalidCookie = -1,
};
static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
jshort density, jobject typed_value,
jboolean resolve_references) {
//通过ptr获取到对应的AssetManager,ptr会在Java层构造函数里调用nativeCreate获取到ptr
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
Res_value value;
ResTable_config selected_config;
uint32_t flags;
//调用jni层assetmanager的GetResource获取资源,传入resid,对应density,输出对象value,配置selected_config
ApkAssetsCookie cookie =
assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
static_cast<uint16_t>(density), &value, &selected_config, &flags);
//如果获取失败cookie=-1
if (cookie == kInvalidCookie) {
//返回-1
return ApkAssetsCookieToJavaCookie(kInvalidCookie);
}
//转换资源id
uint32_t ref = static_cast<uint32_t>(resid);
//解析引用,{@ code false}使其不解析
if (resolve_references) {
cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &flags, &);
if (cookie == kInvalidCookie) {
return ApkAssetsCookieToJavaCookie(kInvalidCookie);
}
}
//CopyValue方法将得到的值,通过env的set方法更改outValue对象的值
return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value);
}
/**
* 通过nativeCreate创建时返回的Long类型指针值,查找对应的AssetManager
**/
static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) {
return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr));
}
NativeGetResourceValue方法中,实际解析资源方法的方法assetmanager->GetResource
/frameworks/base/libs/androidfw/AssetManager2.cpp
ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
uint16_t density_override, Res_value* o t_value,
ResTable_config* out_selected_config,
uint32_t* out_flags) const {
FindEntryResult entry;
//这个方法有点长,根据resid和density_override是否为0查找最佳适配
ApkAssetsCookie cookie =
FindEntry(resid, density_override, false /* stop_at_first_match */, &entry);
if (cookie == kInvalidCookie) {
return kInvalidCookie;
}
if (dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) {
if (!may_be_bag) {
LOG(ERROR) << base::StringPrintf("Resource %08x is a complex map type.", resid);
return kInvalidCookie;
}
// 创建引用,因为我们不能将此复杂类型表示为Res_value.然后在Java层可以根据out_value中获取到值
out_value->dataType = Res_value::TYPE_REFERENCE;
out_value->data = resid;
*out_selected_config = entry.config;
*out_flags = entry.type_flags;
return cookie;
}
const Res_value* device_value = reinterpret_cast<const Res_value*>(
reinterpret_cast<const uint8_t*>(entry.entry) + dtohs(entry.entry->size));
out_value->copyFrom_dtoh(*device_value);
// 转换成运行时的包id
entry.dynamic_ref_table->lookupResourceValue(out_value);
*out_selected_config = entry.config;
*out_flags = entry.type_flags;
return cookie;
}
查找资源的适配过程
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
bool /*stop_at_first_match*/,
FindEntryResult* out_entry) const {
// 如果density_override不为0可能使用
ResTable_config density_override_config;
// 选择我们的配置或者生成一个密度重写的配置
const ResTable_config* desired_config = &configuration_;
if (density_override != 0 && density_override != configuration_.density) {
density_override_config = configuration_;
density_override_config.density = density_override;
desired_config = &density_override_config;
}
//校验16进制前面4位不能为0,
//前两位-系统01,用户应用7f。而第三方资源包,则是从0x02开始逐个递增
//第3-4位是资源类型,如anim文件夹下的资源就是01
//第5-8位是资源每一项对应的id
if (!is_valid_resid(resid)) {
LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
return kInvalidCookie;
}
//获取包id、资源类型id、实体id、以及包id索引
const uint32_t package_id = get_package_id(resid);
const uint8_t type_idx = get_type_id(resid) - 1;
const uint16_t entry_idx = get_entry_id(resid);
const uint8_t package_idx = package_ids_[package_id];
if (package_idx == 0xff) {
LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);
return kInvalidCookie;
}
//初始化参数
const PackageGroup& package_group = package_groups_[package_idx];
const size_t package_count = package_group.packages_.size();
ApkAssetsCookie best_cookie = kInvalidCookie;
const LoadedPackage* best_package = nullptr;
const ResTable_type* best_type = nullptr;
const ResTable_config* best_config = nullptr;
ResTable_config best_config_copy;
uint32_t best_offset = 0u;
uint32_t type_flags = 0u;
// 如果desirable_config与设置的配置相同,那么我们可以使用过滤列表,并且由于它们已经匹配,因此不需要匹配配置。
const bool use_fast_path = desired_config == &configuration_;
for (size_t pi = 0; pi < package_count; pi++) {
const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_;
ApkAssetsCookie cookie = package_group.cookies_[pi];
// 如果此包中的类型ID偏移,则在搜索类型时需要将其考虑在内。
const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx);
if (UNLIKELY(type_spec == nullptr)) {
continue;
}
uint16_t local_entry_idx = entry_idx;
// 如果此软件包提供了IDMAP,请转换条目ID。
if (type_spec->idmap_entries != nullptr) {
if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, local_entry_idx)) {
// 没有映射,因此资源并不意味着位于此叠加包中。
continue;
}
}
type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
// 如果封装是覆盖层,则甚至必须选择相同的配置。
const bool package_is_overlay = loaded_package->IsOverlay();
const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs [type_idx];
//配置相同,已经缓存了
if (use_fast_path) {
const std::vector<ResTable_config>& candidate_configs = filtered_group. configurations;
const size_t type_count = candidate_configs.size();
for (uint32_t i = 0; i < type_count; i++) {
const ResTable_config& this_config = candidate_configs[i];
// 我们可以跳过调用ResTable_config::match()的步骤,因为我们知道所有不匹配的候选配置都已被滤除。
if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
(package_is_overlay && this_config.compare(*best_config) == 0)) {
// 配置匹配并且比以前的选择更好。
// 查找该配置是否存在该输入值。
const ResTable_type* type_chunk = filtered_group.types[i];
const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx);
if (offset == ResTable_type::NO_ENTRY) {
continue;
}
best_cookie = cookie;
best_package = loaded_package;
best_type = type_chunk;
best_config = &this_config;
best_offset = offset;
}
}
} else {
// 这是较慢的路径,它不使用过滤的配置列表。
//在这里,我们必须从映射的APK中读取ResTable_config,将其转换为主机字节序,并填写编译APK时不存在的任何新字段。
//此外,在选择配置时,我们不能只记录指向ResTable_config的指针,我们必须将其复制。
const auto iter_end = type_spec->types + type_spec->type_count;
for (auto iter = type_spec->types; iter != iter_end; ++iter) {
ResTable_config this_config;
this_config.copyFromDtoH((*iter)->config);
if (this_config.match(*desired_config)) {
if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
(package_is_overlay && this_config.compare(*best_config) == 0)) {
// The configuration matches and is better than the previous selection.
// Find the entry value if it exists for this configuration.
const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx);
if (offset == ResTable_type::NO_ENTRY) {
continue;
}
best_cookie = cookie;
best_package = loaded_package;
best_type = *iter;
best_config_copy = this_config;
best_config = &best_config_copy;
best_offset = offset;
}
}
}
}
}
//最好的适配资源没有就返回-1
if (UNLIKELY(best_cookie == kInvalidCookie)) {
return kInvalidCookie;
}
//确定已经存在了资源,找到最好的Entry之后,写入out实体中,返回best_cookie
const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
if (UNLIKELY(best_entry == nullptr)) {
return kInvalidCookie;
}
out_entry->entry = best_entry;
out_entry->config = *best_config;
out_entry->type_flags = type_flags;
out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
out_entry->entry_string_ref =
StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
return best_cookie;
}
上述查找的步骤分为:
1.先校验density_override值是否有(即不为0),然后更新配置
2.校验资源id前两位和三四位类型是否存在
3.循环所有资源的包,然后找到类型规格TypeSpec,TypeSpec中包含了所有的entity的索引id。没找到就继续循环下一个包;如果找到了
4.获取到对应包的不同分辨率的信息,循环选择最佳的资源
5.找到后,写入输出实体entry,返回好的cookie标识
layout.xml资源的加载
从LayoutInflater说起,因为setContentView和在fragment中解析layout.xml都是LayoutInflater.inflate()方法
public abstract class LayoutInflater {
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
//获取到当前的Resources对象
final Resources res = getContext().getResources();
//尝试从预编译里加载View
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
//从Resources获取Layout的资源解析器
XmlResourceParser parser = res.getLayout(resource);
try {
//层层解析子view
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
//使用外观模式将parser包装成AttributeSet,里面提供了解析各种类型数据的方法
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
//提前解析第一个标签
advanceToRootNode(parser);
//返回标签的名称
final String name = parser.getName();
//解析merge标签
if (TAG_MERGE.equals(name)) {
//merge标签--root必须不为空,attachToRoot为true
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//递归降低xml层次结构和实例化View,实例化其子级,然后调用onFinishInflate()
//注意 :默认可见性,因此BridgeInflater可以覆盖它。
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 创建临时的rootView
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// 创建与根匹配的布局参数(如果提供)
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// 如果我们不附加,请为temp设置布局参数。
//(如果是的话,我们在下面使用addView)
temp.setLayoutParams(params);
}
}
// 解析所有的子View
rInflateChildren(parser, temp, attrs, true);
// 如果需要attachToRoot,则添加进root中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 确定是返回传入的根还是在xml中找到的顶视图.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(inflaterContext, attrs)
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// 清除引用
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
private @Nullable
View tryInflatePrecompiled(@LayoutRes int resource, Resources res, @Nullable ViewGroup root,
boolean attachToRoot) {
if (!mUseCompiledView) {
return null;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate (precompiled)");
// 尝试通过resId获取包名
String pkg = res.getResourcePackageName(resource);
// 尝试通过resId获取layout
String layout = res.getResourceEntryName(resource);
//尝试通过反射创建拿到已经编译好的View
try {
Class clazz = Class.forName("" + pkg + ".CompiledView", false, mPrecompiledClassLoader);
Method inflater = clazz.getMethod(layout, Context.class, int.class);
View view = (View) inflater.invoke(null, mContext, resource);
if (view != null && root != null) {
// We were able to use the precompiled inflater, but now we need to do some work to
// attach the view to the root correctly.
XmlResourceParser parser = res.getLayout(resource);
try {
AttributeSet attrs = Xml.asAttributeSet(parser);
advanceToRootNode(parser);
ViewGroup.LayoutParams params = root.generateLayoutParams(attrs);
if (attachToRoot) {
root.addView(view, params);
} else {
view.setLayoutParams(params);
}
} finally {
parser.close();
}
}
return view;
} catch (Throwable e) {
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return null;
}
}
Layout的步骤上述有以下几步:
1、从context->ContextImpl中获取获取到Resources对象
2、尝试从预编译View里加载View
3、从Resources获取Layout的XmlResourceParser资源解析器,其中包含资源id查找到xml文件的过程,将在下面分析
4、使用XmlResourceParser第一个标签,如果是Merge标签,就替换,层层解析出子View。否则,先创建一个临时rootView,然后添加子View,root不为空且attachToRoot=true的话才添加进rootView
下面来看资源id绑定xml的过程
Resources.java
public class Resources {
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
//ResourcesImpl获取到值,实际上也是调用的AssetManager#getResourceValue方法
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
+ " type #0x" + Integer.toHexString(value.type) + " is not valid");
} finally {
releaseTempTypedValue(value);
}
}
}
ResourcesImpl.java
public class ResourcesImpl {
@UnsupportedAppUsage
void getValue(@AnyRes int id, TypedValue outValue, boolean resolveRefs)
throws NotFoundException {
//实际还是调用AssetManager#getResourceValue方法获取,上面已经讲过
boolean found = mAssets.getResourceValue(id, 0, outValue, resolveRefs);
if (found) {
return;
}
throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id));
}
}
assets目录的资源加载
从Resources中获取到AssetManager管理器getAssets()
Resources.java
public class Resources {
/**
* 获取这些资源的基础AssetManager存储。
*/
public final AssetManager getAssets() {
return mResourcesImpl.getAssets();
}
}
ResourcesImpl.java
public class ResourcesImpl {
public AssetManager getAssets() {
return mAssets;
}
}
AssetManager.java
public final class AssetManager implements AutoCloseable {
public @NonNull InputStream open(@NonNull String fileName) throws IOException {
//打开流权限
return open(fileName, ACCESS_STREAMING);
}
public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException {
Preconditions.checkNotNull(fileName, "fileName");
synchronized (this) {
//确认流已经打开
ensureOpenLocked();
//本地打开mObject(本地Assetmanager地址),打开文件名,权限
final long asset = nativeOpenAsset(mObject, fileName, accessMode);
//没找到文件抛出异常
if (asset == 0) {
throw new FileNotFoundException("Asset file: " + fileName);
}
//传入资源对应指针,构建AssetInputStream输入流,来去除文件
final AssetInputStream assetInputStream = new AssetInputStream(asset);
//增加索引
incRefsLocked(assetInputStream.hashCode());
return assetInputStream;
}
}
/**
* 调用c层打开asset
**/
private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode);
}
java层流程:
Resources.getAssets -> ResourcesImpl.getAssets -> AssetManager.open() -> android_util_AssetManager.nativeOpenAsset()
接下来深入jni层查看具体获取
/frameworks/base/core/java/android/content/res/AssetManager.java
static jlong NativeOpenAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path,
jint access_mode) {
//Assets目录下路径
ScopedUtfChars asset_path_utf8(env, asset_path);
//如果路径为空,排除空指针
if (asset_path_utf8.c_str() == nullptr) {
// This will throw NPE.
return 0;
}
ATRACE_NAME(base::StringPrintf("AssetManager::OpenAsset(%s)", asset_path_utf8.c_str()).c_str());
//权限校验
if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM &&
access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) {
jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
return 0;
}
//通过指针获取到对应的AssetManager
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
//通过路径打开Assets下对应资源,能找到就返回
std::unique_ptr<Asset> asset =
assetmanager->Open(asset_path_utf8.c_str(), static_cast<Asset::AccessMode>(access_mode));
//如果asset没有就温控
if (!asset) {
jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
return 0;
}
//返回正确标识
return reinterpret_cast<jlong>(asset.release());
}
实际通过AssetManager2的Open方法打开流
/frameworks/base/libs/androidfw/AssetManager2.cpp
std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename,
Asset::AccessMode mode) const {
//拼上对应路径,打开资源
const std::string new_path = "assets/" + filename;
return OpenNonAsset(new_path, mode);
}
std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
Asset::AccessMode mode,
ApkAssetsCookie* out_cookie) const {
//遍历ApkAssets资源列表,调用ApkAssets->Open(filename, mode)打开文件
for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
//找到值就返回
if (asset) {
if (out_cookie != nullptr) {
*out_cookie = i;
}
return asset;
}
}
if (out_cookie != nullptr) {
*out_cookie = kInvalidCookie;
}
return {};
}
来看ApkAssets的Open(filename, mode)
/frameworks/base/libs/androidfw/ApkAssets.cpp
std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
CHECK(zip_handle_ != nullptr);
::ZipString name(path.c_str());
::ZipEntry entry;
//通过zipName字符串找到对应的ZipEntry实体
int32_t result = ::FindEntry(zip_handle_.get(), name, &entry);
//有就直接返回
if (result != 0) {
return {};
}
//如果是压缩文件
if (entry.method == kCompressDeflated) {
std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
//找到对应file文件映射,如果创建映射失败,抛出异常
if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
entry.compressed_length, true /*readOnly*/)) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
return {};
}
//解压文件,创建资源对象asset
std::unique_ptr<Asset> asset =
Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
//解压失败,返回空
if (asset == nullptr) {
LOG(ERROR) << "Failed to decompress '" << path << "'.";
return {};
}
return asset;
} else {
std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
entry.uncompressed_length, true /*readOnly*/)) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
return {};
}
//在对应的apk中映射到文件
std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), mode);
if (asset == nullptr) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
return {};
}
//返回资源对应的指针
return asset;
}
}
这一步找的是zip包中的压缩文件,而不是资源数据中的资源Entry,这样也能找到apk包里资源目录下的layout文件
JNI部分的流程大概是:
1、asset路径校验、权限校验
2、通过ptr指针获取到AssetManager对象,调用Open()方法打开资源。
3、拼接上”assets/“前缀路径,接下来遍历ApkAssets列表zip包
4、在apk压缩包里找到最适合的layout的返回