积分582 / 贡献0

提问0答案被采纳0文章52

[经验分享] Launcher应用安装问题分析报告 原创 精华

Laval社区小助手 显示全部楼层 发表于 2024-4-1 10:14:52

1 关键字

Launcher;Storage

2 问题描述

问题现象:安装应用到达20个以上后,重启设备,Launcher页面没有任何应用图标

运行环境:硬件 dayu200,软件:3.1release

测试步骤:

  1. 使用hdc将测试安装包安装到设备上,安装数量在20个以上,尽可能稍多一些
  2. 重启设备
  3. Launcher桌面显示异常,不显示任何应用图标

3 问题原因

3.1 正常机制

  • Launcher的桌面应用布局信息是通过Storage存储在文件中。
  • Launcher在系统安装应用后会更新桌面布局配置,调用putSync将当前布局写入内存中,然后通过flushSync持久化到文件中。
  • 桌面布局配置持久化后,设备重启,Launcher页面正常加载。

3.2 异常机制

  • 当桌面应用安装过多,桌面布局配置信息超过8192字节时,底层通过定长数组接收参数,导致数组越界未定义,从而无法正确获取桌面布局配置信息,属性为空,从而将内存中属性更新为空了。
  • 持久化过程中再将内存中的属性更新到文件中,从而导致文件信息也被更新为空。
  • 重启设备,重新加载文件,桌面布局配置为空,从而无法显示任何应用图标。

Storage官方文档说明:key的最大长度限制,需小于80字节。value的最大长度限制,需小于8192字节。

4 解决方案

  • 应用层面,可通过对超长字段进行拆分存储,规避该问题
  • 存储方式,可通过使用关系型数据库@ohos.data.rdb的rdbStore或分布式数据管理@ohos.data.distributedData的kvStore来进行数据存储
  • 底层实现,系统目前文件存储的最大长度限制为8192字节,可通过修改底层存储逻辑,修改限定长度或自动扩容实现

5 定位过程

该问题为底层实现与特殊场景不兼容导致的运行异常:

  • 安装应用20+复现相关问题。

  • 关注系统桌面布局配置LauncherPreference文件中的DesktopApplicationInfo信息更新情况。 文件路径:/data/app/el2/100/database/com.ohos.launcher/pad/LauncherPreference 文件内容:

    <?xml version="1.0" encoding="UTF-8"?>
    <preferences version="1.0">
      <string key="DesktopApplicationInfo">[{"appName":"$string:MainAbility_label","isSystemApp":true,"isUninstallAble":false,"appIconId":16777219,"appLabelId":16777217,"bundleName":"com.example.AircraftWar","abilityName":"com.example.AircraftWar.MainAbility","type":0,"area":[1,1]},{"appName":"$string:MainAbility_label","isSystemApp":true,"isUninstallAble":false,"appIconId":16777219,"appLabelId":16777217,"bundleName":"com.example.Billiards","abilityName":"com.example.Billiards.MainAbility","type":0,"area":[1,1]},{"appName":"$string:app_name","isSystemApp":true,"isUninstallAble":false,"appIconId":16777229,"appLabelId":16777219,"bundleName":"com.example.Browser","abilityName":"MainAbility","type":0,"area":[1,1]},{"appName":"$string:app_name","isSystemApp":false,"isUninstallAble":true,"appIconId":16777219,"appLabelId":16777216,"bundleName":"com.example.baseanimation","abilityName":"com.example.baseanimation.MainAbility","type":0,"area":[1,1]},{"appName":"$string:app_name","isSystemApp":true,"isUninstallAble":false,"appIconId":16777218,"appLabelId":16777216,"bundleName":"com.example.distributedcalc","abilityName":"com.example.distributedcalc.default","type":0,"area":[1,1]},{"appName":"$string:entry_MainAbility","isSystemApp":true,"isUninstallAble":false,"appIconId":16777218,"appLabelId":16777217,"bundleName":"com.example.shopping","abilityName":"com.example.shopping.MainAbility","type":0,"area":[1,1]},{"appName":"$string:app_name","isSystemApp":false,"isUninstallAble":true,"appIconId":16777217,"appLabelId":16777219,"bundleName":"com.huawei.himovie","abilityName":"MainAbility","type":0,"area":[1,1]},{"appName":"$string:entry_Music","isSystemApp":true,"isUninstallAble":false,"appIconId":16777311,"appLabelId":16777219,"bundleName":"com.huawei.himusicdemo","abilityName":"com.huawei.himusicdemo.Music","type":0,"area":[1,1]},{"appName":"$string:app_name","isSystemApp":true,"isUninstallAble":false,"appIconId":150995030,"appLabelId":150994944,"bundleName":"com.ohos.camera","abilityName":"com.ohos.camera.MainAbility","type":0,"area":[1,1]},{"appName":"$string:app_name","isSystemApp":true,"isUninstallAble":false,"appIconId":16777431,"appLabelId":16777225,"bundleName":"com.ohos.contacts","abilityName":"com.ohos.contacts.MainAbility","type":0,"area":[1,1]},{"appName":"$string:messages","isSystemApp":true,"isUninstallAble":false,"appIconId":16777565,"appLabelId":16777321,"bundleName":"com.ohos.mms","abilityName":"com.ohos.mms.MainAbility","type":0,"area":[1,1]},{"appName":"$string:entry_MainAbility","isSystemApp":true,"isUninstallAble":false,"appIconId":134217900,"appLabelId":134217748,"bundleName":"com.ohos.note","abilityName":"com.ohos.note.MainAbility","type":0,"area":[1,1]},{"appName":"$string:app_name","isSystemApp":true,"isUninstallAble":false,"appIconId":16777834,"appLabelId":16777216,"bundleName":"com.ohos.photos","abilityName":"com.ohos.photos.MainAbility","type":0,"area":[1,1]},{"appName":"$string:entry_MainAbility","isSystemApp":true,"isUninstallAble":false,"appIconId":50332153,"appLabelId":50331728,"bundleName":"com.ohos.settings","abilityName":"com.ohos.settings.MainAbility","type":0,"area":[1,1]},{"appName":"$string:app_name","isSystemApp":true,"isUninstallAble":false,"appIconId":16777218,"appLabelId":16777216,"bundleName":"ohos.samples.airquality","abilityName":"ohos.samples.airquality.default","type":0,"area":[1,1]},{"appName":"$string:app_name","isSystemApp":true,"isUninstallAble":false,"appIconId":16777218,"appLabelId":16777216,"bundleName":"ohos.samples.clock","abilityName":"ohos.samples.clock.default","type":0,"area":[1,1]},{"appName":"$string:entry_MainAbility","isSystemApp":true,"isUninstallAble":false,"appIconId":16777219,"appLabelId":16777217,"bundleName":"ohos.samples.webdemo","abilityName":"ohos.samples.webdemo.MainAbility","type":0,"area":[1,1]}]</string>
      <string key="DesktopModeConfig">{"appStartPageType":"Grid","gridConfig":0,"deviceType":"pad"}</string>
      <string key="GridLayoutInfo">{"layoutDescription":{"pageCount":1,"row":5,"column":11},"layoutInfo":[{"bundleName":"com.example.AircraftWar","type":0,"area":[1,1],"page":0,"column":0,"row":0},{"bundleName":"com.example.Billiards","type":0,"area":[1,1],"page":0,"column":1,"row":0},{"bundleName":"com.example.Browser","type":0,"area":[1,1],"page":0,"column":2,"row":0},{"bundleName":"com.example.baseanimation","type":0,"area":[1,1],"page":0,"column":3,"row":0},{"bundleName":"com.example.distributedcalc","type":0,"area":[1,1],"page":0,"column":4,"row":0},{"bundleName":"com.example.shopping","type":0,"area":[1,1],"page":0,"column":5,"row":0},{"bundleName":"com.huawei.himovie","type":0,"area":[1,1],"page":0,"column":6,"row":0},{"bundleName":"com.huawei.himusicdemo","type":0,"area":[1,1],"page":0,"column":7,"row":0},{"bundleName":"com.ohos.camera","type":0,"area":[1,1],"page":0,"column":8,"row":0},{"bundleName":"com.ohos.contacts","type":0,"area":[1,1],"page":0,"column":9,"row":0},{"bundleName":"com.ohos.mms","type":0,"area":[1,1],"page":0,"column":10,"row":0},{"bundleName":"com.ohos.note","type":0,"area":[1,1],"page":0,"column":0,"row":1},{"bundleName":"com.ohos.photos","type":0,"area":[1,1],"page":0,"column":1,"row":1},{"bundleName":"com.ohos.settings","type":0,"area":[1,1],"page":0,"column":2,"row":1},{"bundleName":"ohos.samples.airquality","type":0,"area":[1,1],"page":0,"column":3,"row":1},{"bundleName":"ohos.samples.clock","type":0,"area":[1,1],"page":0,"column":4,"row":1},{"bundleName":"ohos.samples.webdemo","type":0,"area":[1,1],"page":0,"column":5,"row":1}]}</string>
      <string key="SmartDockLayoutInfo">[{"itemType":2,"editable":false,"bundleName":"com.ohos.launcher","abilityName":"com.ohos.launcher.appcenter.MainAbility","appIconId":184549428,"appLabelId":184549400,"appName":"全部应用"},{"itemType":2,"editable":false,"bundleName":"com.ohos.launcher","abilityName":"com.ohos.launcher.recents.MainAbility","appIconId":184549429,"appLabelId":184549401,"appName":"最近任务"},{"itemType":0,"editable":true,"appName":"图库","bundleName":"com.ohos.photos","abilityName":"com.ohos.photos.MainAbility","appIconId":16777834,"appLabelId":16777216},{"itemType":0,"editable":false,"appName":"设置","bundleName":"com.ohos.settings","abilityName":"com.ohos.settings.MainAbility","appIconId":50332153,"appLabelId":50331728}]</string>
    </preferences>
  • 关注属性写入内存putSync与内存信息持久化到文件flushSync调用前后的属性变量。

  • 通过日志定位到问题原因:putSync调用导致的异常。

  • 分析api底层实现:底层实现调用napi_storage.cpp的StorageProxy::SetValueSync。

  • SetValueSync具体逻辑:声明定长char数组用来接收key(MAX_KEY_LENGTH = 80),获取value类型,当类型为string时,声明定长char数组用来接收value(MAX_VALUE_LENGTH = 8 * 1024),然后调用preferences_impl.cpp的PutString。

    napi_value StorageProxy::SetValueSync(napi_env env, napi_callback_info info)
    {
        napi_value thiz = nullptr;
        size_t argc = 2;
        napi_value args[2] = { 0 };
    
        LOG_DEBUG("SETVALUE");
        NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &thiz, nullptr));
        NAPI_ASSERT(env, argc == 2, "Wrong number of arguments");
        // get value type
        napi_valuetype valueType = napi_undefined;
        NAPI_CALL(env, napi_typeof(env, args[0], &valueType));
        NAPI_ASSERT(env, valueType == napi_string, "type mismatch for key");
    
        // get input key
        char key[MAX_KEY_LENGTH] = { 0 };
        size_t out = 0;
        NAPI_CALL(env, napi_get_value_string_utf8(env, args[0], key, MAX_KEY_LENGTH, &out));
    
        StorageProxy *obj = nullptr;
        NAPI_CALL(env, napi_unwrap(env, thiz, reinterpret_cast<void **>(&obj)));
        NAPI_ASSERT(env, (obj != nullptr && obj->value_ != nullptr), "unwrap null native pointer");
    
        NAPI_CALL(env, napi_typeof(env, args[1], &valueType));
        if (valueType == napi_number) {
            double value = 0.0;
            NAPI_CALL(env, napi_get_value_double(env, args[1], &value));
            int result = obj->value_->PutDouble(key, (double)value);
            NAPI_ASSERT(env, result == E_OK, "call PutDouble failed");
        } else if (valueType == napi_string) {
            char *value = new char[MAX_VALUE_LENGTH];
    
            napi_status status = napi_get_value_string_utf8(env, args[1], value, MAX_VALUE_LENGTH, &out);
            if (status != napi_ok) {
                LOG_DEBUG("napi_get_value_string_utf8 failed");
                LOG_DEBUG(value);
            } else {
                // get value
                int result = obj->value_->PutString(key, value);
            }        
            delete[] value;
            NAPI_ASSERT(env, result == E_OK, "call PutString failed");
        } else if (valueType == napi_boolean) {
            bool value = false;
            NAPI_CALL(env, napi_get_value_bool(env, args[1], &value));
            // get value
            int result = obj->value_->PutBool(key, value);
            NAPI_ASSERT(env, result == E_OK, "call PutBool failed");
        } else {
            NAPI_ASSERT(env, false, "Wrong second parameter type");
        }
        return nullptr;
    }

    注:当属性长度超长后会导致数组越界未定义,从而无法正常获取属性值,导致属性值为空,从而导致内存中存放属性有误

  • PutString具体逻辑:调用CheckStringValue进行value值校验(校验长度),校验通过调用PutPreferencesValue。

    int PreferencesImpl::PutString(const std::string &key, const std::string &value)
    {
        int errCode = CheckKey(key);
        if (errCode != E_OK) {
            return errCode;
        }
        errCode = CheckStringValue(value);
        if (errCode != E_OK) {
            return errCode;
        }
        PutPreferencesValue(key, PreferencesValue(value));
        return E_OK;
    }
    int PreferencesImpl::CheckKey(const std::string &key)
    {
        if (key.empty()) {
            LOG_ERROR("The key string is null or empty.");
            return E_KEY_EMPTY;
        }
        if (Preferences::MAX_KEY_LENGTH < key.length()) {
            LOG_ERROR("The key string length should shorter than 80.");
            return E_KEY_EXCEED_MAX_LENGTH;
        }
        return E_OK;
    }
    int PreferencesImpl::CheckStringValue(const std::string &value)
    {
        if (Preferences::MAX_VALUE_LENGTH < value.length()) {
            LOG_ERROR("The value string length should shorter than 8 * 1024.");
            return E_VALUE_EXCEED_MAX_LENGTH;
        }
        return E_OK;
    }
  • PutPreferencesValue具体逻辑:AwaitLoadFile获取资源锁,调用insert_or_assign存放属性到内存map中,调用push_back记录已修改属性的key值到内存map中。

    void PreferencesImpl::PutPreferencesValue(const std::string &key, const PreferencesValue &value)
    {
        AwaitLoadFile();
    
        std::lock_guard<std::mutex> lock(mutex_);
    
        auto iter = map_.find(key);
        if (iter != map_.end()) {
            PreferencesValue &val = iter->second;
            if (val == value) {
                return;
            }
        }
    
        map_.insert_or_assign(key, value);
        modifiedKeys_.push_back(key);
    }
  • 属性写入内存后,再由flushSync来进行数据的持久化操作,将内存数据写入到文件中。

6 知识分享

  • OpenHarmony文件存储@ohos.data.storage目前的长度限制:key的最大长度限制,需小于80字节,value的最大长度限制,需小于8192字节。
  • 数据库存储目前逻辑上是没有长度限制的,长度限制需参考数据库属性配置,sqlite中text存储可变长度的非Unicode数据,最大长度为2^31-1(2,147,483,647)个字符。

©著作权归作者所有,转载或内容合作请联系作者

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部