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阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容