1 关键字

资源引用;资源文件;字符串;

2 问题描述

OH版本:3.1 Release

IDE版本 DevEco Studio 3.0 Beta3 Build Version: 3.0.0.900 SDK版本 8

问题现象: 以下说明均以ETS(基于TS扩展的声明式开发范式)开发模式为准

Text($r('app.string.string_hello'))

UI展示为 hello

Text($r('app.string.string_hello')  + " world")

UI展示为 [Object object]

预期展示为 hello world

3 问题原因

据文档说明,Text组件 (https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/arkui-ts/ts-basic-components-text.md)接口形式:Text(content?: string) 即参数为一个字符串类型,所以开发者可能会使用一些常规的拼接字符串方法,例如“+” ,并理所当然认为界面应该正常展示。 实际上从STK的接口描述来看

interface TextInterface {
  /**
   * Called when writing text.
   * @since 7
   */
  (content?: string | Resource): TextAttribute;
}

这说明 content参数除了是字符串外,还可以是一个Resource类型,$r('....')就是这样的Resource类型

通过ace-ets2bundle工具进行 jsbundle转换后可以看到 对于文中开始提到代码,会被转成这样的形式:

Text.create({ "id": 16777218, "type": 10003, params: [] })
Text($r('app.string.string_hello')  + " world")

则变成了这样

Text.create({ "id": 16777218, "type": 10003, params: [] } + " world");
{ "id": 16777218, "type": 10003, params: [] } + " world"

的结果就是[Object object],所以界面展示"[Object object]",问题是

Text.create({ "id": 16777218, "type": 10003, params: [] })

同理也应该展示[Object object],是如何变成实际的字符串的?

查看对应的前端开发框架到UI后端引擎和JS引擎的源码 (https://gitee.com/openharmony/ace_ace_engine

路径: frameworks\bridge\declarative_frontend\jsview\js_text.cpp

void JSText::Create(const JSCallbackInfo& info)
{
    std::string data;
    if (info.Length() > 0) {
        ParseJsString(info[0], data);
    }
    ...

表明create方法接受的参数首先需要解析为一个C++的字符串类型

ParseJsString定义在 frameworks\bridge\declarative_frontend\jsview\js_view_abstract.cpp

bool JSViewAbstract::ParseJsString(const JSRef<JSVal>& jsValue, std::string& result)
{
    //参数为字符串或Object类型
    if (!jsValue->IsString() && !jsValue->IsObject()) {
        LOGE("arg is not String or Object.");
        return false;
    }

    ...

    //Object类型解析如下

    JSRef<JSObject> jsObj = JSRef<JSObject>::Cast(jsValue);

    //获取type key 得到的值用来对比是否为 Resource 类型
    JSRef<JSVal> type = jsObj->GetProperty("type");
    if (!type->IsNumber()) {
        LOGW("type is not number");
        return false;
    }

    //获取id key 得到的值 resId会用来获取实际资源字符
    JSRef<JSVal> resId = jsObj->GetProperty("id");
    if (!resId->IsNumber()) {
        LOGW("resId is not number");
        return false;
    }   

    ....

    //获取params key
    JSRef<JSVal> args = jsObj->GetProperty("params");
    if (!args->IsArray()) {
        LOGW("args is not array");
        return false;
    }

    JSRef<JSArray> params = JSRef<JSArray>::Cast(args);

    /*
    如果是Resource类型 解析如下 
    ResourceType::STRING 为10003  和create参数内的 ```"type": 10003```指示的type 一致 
     */
    if (type->ToNumber<uint32_t>() == static_cast<uint32_t>(ResourceType::STRING)) {
        
        //根据 resId 获取资源实际对应的字符
        auto originStr = themeConstants->GetString(resId->ToNumber<uint32_t>());

        //ReplaceHolder?
        ReplaceHolder(originStr, params, 0);
        result = originStr;
    }
    
    ...
}

ReplaceHolder是起什么作用? 查看 ReplaceHolder函数: (frameworks\bridge\declarative_frontend\jsview\js_view_abstract.cpp)

void ReplaceHolder(std::string& originStr, JSRef<JSArray> params, int32_t containCount)
{
    auto size = static_cast<int32_t>(params->Length());

    //如果params为空
    if (containCount == size) {
        return;
    }
    ...


    /*
    RESOURCE_APP_STRING_PLACEHOLDER 是一个正则规则 
    const std::regex RESOURCE_APP_STRING_PLACEHOLDER(R"(\%((\d+)(\$)){0,1}([dsf]))", std::regex::icase);
    以下循环开始查找字符串内的 %d %s $f规则并根据 param里的字符进行替换
    */


    while (std::regex_search(start, end, matchs, RESOURCE_APP_STRING_PLACEHOLDER)) {
        std::string pos = matchs[2];
        std::string type = matchs[4];
        ...
        //单次查找字符替换逻辑

        originStr.replace(matchs[0].first - originStr.begin(), matchs[0].length(), replaceContentStr);
        start = originStr.begin() + matchs.prefix().length() + replaceContentStr.length();
        end = originStr.end();
        searchTime++;
    }
}

ReplaceHolder实现了字符串格式化输出功能,实现了三种格式替换: %s代表的是格式化字符串,%d格式化整数,%f格式化浮点数字

例如

{
    "name":"string_hello",
    "value":"hello%d"
}
​
$r('app.string.string_hello',10)  // hello10
​
{
    "name":"string_hello",
    "value":"hello%d%s"
}
$r('app.string.string_hello',10,"world") // hello10world
​

4 解决方案

如果要通过 $r() 引入应用资源里的字符串并实现定制化,采用字符模板(例如"hello%d%s")+ 替换参数的方式实现,而不是常规的字符串拼接。例如

// string_hello:
{
    "name":"string_hello",
    "value":"hello %s"
}
$r('app.string.string_hello',"world") // hello world
Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐