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的返回