Android资源管理框架-------之资源配置信息的更新(九)

        上一篇文章中我们讲了Android是如何选取最符合设备当前配置的资源的,在选取的时候要最多要进行match、isBetterThan、isMoreSpecificThan三次比较,特别是前两个步骤,它们都要设备当前的配置作为参数,也就是比较的依据。可是设备的配置信息不是一成不变的,当我们设置了语言,或者旋转了屏幕后,设备信息配置信息就会发生变化,这个变化必须得及时告知Android资源管理模块,否则比较的时候使用的还是老的设备配置信息,这样选择的资源很有可能就不是最优的了,但是这个变化是如何一步一步传递给Android资源管理模块的呢?我们今天挖一挖这个问题。

        从前一篇文章中我们可以知道,Android设备的配置项有很多,我们不可能一个一个地全部去分析,这里就选取相对复杂而且比较典型的屏幕旋转来稍作分析,先看时序图:
Android资源管理框架-------之资源配置信息的更新(九)_第1张图片
        我们看到Orientation变化时,涉及到Sensor、WMS、AMS以及应用进程的ActivityThreadSensorEventListenerImplSensorEventListener接口的实现类,这个接口用于获取Sensor的数据。在WMS构造的时候会初始化PhoneWindowManager对象,而PhoneWindowManager会根据设备的运行状况,比如是否Sensor是否打开、方向是否锁定、亮屏灭平等,通过WindowOrientationListener来向SensorManager注册和反注册SensorEventListener,也就是SensorEventListenerImpl。这样,当Sensor的数据过来后,就会调用onSensorChanged方法,然后就进入上图中的调用序列了。一如既往,system_server(也就是WMS和AMS所在的进程)和应用程序进程通信的接口还是IApplicationThread这个binder。方向发生变化的事件到达应用进程的ActivityThread后,scheduleConfigurationChanged方法还是走我们熟悉的H这个Handler,最终调到ResourcesManagerapplyConfigurationToResourcesLocked方法。ResourcesManager这个类的主要作用是统一管理一个应用的所有资源,准确地说是一个应用使用的所有Resources对象。我们知道一个Resources对象是可以加载多个资源包的,但是Android为了更加灵活,也是支持一个应用创建多个Resources对象,并对这些资源做统一管理的,比如大家可能经常用到这个方法Context.createPackageContext方法,它是可以创建并访问别的APK中的资源也就是Resources对象的,这个时候,它创建的Resources对象也会统一放入ResourcesManager类来管理。比如,下次我们再访问这个Resources中的资源,就不用再次从别的APK中创建了,直接从ResourcesManager中缓存中取就是了。当然,当设备的配置信息变化了以后,ResourcesManager也会一个一个地通知这些缓存的Resources的,具体就是调用它们的updateConfiguration方法,这个方法会对配置信息稍做修正,从Configuration中取出各个配置项,然后调用AssetManagersetConfiguration方法,这个方法是个native方法:

//framework/base/core/jni/android_util_AssetManager.cpp
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)
{
    //熟悉的老套路,从java层保存的地址直接拿到native层的AssetManager对象
    AssetManager* am = assetManagerForJavaObject(env, clazz);
    if (am == NULL) {
        return;
    }
    //把各个配置项的信息重新组合成ResTable_config对象
    ResTable_config config;
    memset(&config, 0, sizeof(config));

    const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;

    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;
    //交给native层AssetManager
    am->setConfiguration(config, locale8);

    if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);
}

        我们看到这个方法的主要工作就是把java层传递过来的配置信息组合为ResTable_config,然后交给native层的AssetManager来处理:

//frameworks/base/libs/androidfw/AssetManager.cpp
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();
    }
}

        其实setLocaleLocked方法也会调用到updateResourceParamsLocked方法:

//frameworks/base/libs/androidfw/AssetManager.cpp
void AssetManager::updateResourceParamsLocked() const
{
    ResTable* res = mResources;
    if (!res) {
        return;
    }

    if (mLocale) {
        mConfig->setBcp47Locale(mLocale);
    } else {
        mConfig->clearLocale();
    }
    //最终更新到ResTable对象
    res->setParameters(mConfig);
}

        看ResTable的实现:

//frameworks/base/libs/androidfw/ResourceTypes.cpp
void ResTable::setParameters(const ResTable_config* params)
{
    mLock.lock();
    TABLE_GETENTRY(ALOGI("Setting parameters: %s\n", params->toString().string()));
    mParams = *params;
    for (size_t i=0; i<mPackageGroups.size(); i++) {
        TABLE_NOISY(ALOGI("CLEARING BAGS FOR GROUP %d!", i));
        mPackageGroups[i]->clearBagCache();
    }
    mLock.unlock();
}

        对于每一个PackageGroup,它所缓存的Bag资源,在这个时候已经没有意义了,因为设备的配置信息已经改变,要根据新的配置重新选取。到这里,资源配置信息的改变,就一步一步通知到ResTable了,此后再选取最优资源的时候,所依据的配置标准就是新的config了,从ResourcesManagerResTable的时序图如下:
Android资源管理框架-------之资源配置信息的更新(九)_第2张图片
        本篇的内容比较少,本想打算把AssetManager2也放进来的说一说呢,但是我发现放进来的话,就太多了,后面单开一篇吧,好好说一说AssetManager2

你可能感兴趣的:(#,AssetManager)