Android資源加載流程

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章