Resources和AssetManager創建過程

1.png

每個apk有一個Resources

  • getTopLevelResources

      synchronized (this) {
       // Resources is app scale dependent.
       if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
    
       WeakReference<Resources> wr = mActiveResources.get(key);
       r = wr != null ? wr.get() : null;
       //if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
       if (r != null && r.getAssets().isUpToDate()) {
           if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
    + ": appScale=" + r.getCompatibilityInfo().applicationScale
                   + " key=" + key + " overrideConfig=" + overrideConfiguration);
           return r;
       }
    
    • mActiveResources. ActivityThread類的成員變量mActiveResources指向的是一個HashMap。這個HashMap用來維護在當前應用程序進程中加載的每一個Apk文件及其對應的Resources對象的對應關系.
    • 給定一個Apk文件路徑,ActivityThread類的成員函數getTopLevelResources可以在成員變量mActiveResources中檢查是否存在一個對應的Resources對象。如果不存在,那么就會新建一個,并且保存在ActivityThread類的成員變量mActiveResources中。
    • 參數resDir即為要獲取其對應的Resources對象的Apk文件路徑
  • 創建AssetManager對象

    public AssetManager() {
       synchronized (this) {
           if (DEBUG_REFS) {
               mNumRefs = 0;
               incRefsLocked(this.hashCode());
           }
           init(false);
           if (localLOGV) Log.v(TAG, "New asset manager: " + this);
           ensureSystemAssets();
       }
    }
    

    先調用了init(false)方法。而init通過jni調用到了底層。

    AssetManager的文件定義在android-6.0.0_r1\frameworks\base\core\java\android\content\res\AssetManager.java中。而c++層的函數定義在frameworks/base/core/jni/android_util_AssetManager.cpp

    static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
    {
        if (isSystem) {
            verifySystemIdmaps();
        }
        AssetManager* am = new AssetManager();
        if (am == NULL) {
            jniThrowException(env, "java/lang/OutOfMemoryError", "");
            return;
        }
    
        am->addDefaultAssets();
    
        ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
        env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
    }
    
    • 這里先創建了一個AssetManager對象。AssetManager.cpp定義在android-6.0.0_r1\frameworks\base\libs\androidfw\AssetManager.cpp中。

      • 然后進入到addDefaultAssets()方法。該方法添加默認的資源路徑

        bool AssetManager::addDefaultAssets()
        {
            const char* root = getenv("ANDROID_ROOT");
            LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_ROOT not set");
        
            String8 path(root);
            path.appendPath(kSystemAssets);
        
            return addAssetPath(path, NULL);
        }
        
        • s首先通過環境變量ANDROID_ROOT來獲得Android的系統路徑
        • 接著再將全局變量kSystemAssets所指向的字符串“framework/framework-res.apk”附加到這個系統路徑的后面去
        • addAssetPath來將它添加到當前正在初始化的AssetManager對象中去。
      • 然后執行env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));將c++層生成的AssetManager設置到java層的mObject中

            // For communication with native code.
            private long mObject;
        

    到這里AssetManager創建完畢。然后設置相關的路徑

    AssetManager assets = new AssetManager();
    // resDir can be null if the 'android' package is creating a new Resources object.
    // This is fine, since each AssetManager automatically loads the 'android' package
    // already.
    if (resDir != null) {
       if (assets.addAssetPath(resDir) == 0) {
           return null;
       }
    }
    
    if (splitResDirs != null) {
       for (String splitResDir : splitResDirs) {
           if (assets.addAssetPath(splitResDir) == 0) {
               return null;
           }
       }
    }
    
    if (overlayDirs != null) {
       for (String idmapPath : overlayDirs) {
           assets.addOverlayPath(idmapPath);
       }
    }
    
    if (libDirs != null) {
       for (String libDir : libDirs) {
           if (libDir.endsWith(".apk")) {
               // Avoid opening files we know do not have resources,
               // like code-only .jar files.
               if (assets.addAssetPath(libDir) == 0) {
                   Log.w(TAG, "Asset path '" + libDir +
                           "' does not exist or contains no resources.");
               }
           }
       }
    }
    

    接著就創建Resource對象

    r = new Resources(assets, dm, config, compatInfo);
    

    這里看到AssetManager保存到了Resources對象中。接著進入到Resources的構造方法中

    public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config,
           CompatibilityInfo compatInfo) {
       mAssets = assets;
       mMetrics.setToDefaults();
       if (compatInfo != null) {
           mCompatibilityInfo = compatInfo;
       }
       updateConfiguration(config, metrics);
       assets.ensureStringBlocks();
    }
    

    最后進入到updateConfiguration(Configuration config, DisplayMetrics metrics, CompatibilityInfo compat)

    mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
           locale, mConfiguration.orientation,
           mConfiguration.touchscreen,
           mConfiguration.densityDpi, mConfiguration.keyboard,
           keyboardHidden, mConfiguration.navigation, width, height,
           mConfiguration.smallestScreenWidthDp,
           mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
           mConfiguration.screenLayout, mConfiguration.uiMode,
           Build.VERSION.RESOURCES_SDK_INT);
    
    • mConfiguration指向的是一個Configuration對象,用來描述設備當前的配置信息

    • Resources類的成員函數updateConfiguration首先是根據參數config和metrics來更新設備的當前配置信息,例如,屏幕大小和密碼、國家地區和語言、鍵盤配置情況等等,接著再調用成員變量mAssets所指向的一個Java層的AssetManager對象的成員函數setConfiguration來將這些配置信息設置到與之關聯的C++層的AssetManager對象中去。

    • 進入到c++層中的定義

      static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz,
                                                                jint mcc, jint mnc,
                                                                jstring locale, jint orientation,
                                                                jint touchscreen, jint density,
                                                                jint keyboard, jint keyboardHidden,
                                                                jint navigation,
                                                                jint screenWidth, jint screenHeight,
                                                                jint smallestScreenWidthDp,
                                                                jint screenWidthDp, jint screenHeightDp,
                                                                jint screenLayout, jint uiMode,
                                                                jint sdkVersion)
      {
          AssetManager* am = assetManagerForJavaObject(env, clazz);
          if (am == NULL) {
              return;
          }
      
          ResTable_config config;
          memset(&config, 0, sizeof(config));
      
          const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;
      
          // Constants duplicated from Java class android.content.res.Configuration.
          static const jint kScreenLayoutRoundMask = 0x300;
          static const jint kScreenLayoutRoundShift = 8;
      
          config.mcc = (uint16_t)mcc;
          config.mnc = (uint16_t)mnc;
          config.orientation = (uint8_t)orientation;
          config.touchscreen = (uint8_t)touchscreen;
          config.density = (uint16_t)density;
          config.keyboard = (uint8_t)keyboard;
          config.inputFlags = (uint8_t)keyboardHidden;
          config.navigation = (uint8_t)navigation;
          config.screenWidth = (uint16_t)screenWidth;
          config.screenHeight = (uint16_t)screenHeight;
          config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp;
          config.screenWidthDp = (uint16_t)screenWidthDp;
          config.screenHeightDp = (uint16_t)screenHeightDp;
          config.screenLayout = (uint8_t)screenLayout;
          config.uiMode = (uint8_t)uiMode;
          config.sdkVersion = (uint16_t)sdkVersion;
          config.minorVersion = 0;
      
          // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
          // in C++. We must extract the round qualifier out of the Java screenLayout and put it
          // into screenLayout2.
          config.screenLayout2 =
                  (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
      
          am->setConfiguration(config, locale8);
      
          if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);
      }
      

      將ResTable_config設置到AssetManager中。繼續進入到setConfiguration

      void AssetManager::setConfiguration(const ResTable_config& config, const char* locale)
      {
          AutoMutex _l(mLock);
          *mConfig = config;
          if (locale) {
              setLocaleLocked(locale);
          } else if (config.language[0] != 0) {
              char spec[RESTABLE_MAX_LOCALE_LEN];
              config.getBcp47Locale(spec);
              setLocaleLocked(spec);
          } else {
              updateResourceParamsLocked();
          }
      }
      
      • AssetManager類的成員變量mConfig指向的是一個ResTable_config對象,用來描述設備的當前配置信息,AssetManager類的成員函數setConfiguration首先將參數config所描述的設備配置信息拷貝到它里面去。

      • local的值不等于NULL,那么它指向的字符串就是用來描述設備的國家、地區和語言信息的,這時候AssetManager類的成員函數setConfiguration就會調用另外一個成員函數setLocalLocked來將它們設置到AssetManager類的另外一個成員變量mLocale中去。

      • local的值等于NULL,并且參數config指向的一個ResTable_config對象包含了設備的國家、地區和語言信息,那么AssetManager類的成員函數setConfiguration同樣會調用另外一個成員函數setLocalLocked來將它們設置到AssetManager類的另外一個成員變量mLocale中去。

      • 如果參數local的值等于NULL,并且參數config指向的一個ResTable_config對象沒有包含設備的國家、地區和語言信息,那么就說明設備的國家、地區和語言等信息不需要更新,這時候AssetManager類的成員函數setConfiguration就會直接調用另外一個成員函數updateResourceParamsLocked來更新資源表中的設備配置信息。

      注意,AssetManager類的成員函數setLocalLocked來更新了成員變量mLocale的內容之后,同樣會調用另外一個成員函數updateResourceParamsLocked來更新資源表中的設備配置信息。

      • updateResourceParamsLocked()方法

        void AssetManager::updateResourceParamsLocked() const
        {
            ResTable* res = mResources;
            if (!res) {
                return;
            }
        
            if (mLocale) {
                mConfig->setBcp47Locale(mLocale);
            } else {
                mConfig->clearLocale();
            }
        
            res->setParameters(mConfig);
        }
        
        • AssetManager類的成員變量mResources指向的是一個ResTable對象,這個ResTable對象描述的就是一個資源索引表
        • AssetManager類的成員函數updateResourceParamsLocked首先是將成員變量mLocale所描述的國家、地區和語言信息更新到另外一個成員變量mConfig中去,接著再將成員變量mConfig所包含的設備配置信息設置到成員變量mResources所描述的一個資源索引表中去,這是通過調用成員變量mResources所指向的一個ResTable對象的成員函數setParameters來實現的。

      updateConfiguration執行完后就調用到assets.ensureStringBlocks()

      /*package*/ final void ensureStringBlocks() {
       if (mStringBlocks == null) {
           synchronized (this) {
               if (mStringBlocks == null) {
                   makeStringBlocks(sSystem.mStringBlocks);
               }
           }
       }
      }
      
      • mStringBlocks指向的是一個StringBlock數組,其中,每一個StringBlock對象都是用來描述一個字符串資源池。每一個資源表都包含有一個資源項值字符串資源池,AssetManager類的成員變量mStringBlocks就是用來保存所有的資源表中的資源項值字符串資源池的.

      • ensureStringBlocks首先檢查成員變量mStringBlocks的值是否等于null。如果等于null的話,那么就說明當前應用程序使用的資源表中的資源項值字符串資源池還沒有讀取出來,這時候就會調用另外一個成員函數makeStringBlocks來進行讀取。

        /*package*/ final void makeStringBlocks(StringBlock[] seed) {
           final int seedNum = (seed != null) ? seed.length : 0;
           final int num = getStringBlockCount();
           mStringBlocks = new StringBlock[num];
           if (localLOGV) Log.v(TAG, "Making string blocks for " + this
                   + ": " + num);
           for (int i=0; i<num; i++) {
               if (i < seedNum) {
                   mStringBlocks[i] = seed[i];
               } else {
                   mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
               }
           }
        }
        
        • getStringBlockCount獲取字符串資源池的數量。這里又通過jni進行了調用

          static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
          {
              AssetManager* am = assetManagerForJavaObject(env, clazz);
              if (am == NULL) {
                  return 0;
              }
              return am->getResources().getTableCount();
          }
          
          const ResTable& AssetManager::getResources(bool required) const
          {
              const ResTable* rt = getResTable(required);
              return *rt;
          }
          
        • 通過循環先將系統的資源添加到數組中,然后再講剩余的資源池添加到數組中。

    到這里整個Resources和AssetManager的創建已經完成。

    ?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內容