Trime同文输入法JNI加载过程

news2024/12/23 10:48:59

Trime同文输入法JNI加载过程

  • JNI初始化顺序
    • 第一步、加载librime_jni.so库
    • 第二步、自动注册机制
    • 第三步、正式加载librime_jni.so库
    • 插入一个话题、简化打印记录
    • 第四步、执行Rime.java中的init()方法
      • LoadModules()
      • LoadModule()
      • rime_core_initialize()调用顺序
      • Class不是class关键字,仅仅是个结构体名称
      • check(boolean full_check)
        • start_maintenance()
        • `is_maintenance_mode()`,`join_maintenance_thread()`
          • ConfigFileUpdate::Run()
          • WorkspaceUpdate::Run()
      • create_session()
      • initSchema()
    • 第五步
    • 问题
    • 发现
    • 类Deployer的理解
    • 创建方案对象
        • 有个问题
      • 各个类的包含关系
    • 第六步、加载script_translator
    • 第七步 deployTheme()
    • 第八步、编译词典
    • 插入一个话题,格式化makelist.txt

JNI初始化顺序

  在追溯中文模式下按下w时发生的调用,跟踪到ProcessKey()函数中,出现processors_变量弄不明白,跟踪processors_变量发现在InitializeComponents()函数中被初始化。越看越迷糊,很多东西得从头开始看。

第一步、加载librime_jni.so库

  librime_jni.so放在目录..\trime-develop\app\src\main\jniLibs\x86_64下,在loadLibrary()函数中只需要写rime_jni

static {
        System.out.println("获取系统的属性:====================================");
        String lib = System.getProperty("java.library.path");

        // 加载C++库
        System.loadLibrary("rime_jni");
}

第二步、自动注册机制

  gcc对c语言做了很多扩展,使得c语言的表现力得到了很大的增强,主要介绍一下constructor扩展,这个扩展和C++的构造函数很像,它会在main函数之前由程序加载器自动调用。利用constructor属性,我们可以定义一些宏来实现模块的自动注册机制。也就是说我们用宏自动构造注册函数,然后把注册函数赋予constructor属性,这样我们在添加新的模块的时候就不需要显示的调用注册函数来,只需要在模块文件内加上一个宏调用即可。
  因为有下面这段代码,所以等函数最先执行

#if defined(__GNUC__)
#define RIME_MODULE_INITIALIZER(f)                        \
        static void f(void) __attribute__((constructor)); \
        static void f(void)
#elif defined(_MSC_VER)
#pragma section(".CRT$XCU", read)
#define RIME_MODULE_INITIALIZER(f)                                       \
        static void __cdecl f(void);                                     \
        __declspec(allocate(".CRT$XCU")) void(__cdecl * f##_)(void) = f; \
        static void __cdecl f(void)
#endif

  预编译时,宏RIME_MODULE_INITIALIZER会被替换成下面两个函数,其中##name会被替换成 core, gears, charcode等词块,成为rime_register_module_core(void), rime_register_module_gears(void), rime_register_module_charcode(void)等函数。

static void rime_register_module_##name(void) __attribute__((constructor));
static void rime_register_module_##name(void)
{                                                             
        static RimeModule module = {0};                       
        if (!module.data_size)                                
        {                                                     
                RIME_STRUCT_INIT(RimeModule, module);         
                module.module_name = #name;                   
                module.initialize = rime_##name##_initialize; 
                module.finalize = rime_##name##_finalize;     
        }                                                     
        rime_get_api()->register_module(&module);             
}

下面的函数最先执行,即先于main()函数执行

core_module.cc rime_register_module_core(void)
setup.cc rime_register_module_default()
setup.cc rime_register_module_deployer()
dict_module.cc rime_register_module_dict()
gears_module.cc rime_register_module_gears(void)
levers_module.cc rime_register_module_levers()

rime_api.h里要注意!宏嵌套宏,宏又嵌套宏。在setup.cc中有两个宏RIME_REGISTER_MODULE_GROUP

RIME_REGISTER_MODULE_GROUP(default, "core", "dict", "gears")
RIME_REGISTER_MODULE_GROUP(deployer, "core", "dict", "levers")

该宏包含:

RIME_REGISTER_MODULE_GROUP
RIME_MODULE_LIST
RIME_REGISTER_MODULE
RIME_MODULE_INITIALIZER
RIME_STRUCT_INIT

请添加图片描述
预编译后RIME_REGISTER_MODULE_GROUP(default, "core", "dict", "gears")被替换成:

// RIME_MODULE_LIST替换成
static const char *rime_default_module_group[] = {"core", "dict", "gears"};

static void rime_default_initialize()
{
        rime::LoadModules(rime_default_module_group);
}
static void rime_default_finalize()
{
}

// 以下是RIME_REGISTER_MODULE(name)内容
void rime_require_module_default() {}

static void rime_register_module_default(void) __attribute__((constructor));
static void rime_register_module_default(void)
{
        static RimeModule module = {0};
        if (!module.data_size)
        {
                module.data_size = sizeof(RimeModule) - sizeof(module.data_size);
                module.module_name = default;
                module.initialize = rime_default_initialize;
                module.finalize = rime_default_finalize;
        }
        
        // 在rime_api.cc中有s_api.register_module = &RimeRegisterModule;   
		// rime_get_api()获得s_api,register_module指向函数 RimeRegisterModule(RimeModule *module)
		// 所以此处调用的是RimeRegisterModule()
        rime_get_api()->register_module(&module);
}

函数执行顺序:

FF:   115行 core_module.cc rime_register_module_core()   RIME_REGISTER_MODULE() core
FF:   34行 setup.cc rime_register_module_default()   RIME_REGISTER_MODULE() default
FF:   38行 setup.cc rime_register_module_deployer()   RIME_REGISTER_MODULE() deployer
FF:   70行 dict_module.cc rime_register_module_dict()   RIME_REGISTER_MODULE() dict
FF:   118行 gears_module.cc rime_register_module_gears()   RIME_REGISTER_MODULE() gears

第三步、正式加载librime_jni.so库

  Java JNI有两种方法,一种是通过javah,获取一组带签名函数,然后实现这些函数。 这种方法很常用,也是官方推荐的方法。
  还有一种就是JNI_OnLoad方法。 当Android的VM(Virtual Machine)执行到C组件(即*so档)里的System.loadLibrary()函数时, 首先会去执行C组件里的JNI_OnLoad()函数
它的用途有二:

  • 告诉VM此C组件使用那一个JNI版本。 如果你的*.so档没有提供JNI_OnLoad()函数,VM会默认该*.so档是使用最老的JNI 1.1版本。由于新版的JNI做了许多扩充,如果需要使用JNI的新版功能,例如JNI 1.4的java.nio.ByteBuffer,就必须藉由JNI_OnLoad()函数来告知VM。
  • 由于VM执行到System.loadLibrary()函数时,就会立即先呼叫JNI_OnLoad()
    所以C组件的开发者可以藉由JNI_OnLoad()来进行C组件内的初期值之设定(Initialization) 。

其实Android中的so文件就像是Windows下的DLL一样,JNI_OnLoadJNI_OnUnLoad函数就像是DLL中的PROCESS ATTATCHDEATTATCH的过程一样,可以同样做一些初始化和反初始化的动作。

jint JNI_OnLoad(JavaVM *vm, void *reserved)
{
        JNIEnv *env;
        if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK)
        {
                return -1;
        }
        registerNativeMethods(env, CLASSNAME, sMethods, NELEMS(sMethods));
        return JNI_VERSION_1_6;
}

插入一个话题、简化打印记录

  logcat输出时特别讨厌输出下面这样的内容,前面没用的信息太多了。

2022-07-15 22:42:43.758 26700-26700/com.osfans.trime D/KeyboardView: I am in  onBufferDraw() 3
2022-07-15 22:42:43.758 26700-26700/com.osfans.trime D/KeyboardView: I am in  onBufferDraw() end
2022-07-15 22:42:43.759 26700-26700/com.osfans.trime D/KeyboardView: I am in  invalidateKey() 1 end
2022-07-15 22:42:43.759 26700-26700/com.osfans.trime D/KeyboardView: I am in  onModifiedTouchEvent() end. mLastX = 106, mLastY = 472
2022-07-15 22:42:43.759 26700-26700/com.osfans.trime D/KeyboardView: I am in  performClick()
2022-07-15 22:42:43.770 26700-26700/com.osfans.trime D/Trime: I am in onComputeInsets()
2022-07-15 22:42:43.770 26700-26700/com.osfans.trime D/KeyboardView: I am in  onDraw()

在找解决方案时发现在cmd中配置一下adb logcat -s就可以在logcatrun窗口输出精简的内容。

PS D:\project\andriod\trime-develop\app\src\main\jniLibs\x86_64> cmd
Microsoft Windows [版本 10.0.19044.1826]
(c) Microsoft Corporation。保留所有权利。

D:\project\andriod\trime-develop\app\src\main\jniLibs\x86_64>adb logcat -c

D:\project\andriod\trime-develop\app\src\main\jniLibs\x86_64>adb logcat -s

如图显示:
在这里插入图片描述

第四步、执行Rime.java中的init()方法

  接着执行Rime.java中的private static void init(boolean full_check)函数。init()由Config.java 中的 prepareRime()调用。
调用顺序:

Config.java Config() -> prepareRime() -> Rime.java get() -> Rime() -> init()

在这里插入图片描述

LoadModules()

  setup.cc中的LoadModules()函数需要注意,形参module_names的值就是kDefaultModules的值。宏RIME_EXTRA_MODULES的值为lua

// RIME_EXTRA_MODULES 来源于app\src\main\jni\librime\CMakeLists.txt中的add_definitions(-DRIME_EXTRA_MODULES=${list})
namespace rime
{
#define Q(x) #x
        RIME_API RIME_MODULE_LIST(kDefaultModules, "default" RIME_EXTRA_MODULES);
  • 功能:
    通过字符串查找要调用的模块函数
  • 描述:
    • 第一次被RimeInitialize()调用,由"defualt"关键字找到default模块,将default模块传递给LoadModule()函数。再由LoadModule()函数通过module->initialize()调用rime_default_initialize()函数。
    • 第二次被rime_default_initialize()调用,rime_default_initialize()函数定义在宏RIME_REGISTER_MODULE_GROUP中,在rime_default_initialize()函数中将数组rime_default_module_group[]传递给了此函数。rime_default_module_group[]数组的第一个元素是"core",根据"core"找到core模块,传递给LoadModule()函数。再由LoadModule()函数通过module->initialize()调用rime_core_initialize()函数。
  • 总结:
    调用关系:RimeInitialize()->LoadModules()->LoadModule()->rime_default_initialize()->LoadModules()->.....
RIME_API void LoadModules(const char *module_names[])
{
        //  module_names: default
        ModuleManager &mm(ModuleManager::instance());
        for (const char **m = module_names; *m; ++m)
        {
                // 为什么能找到default,因为在RimeRegisterModule()已经调用了Register()
                if (RimeModule *module = mm.Find(*m)) 
                {
                        mm.LoadModule(module); // 调用module.h
                }
        }
}

  mm.Find(*m)之所以能找到default找不到lua是因为rime_api.cc RimeRegisterModule()中调用module.cc Register()注册了default模块。而RimeRegisterModule()RIME_REGISTER_CUSTOM_MODULE(name)中定义的函数rime_get_api()->register_module(&module)调用。因为s_api.register_module = &RimeRegisterModule;调用s_api.register_module()就是调用RimeRegisterModule()。

// 此函数被rime_api.cc中的RimeRegisterModule()调用
void ModuleManager::Register(const string &name, RimeModule *module)
{
        map_[name] = module;
}

LoadModule()

  module.cc中的LoadModule()函数由setup.cc中的LoadModules()函数调用。

  • 功能:
    通过字符串查找要调用的模块函数
  • 描述:
    LoadModule()setup.cc中的LoadModules()函数调用。
    • 一:由于module->initialize在本库被加载时由宏RIME_REGISTER_MODULE()初始化了。在宏RIME_REGISTER_MODULE()中将default模块的initialize = rime_default_initialize()因此调用module->initialize()时其实是在调用rime_default_initialize()函数。rime_default_initialize()函数定义在宏RIME_REGISTER_MODULE_GROUP()中,先于main()函数执行。
    • 二:rime_default_initialize()函数调用LoadModules()函数,在LoadModules()函数中,根据rime_default_module_group[] = {"core", "dict", "gears"}中的元素依次找到对应的模块,传递到LoadModule()再执行rime_core_initialize()函数。"core"对应的模块在rime_register_module_core()函数中被初始化。rime_register_module_core()函数定义在宏RIME_MODULE_INITIALIZER()中先于main()执行,宏RIME_MODULE_INITIALIZER()包含在宏RIME_REGISTER_MODULE()中,宏RIME_REGISTER_MODULE(core)core_module.cc中,加载.so文件时是第一个执行的,先于main()函数执行。
void ModuleManager::LoadModule(RimeModule *module)
{
        if (!module || loaded_.find(module) != loaded_.end())
        {
                return;
        }
        loaded_.insert(module);
        if (module->initialize != NULL)
        {
                // RIME_REGISTER_MODULE
                module->initialize();  
        }
        else
        {
                LOG(WARNING) << "missing initialize() function in module: " << module;
        }
}

  调用随着LoadModules()中module->initialize()被循环调用会依次调用rime_core_initialize()rime_dict_initialize()rime_gears_initialize()进行各个部件的注册。

rime_core_initialize()调用顺序

核心部件初始化
setup.cc LoadModules() -> LoadModule() -> setup.cc rime_default_initialize() -> LoadModules() -> LoadModule() -> core_module.cc rime_core_initialize()
在这里插入图片描述

Class不是class关键字,仅仅是个结构体名称

全局搜索": public Class<"就能看到所有继承结构体Class的类,非常重要。

14 个结果 - 14 文件

librime\src\rime\deployer.h:
  32:         class DeploymentTask : public Class<DeploymentTask, TaskInitializer>

librime\src\rime\filter.h:
  37:         class Filter : public Class<Filter, const Ticket &>

librime\src\rime\formatter.h:
  20:         class Formatter : public Class<Formatter, const Ticket &>

librime\src\rime\processor.h:
  50:         class Processor : public Class<Processor, const Ticket &>

librime\src\rime\segmentor.h:
  38:         class Segmentor : public Class<Segmentor, const Ticket &>

librime\src\rime\translator.h:
  59:         class Translator : public Class<Translator, const Ticket &>

librime\src\rime\config\config_component.h:
  25:         class Config : public Class<Config, const string &>, public ConfigItemRef

librime\src\rime\dict\corrector.h:
  64:         class Corrector : public Class<Corrector, const Ticket &>

librime\src\rime\dict\db.h:
  37:         class Db : public Class<Db, const string &>

librime\src\rime\dict\dictionary.h:
  68:         class Dictionary : public Class<Dictionary, const Ticket &>

librime\src\rime\dict\reverse_lookup_dictionary.h:
  66:         class ReverseLookupDictionary : public Class<ReverseLookupDictionary, const Ticket &>

librime\src\rime\dict\user_dictionary.h:
  57:         class UserDictionary : public Class<UserDictionary, const Ticket &>

librime\src\rime\gear\grammar.h:
  12:         class Grammar : public Class<Grammar, Config *>

librime\test\component_test.cc:
  13: class Greeting : public Class<Greeting, const string &>

check(boolean full_check)

check()中调用3个函数,分别是start_maintenance()is_maintenance_mode()join_maintenance_thread()。下图只画了start_maintenance()

start_maintenance()

在这里插入图片描述
bool CleanOldLogFiles::Run(Deployer *deployer) 顾名思义似乎是清理日志文件的
bool InstallationUpdate::Run(Deployer *deployer) 顾名思义,安装目录更新
bool DetectModifications::Run(Deployer *deployer) 顾名思义,检测是否有修改,是否需要更新。
resource.cc ResolvePath()的作用是加上前后缀,user变成user.yamldeployer.cc StartWork()搞不清这个函数是干什么用的

is_maintenance_mode()join_maintenance_thread()

在这里插入图片描述
很多细节函数没画,这个软件太难用了

ConfigFileUpdate::Run()

ConfigFileUpdate::Run()函数很复杂。WorkspaceUpdate::Run()还没完,后面还有一张。
在这里插入图片描述

WorkspaceUpdate::Run()

94行 config_cow_ref.h Cow() start. key = menu
I/: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21行 config_cow_ref.h ConfigCowRef() 1.
I/: . . . . . . . . . . . . . . . . . . . . . . . . . 74行 config_compiler_impl.h IncludeReference()
太长了,很多函数没画出来,比如上面这些。这张图将上面的包含进来了。
在这里插入图片描述

在这里插入图片描述

create_session()

图中config_component.cc 中的GetConfigData()函数,会调用一系列函数前面的图中已有这张图没画出来。
在这里插入图片描述

initSchema()

initSchema() -> get_schema_list() -> RimeGetSchemaList()。
执行步骤:

  1. 在RimeGetSchemaList()中首先创建default_schema对象,RimeGetSchemaList() -> Schema() -> FetchUsefulConfigItems()。读取default.yaml文件,因为此前已经读取过了所以此处不再读取。FetchUsefulConfigItems()直接从集合root中读取一些default.yaml中的数据项。
  2. 有没有发现,每次调用config_component.cc GetConfigData()函数的时候都要先执行component.h Require(const string &name),需要先根据name获取到对应的对象。以Config::Require(“schema”)->Create(schema_id))为例,通过"schema"获取到的对象早就在core_module.cc的rime_core_initialize()函数中创建好了。
// 不同于上下两个,此构造函数没有参数
// 创建一个ConfigComponent对象,其中包含一个成员变量ConfigLoader和一个成员函数LoadConfig()
auto config_loader = new ConfigComponent<ConfigLoader, DeployedConfigResourceProvider>;

r.Register("config", config_loader);
r.Register("schema", new SchemaComponent(config_loader));

  并且在构造函数SchemaComponent()中将创建的ConfigComponent对象赋给了成员变量Config::Component *config_component_,在创建ConfigComponent对象时同时创建父类对象ConfigComponentBase。父类ConfigComponentBase中有成员变量cache_,所以子类ConfigComponent中也有成员变量cache_同时ConfigComponent 继承自 ConfigComponentBase 继承自 Config::Component 继承自 ComponentBase,Config 继承自 ConfigItemRef。父类ConfigItemRef中有成员变量an<ConfigData> data_,所以ConfigComponent类中也有成员变量data_

// 那么cache_在哪里初始化的??在GetConfigData()初始化。
// 从default.yaml和luna_pinyin.schema.yaml中读取到的内容,根据string键值放到此cache_中。
// root在
// ConfigData::Traverse()被读取。
map<string, weak<ConfigData>> cache_;

  那么通过Config::Require(“schema”)获取到的对象中也包含成员变量cache_cache_中存储的是ConfigData对象,ConfigData对象中又包含成员变量an<ConfigItem> root。而GetConfigData()的功能是根据file_name的值从集合(map)类型的变量cache_中获取ConfigData类型的对象,如果cache_中有该对象,则直接返回该对象。如果没有则重新加载并解析file_name.yaml后,重新创建一个ConfigData类型对象放入cache_中,并返回该对象。

an<ConfigData> ConfigComponentBase::GetConfigData(const string &file_name)
{
        auto config_id = resource_resolver_->ToResourceId(file_name);
        weak<ConfigData> &wp(cache_[config_id]); 

        if (wp.expired()) // 如果已经加载了一次default.yaml,第二次不会进入
        {
                auto data = LoadConfig(config_id);
                wp = data;

                return data;
        }
        return wp.lock();
}

所以在schema.cc Schema()中:

config_.reset(boost::starts_with(schema_id_, L".") ? 
	Config::Require("config")->Create(schema_id.substr(1)) :
	 	Config::Require("schema")->Create(schema_id));

  获取到的是根据"luna_pinyin"得到的ConfigData对象中的成员变量root中保存的是解析luna_pinyin.schema.yaml后的内容。在Schema::FetchUsefulConfigItems()函数中通过config_->GetString(“schema/name”, &schema_name_)进而调用ConfigData::Traverse()读取"schema/name"。所以Traverse()函数从root中可以获取luna_pinyin.schema.yaml中的内容。

schema:
  schema_id: luna_pinyin
  name: 朙月拼音
  version: "0.14.test"
  1. 获取方案列表,"schema_list"数据项在default.yaml文件中。
schema_list:
  - schema: luna_pinyin
  - schema: cangjie5

在这里插入图片描述

第五步

  Rime.java文件中的init()函数执行完毕之后会执行prepareRime()函数。他们都在Config.java中的Config()函数中被调用。

/**
 * 很重要的方法,newOrReset() -> Config.get() -> Config()
 * newOrReset() -> Config.get() -> Config()
 * @param context
 */
public Config(@NonNull Context context) {
    self = this;
    assetManager = context.getAssets();
    themeName = getPrefs().getLooks().getSelectedTheme();
    prepareRime(context);
    deployTheme();
    init();
    prepareCLipBoardRule();
}

.yaml文件示例

# android_keys,symbols,when,property,action为map键值对
# name 为列表
android_keys:
  name: [VoidSymbol, SOFT_LEFT, SOFT_RIGHT, HOME, BACK, CALL, ENDCALL,
    exclam, quotedbl, dollar, percent, ampersand, colon, less, greater,
    question, asciicircum, underscore, braceleft, bar, braceright, asciitilde]
  symbols: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ!"$%&:<>?^_{|}~'
  when:
    ascii: 西文標籤
    paging: 翻頁標籤
    swipe_down: 下滑
  property:
    width: 寬度
    height: 高度
    gap: 間隔
  action:
    command: 命令
    option: 參數

init() -> loadMap() -> config_get_map() -> _get_map() -> _get_value()
  执行loadMap()的时候,已经将.yaml文件中的数据加载并解析完放入root变量了,config_get_map()中开始获取第一个数据项__build_info,此数据项不在.yaml文件中应该是c++程序中添加的。在_get_map()中获取map对象,键值对,交给_get_value()处理,如果是纯量(scalars):单个的、不可再分的值,则创建对象并返回。如果是map则调用_get_map(),在_get_map()中遍历每个数据项交给_get_value()处理,若又遇到map在继续调用_get_map()处理。遇到list同理,如此递归直到所有数据项处理完毕。
在这里插入图片描述
想把trime的界面改成简体,在设置界面找了一圈没找到能改成简体的设置方法。.yaml文件中也都是繁体没有简体数据项。

问题

  1. 全局搜索user_config的时候在config_component.cc中看这样一段代码不明白啥意思。
const ResourceType UserConfigResourceProvider::kDefaultResourceType = {
    "user_config", "", ".yaml"};

ResourceResolver *UserConfigResourceProvider::CreateResourceResolver(
    const ResourceType &resource_type)
{
        LOG(INFO) << " ";
        return Service::instance().CreateUserSpecificResourceResolver(resource_type);
}
  1. 有一个问题,tongwenfeng.trime.yamltrime.yamldefault.yamlluna_pinyin.schema.yaml这些文件读取出来都是放在同一个变量中吗?还是trime.yaml中的内容放在Engine对象里?luna_pinyin.schema.yaml中的内容放在Switcher对象中?问题已解决请看initSchema
  2. Session,Engine,Context,Schema,Service,Ticket这些类各自之间有什么关系??
  3. 崩溃报错:
D/Config:   I am in deployTheme() config = tongwenfeng.trime.yaml
I/ rime.cc: 
I/ rime.cc: --------- beginning of crash
A/libc: Fatal signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), 
			fault addr 0xd5 in tid 29411 (DefaultDispatch), pid 29331 (om.osfans.trime)
D/EGL_emulation: app_time_stats: avg=8.53ms min=1.99ms max=32.68ms count=60
D/EGL_emulation: app_time_stats: avg=5.11ms min=1.94ms max=30.59ms count=49

原因是%d错位:

__android_log_print(ANDROID_LOG_INFO,
" rime.cc", "deploy_config_file() file%s  %d,_name =行  version_key= %s,", s, s2, __LINE__);

改正:

__android_log_print(ANDROID_LOG_INFO,
" rime.cc", " %d行 deploy_config_file() file_name = %s, version_key= %s,", __LINE__, s, s2);
  1. 解决ResolvePath()中无法打印全路径问题。这个问题困扰了我很久。
    在resource.cc文件中FallbackResourceResolver::ResolvePath(const string &resource_id)函数内一直想打印获得的文件路径到底是那个,代码如下:
boost::filesystem::path
FallbackResourceResolver::ResolvePath(const string &resource_id)
{
    auto default_path = ResourceResolver::ResolvePath(resource_id);
    // LOG(INFO) << "default_path = " << (default_path.filename().string()).c_str();
    // LOG(INFO) << "default_path = " << (default_path.filename().string());
    // LOG(INFO) << "default_path = " << (default_path.string());
    // __android_log_print(ANDROID_LOG_INFO, " resource.cc", " %d行 ResolvePath() default_path = %s,", 
    //                 __LINE__, default_path.filename().string()); // 报错
    __android_log_print(ANDROID_LOG_INFO, " resource.cc", " %d行 ResolvePath() default_path = %s", 
                    __LINE__, default_path.string().c_str());

    // LOG(INFO) << "default_path.relative_path() = " << default_path.relative_path();
    // __android_log_print(ANDROID_LOG_INFO, "resource.cc", "ResolvePath()  %d行, value = %d", __LINE__, value);
    // LOG(INFO) << "default_path = " << boost::filesystem::system_complete(default_path);
    // LOG(INFO) << "default_path.string() = " << default_path.string();
    // LOG(INFO) << "default_path.filename() = " << default_path.filename();

    if (!boost::filesystem::exists(default_path))
    {
        auto fallback_path = boost::filesystem::absolute(
            boost::filesystem::path(type_.prefix + resource_id + type_.suffix), fallback_root_path_);
            
        // LOG(INFO) << "fallback_path = " << boost::filesystem::system_complete(fallback_path);
        // LOG(INFO) << "fallback_path.filename() = " << (fallback_path.filename());
// __android_log_print(ANDROID_LOG_INFO, "resource.cc", 
// "ResolvePath()  %d行, fallback_path = %s", __LINE__, fallback_path);
        if (boost::filesystem::exists(fallback_path))
        {
                return fallback_path;
        }
    }
    return default_path;
}

ResolvePath()函数返回的是全路径,用

LOG(INFO) << "default_path = " << (default_path.filename().string());

打印出来是:

68行  resource.cc ResolvePath() default_path = default.yaml

LOG(INFO) << "default_path = " << (default_path.string());

打印出来是:

69行  resource.cc ResolvePath() default_path = 1

试了其他很多办法都没法全路径打印出来,上面注释掉的打印语句基本不能用,最后用

__android_log_print(ANDROID_LOG_INFO, " resource.cc", 
	" %d行 ResolvePath() default_path = %s", __LINE__, default_path.string().c_str());

打印出来是:

resource.cc:  73行 ResolvePath() default_path = /sdcard/rime/build/default.yaml
  1. 当我查看打印结果时出现了"__build_info"和"timestamps"这两个标签,明明原始文件中没有。
I/ FF:   78行  config_data.cc LoadFromFile() config file = default.yaml
I/ FF:   98行  config_data.cc LoadFromFile() loading the file_name_ = default.yaml
I/ FF:   508行  config_data.cc ConvertFromYaml() key = rime_version
I/ FF:   508行  config_data.cc ConvertFromYaml() key = default
I/ FF:   508行  config_data.cc ConvertFromYaml() key = default.custom
I/ FF:   508行  config_data.cc ConvertFromYaml() key = timestamps
I/ FF:   508行  config_data.cc ConvertFromYaml() key = __build_info
I/ FF:   508行  config_data.cc ConvertFromYaml() key = Control_L
I/ FF:   508行  config_data.cc ConvertFromYaml() key = Control_R

于是查看模拟器中的default.yaml文件,也有"__build_info"和"timestamps"这两个标签。我想应该是部署输入法时加入进去的吧。

emulator64_x86_64_arm64:/sdcard/rime/build $ cat default.yaml
__build_info:
  rime_version: 1.7.3
  timestamps:
    default: 1620664065
    default.custom: 1662284347
ascii_composer:
  switch_key:
    Control_L: noop
    Control_R: noop
    Shift_L: inline_ascii
    Shift_R: commit_text
config_version: 0.15.minimal
key_binder:

发现

  1. 要想获得二级标签"hotkeys"前面必须有一级标签"switcher"
if (auto hotkeys = config->GetList("switcher/hotkeys")) // 要想获得二级标签"hotkeys"必须前面有一级标签"switcher"
{
        hotkeys_.clear();
        for (size_t i = 0; i < hotkeys->size(); ++i)
        {
                auto value = hotkeys->GetValueAt(i);

                if (!value)
                        continue;
                hotkeys_.push_back(KeyEvent(value->str()));
        }
}
  1. app\src\main\assets\rime\tongwenfeng.trime.yamlapp\src\main\assets\rime\trime.yaml主要用于绘制软键盘按键界面,不属于rime项目属于trime项目。
  2. 发现一个现象,AsciiComposerKeyBinderRecognizerPunctConfig、都有一个LoadConfig()函数。處理各類按鍵消息的組件一般都有这个函数ProcessKeyEvent()
  3. 全局搜索Require(“config”)->Create(可以找到所有加载default.yaml文件的地方。
  4. 在模块文件core_module.cc、dict_module.cc、gears_module.cc中都是用rime_get_api()函数,只有在levers_module.cc中用rime_levers_get_api()函数。另外前三个文件中用宏RIME_REGISTER_MODULE()只有在levers_module.cc中用宏RIME_REGISTER_CUSTOM_MODULE()
  5. 所有继承类Translator的类,原本以为Translator没什么用,类名中都包含Translator。看来Segmentor、Formatter、Filter、Processor也都是如此。
11 个结果 - 11 文件

librime\sample\src\trivial_translator.h:
  22:         class TrivialTranslator : public Translator

librime\src\rime\gear\echo_translator.h:
  17:         class EchoTranslator : public Translator

librime\src\rime\gear\history_translator.h:
  17:         class HistoryTranslator : public Translator

librime\src\rime\gear\punctuator.h:
  70:         class PunctTranslator : public Translator

librime\src\rime\gear\reverse_lookup_translator.h:
  24:         class ReverseLookupTranslator : public Translator

librime\src\rime\gear\schema_list_translator.h:
  18:         class SchemaListTranslator : public Translator

librime\src\rime\gear\script_translator.h:
  31:         class ScriptTranslator : public Translator, public Memory, public TranslatorOptions

librime\src\rime\gear\switch_translator.h:
  17:         class SwitchTranslator : public Translator

librime\src\rime\gear\table_translator.h:
  29:         class TableTranslator : public Translator, public Memory, public TranslatorOptions

librime-charcode\src\codepoint_translator.h:
  17:         class CodepointTranslator : public Translator

librime-lua\src\lua_gears.h:
  70:         class LuaTranslator : public Translator
  1. rime中有4个最基本的类,分别是ModuleManager、PluginManager、Service、Registry这些类只有一个单例,通过单例模式生成。那么这些类之间有什么关系呢??

module.h中的成员变量和module.cc中的注册函数:

// module registry
using ModuleMap = map<string, RimeModule *>; // map容器
ModuleMap map_;

// 此函数只被rime_api.cc中的RimeRegisterModule()调用
void ModuleManager::Register(const string &name, RimeModule *module)
{
        map_[name] = module;
}

类ModuleManager、Registry中都有注册函数,一个用来注册模块一个用来注册部件。

类Deployer的理解

Deployer类是与部署相关的类,在rime_api.cc中有大量用到。Deployer类中有很多与目录相关的属性。

// 部署者; 部署人员; 部署工具; 部署器; 部署商;
class Deployer : public Messenger
{
    public:
        // read-only access after library initialization {
        string shared_data_dir;
        string user_data_dir;
        string prebuilt_data_dir;
        string staging_dir;
        string sync_dir;
        string user_id;
        string distribution_name;
        string distribution_code_name;
        string distribution_version;
        // }
		........
    private:
        std::queue<of<DeploymentTask>> pending_tasks_;
        std::mutex mutex_;

        // C++11中的std::future是一个模板类。std::future提供了一种用于访问异步操作结果的机制。
        // std::future所引用的共享状态不能与任何其它异步返回的对象共享
        std::future<void> work_;
        bool maintenance_mode_ = false;
};

rime_api.cc中只用到下列语句,因为Service对象是单例所以返回的Deployer对象也是唯一的。

Deployer &deployer(Service::instance().deployer());

获得Deployer对象是为了使用Deployer中的函数:

// 部署者; 部署人员; 部署工具; 部署器; 部署商;
class Deployer : public Messenger
{
    public:
		......
        bool RunTask(const string &task_name, TaskInitializer arg = TaskInitializer());
        bool ScheduleTask(const string &task_name, TaskInitializer arg = TaskInitializer());
        void ScheduleTask(an<DeploymentTask> task);
        an<DeploymentTask> NextTask();
        bool HasPendingTasks();

        bool Run();
        bool StartWork(bool maintenance_mode = false);
        bool StartMaintenance();
        bool IsWorking();
        bool IsMaintenanceMode();

        // the following two methods equally wait until all threads are
        // joined下面的两个方法同样会等到所有线程都加入
        void JoinWorkThread();
        void JoinMaintenanceThread();

        string user_data_sync_dir() const;
};

创建方案对象

  default.yaml中有如下内容,这些内容将在schema.cc中的ForEachSchemaListEntry()函数中读取并在ParseSchemaListEntry()中解析,最后在匿名函数process_entry()luna_pinyin读取出来赋给变量recent,再在CreateSchema()调用构造函数Schema(recent)创建luna_pinyin对象。

schema_list:
  - schema: luna_pinyin
  - schema: cangjie5

在创建luna_pinyin对象的过程从需要加载luna_pinyin.schema.yaml,在构造函数Schema(recent)中有如下代码:

Schema::Schema(const string &schema_id) : schema_id_(schema_id)
{
        config_.reset(boost::starts_with(schema_id_, L".") ? 
                Config::Require("config")->Create(schema_id.substr(1)) : Config::Require("schema")->Create(schema_id));

        FetchUsefulConfigItems();
}

  主要是调用config_component.ccCreate(),进而调用config_component.cc GetConfigData()Require()中给出参数不同,就调用不同对象的create()函数。全局搜索")->Create(可以获得如下结果:

librime\src\rime\schema.cc:
  28:  config_.reset(Config::Require("config")->Create("default"));
  38   config_.reset(boost::starts_with(schema_id_, L".") ? 
  39:  Config::Require("config")->Create(schema_id.substr(1)) : Config::Require("schema")->Create(schema_id));

librime\src\rime\switcher.cc:
  40:  user_config_.reset(Config::Require("user_config")->Create("user")); // config_component.cc Create()

librime\src\rime\gear\ascii_composer.cc:
  225:  the<Config> preset_config(Config::Require("config")->Create("default"));

librime\src\rime\lever\deployment_tasks.cc:
  109:  the<Config> user_config(Config::Require("user_config")->Create("user"));
  269:  the<Config> config(Config::Require("config")->Create("default"));
  363:  the<Config> user_config(Config::Require("user_config")->Create("user"));
  515:  config.reset(Config::Require("schema")->Create(schema_id));
  524:  the<Dictionary> dict(Dictionary::Require("dictionary")->Create({&schema, "translator"});
  668:  the<Config> config(Config::Require("config")->Create(file_name_)); // config_component.cc Create()
  677:  config.reset(Config::Require("config_builder")->Create(file_name_));

  上面搜索到的结果中只有Config::Require("schema")->Create(schema_id)是调用了schema.cc文件中的Create()函数,其他都是直接调用config_component.ccCreate()
  因为ConfigComponent 继承自 ConfigComponentBase 继承自Config::Component继承自ComponentBaseConfigComponentBase类的成员函数Create(const string &file_name)自然被ConfigComponent继承。而类ConfigComponent在应用程序加载时在rime_core_initialize()中创建,并通过Registry类的Register()函数注册到了Registry类的成员变量map_中。Config::Require("config")等形式利用Registry类的ComponentBase *Registry::Find(const string &name)函数从map_获取与字符串对应的对象。最后执行各自对象中的Create()函数。同理,Config::Require("schema")获取到是早以在rime_core_initialize()中注册好的SchemaComponent对象,并在SchemaComponent中重写了Create()函数。
  至于为什么Require("schema")获取的是SchemaComponent对象却可以调用config_component.ccCreate()是因为在schema.cc文件中实现了SchemaComponent::Create()函数。并在Create()中调用了config_component.ccCreate()
librime\src\rime\schema.cc

// 创建.default.schema方案对象
Config *SchemaComponent::Create(const string &schema_id)
{
        return config_component_->Create(schema_id + ".schema");
}

有个问题

GetConfigData()调用LoadConfig()调用ResolvePath(),在ResolvePath()中可以打印很多type_.name的值。为什么?

type_.name =user_config
type_.name =compiled_config
type_.name = table
type_.name = prism
type_.name =db
type_.name =$config_source_file

  目前可以确定的是当执行Config::Require("config")时在ResolvePath()函数中打印出type_.name =compiled_config,执行Config::Require("user_config")时在ResolvePath()函数中打印出type_.name =user_config
config_component.cc中有:

const ResourceType DeployedConfigResourceProvider::kDefaultResourceType = {
     "compiled_config", "", ".yaml"};
const ResourceType UserConfigResourceProvider::kDefaultResourceType = {
     "user_config", "", ".yaml"};

因为在config_component.h中有:

template <class Loader, class ResourceProvider = ConfigResourceProvider>
ConfigComponent(const ResourceType &resource_type = ResourceProvider::kDefaultResourceType)
     : ConfigComponentBase(ResourceProvider::CreateResourceResolver(resource_type))

core_module.ccrime_core_initialize()中有:

auto config_loader = new ConfigComponent<ConfigLoader, DeployedConfigResourceProvider>;
r.Register("config", config_loader);
auto user_config =
	 new ConfigComponent<ConfigLoader, UserConfigResourceProvider>([](ConfigLoader *loader)
r.Register("user_config", user_config);

  所以在core_module.cc文件的rime_core_initialize()中创建ConfigComponent类型的对象config_loader时DeployedConfigResourceProvider替换了ConfigResourceProvider,DeployedConfigResourceProvider::kDefaultResourceType 替换了resource_type,所以config_loader的type_ = {“compiled_config”, “”, “.yaml”}。
  创建ConfigComponent类型的对象user_configconfig_loader时UserConfigResourceProvider替换了ConfigResourceProvider,DeployedConfigResourceProvider::kDefaultResourceType 替换了resource_type,所以user_config的type_ = {“user_config”, “”, “.yaml”}。

各个类的包含关系

Ticket类包含
Engine *engine = nullptr;
Schema *schema = nullptr;

  1. Session,Engine,Context,Schema,Service,Ticket,Processor,Switcher这些类各自之间有什么关系??
    在service.cc的Service::CreateSession()中调用构造函数Session::Session()创建会话,在构造函数Session::Session()中执行Engine::Create()创建一个Engine对象赋给Session类的成员变量engine_。在Engine::Create()中new一个Engine的子类ConcreteEngine。

Engine类有成员变量如下:

class Engine : public Messenger
{
......
protected:
        Engine();

        the<Schema> schema_;
        the<Context> context_;
        CommitSink sink_;
        Engine *active_engine_ = nullptr;
};

ConcreteEngine类有成员变量如下:

// empty vector of of<Processor>, empty vector of Processor智能指针
vector<of<Processor>> processors_;
vector<of<Segmentor>> segmentors_;
vector<of<Translator>> translators_;
vector<of<Filter>> filters_;
vector<of<Formatter>> formatters_;
vector<of<Processor>> post_processors_;

Context类有成员变量如下:

string input_;
size_t caret_pos_ = 0;
Composition composition_;
CommitHistory commit_history_;
map<string, bool> options_;
map<string, string> properties_;

Notifier commit_notifier_;
Notifier select_notifier_;
Notifier update_notifier_;
Notifier delete_notifier_;
OptionUpdateNotifier option_update_notifier_;
PropertyUpdateNotifier property_update_notifier_;
KeyEventNotifier unhandled_key_notifier_;

Schema类有成员变量如下:

string schema_id_;
string schema_name_;
the<Config> config_;

// frequently used config items常用配置项目
int page_size_ = 5;
bool page_down_cycle_ = false;
string select_keys_;

Processor很特别,Engine的子类ConcreteEngine有一个vector<of<Processor>> processors_成员变量,而Processor类中又有一个成员变量Engine *engine_。
至此清楚了:

  1. Service类有一个集合(map)类型的成员变量sessions_,根据key值存放Session对象。
  2. Session类有一个成员变量engine_,在CreateSession()中创建的Engine类的对象存入engine_中。
  3. Engine类有一个Schema类型成员变量和一个Context类型成员变量。

第六步、加载script_translator

  从engin.cc中的InitializeComponents()出发,读取luna_pinyin.schema.yaml文件中的"engine/translators/script_translator"

engine:
  processors:
......
  segmentors:
......
  translators:
    - punct_translator
    - reverse_lookup_translator
    - script_translator
    - table_translator@cangjie
    - script_translator@pinyin
  filters:
......

根据字符串"script_translator"获取对应的Component对象。

// 脚本翻译器,用于拼音等基于音节表的输入方案
r.Register("script_translator", new Component<ScriptTranslator>);

获得对应的Component对象以后,执行Component对象中的Create()函数,在Create()函数中执行new ScriptTranslator()

an<Translator> t(c->Create(ticket));

然后跳到ScriptTranslator()函数中,执行如下图:
在这里插入图片描述
下图是上图的补充
在这里插入图片描述

第七步 deployTheme()

  1. 字面意思,部署主题。在deployTheme()中循环执行deploy_config_file(),被两个地方调用。
    Config() -> init() -> deploy_config_file()
    Config() -> deployTheme() -> deploy_config_file()
  2. 注意:operator[]是个函数。
    在这里插入图片描述

第八步、编译词典

在这里插入图片描述
在这里插入图片描述

插入一个话题,格式化makelist.txt

  vscode插件cmake-format无法在windwons上运行,对于windows用户,通过查阅cmake-format的官方文档后并没有找到其对windows系统的任何支持,但好在cmake-format是一个开源的项目,在github上的能找到其开源的项目代码,这一看才知道其没有windows的支持是不足为怪的,因为这个项目原生采用python编写的。既然源码都开源了,那就有办法了,查阅了其源码后发现只要把其封装成一个可供windows执行的可执行文件就可以了。

  首先我们需要安装python,我这里安装的是python3.8,然后在python中安装pyinstaller包,这个包可以帮我们把python项目打包发布到windows上。然后在python中安装cmake-format包,同样我们也可以通过pip来安装,然后在python的包目录下我们可以找到安装好的cmake-format包,我这里的文件地址为D:\python-3.8\Lib\site-packages\cmake_format\,根据你的python安装位置会有所不同,之后我们将其用pyinstaller打包成exe就好了,这里直接打包是不行的,会遗漏很多模块。
安装pyinstaller包

PS E:\Python\Python310> pip install pyinstaller

安装cmake-format包

PS E:\Python\Python310\Lib\site-packages> pip install cmake_format

在cmd中执行,就能输出格式化的CMakeLists.txt。

E:\Python\Python310\Lib\site-packages>cmake-format D:\project\andriod\trime-develop\app\src\main\jni\librime\thirdparty\src\CMakeLists.txt -o CMakeLists.txt

加配置文件

E:\Python\Python310\Lib\site-packages>cmake-format D:\project\andriod\trime-develop\app\src\main\jni\librime\thirdparty\src\CMakeLists.txt --config-file formatting.py -o CMakeLists.txt

配置文件 formatting.py

# -----------------------------
# Options effecting formatting.
# -----------------------------
with section("format"):

  # How wide to allow formatted cmake files
  line_width =120 

  # How many spaces to tab for indent
  tab_size = 8

  # If true, separate flow control names from their parentheses with a space
  separate_ctrl_name_with_space = False

  # If true, separate function names from parentheses with a space
  separate_fn_name_with_space = False

  # If a statement is wrapped to more than one line, than dangle the closing
  # parenthesis on its own line.
  dangle_parens = False

cmakeclang GitHub地址
格式化详细配置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/157878.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3D应用无需下载即点即用,云应用带来更轻量的元宇宙

最近一个程序员朋友告诉我&#xff0c;公司市场部想做一个元宇宙相关的互动游戏&#xff0c;于是给技术团队提了一个带用户线上沉浸式环游园区的H5开发需求。他摸着所剩无几的头发据理力争&#xff1a;这个需求真的做不了&#xff01;我听了很疑惑&#xff0c;现在许多品牌都在…

工作流引擎架构设计

原文链接&#xff1a; 工作流引擎架构设计 最近开发的安全管理平台新增了很多工单申请流程需求&#xff0c;比如加白申请&#xff0c;开通申请等等。最开始的两个需求&#xff0c;为了方便&#xff0c;也没多想&#xff0c;就直接开发了对应的业务代码。 但随着同类需求不断增…

深度学习PyTorch 之 网络结构可视化

深度学习&PyTorch 之 DNN-回归 深度学习&PyTorch 之 DNN-回归&#xff08;多变量&#xff09; 分别介绍了DNN回归的方法和代码&#xff0c;但是模型建立好了&#xff0c;他到底是个什么样子呢&#xff1f; 我们这节给大家介绍一个查看模型结构的方法 可视化介绍 我们…

【信管8.1】项目人力资源管理概念及过程

项目人力资源管理概念及过程不管你做什么事&#xff0c;要成就什么事业&#xff0c;要做什么项目&#xff0c;这一切&#xff0c;都是由人来完成的。因此&#xff0c;人力资源对于项目管理来说&#xff0c;是非常重要的一个管理过程。同时&#xff0c;人力资源管理也是整个管理…

2023/1/11 Web前端Promise从入门到精通

ES6引入的进行异步编程的解决方案&#xff0c;从语法上说它是一个构造函数。 异步编程包括但不限于&#xff1a;文件操作、数据库操作、AJAX、定时器 为什么要用Promise&#xff1f; 之前进行异步编程直接通过回调函数的方式进行&#xff0c;会导致回调地狱。 回调函数&#…

Qt扫盲-QMenu理论总结

QMenu理论总结一、概述二、常用操作1. 添加Action2. 信号槽3. 可撕下菜单4. 展示菜单一、概述 QMenu其实就是菜单控件&#xff0c;菜单控件本质上就是一个选择项目。它可以是菜单栏中的下拉菜单&#xff0c;也可以是独立的上下文菜单。当用户单击相应的位置或按下指定的快捷键…

【大数据】第一章:了解Hadoop生态圈

大数据特点&#xff08;4V&#xff09; Volume(大量) 非常非常多&#xff0c;大企业数据接近1EB Velocity(高速) 比如在双十一&#xff0c;数据爆增 Variety(多样) 很多样子的数据&#xff0c;比如&#xff0c;代码&#xff0c;图片&#xff0c;视频&#xff0c;JSON&am…

【C++】八大排序

文章目录前言1. 插入排序2. 希尔排序3. 选择排序4. 堆排序5. 冒泡排序6. 快速排序(重点)6.1 快速排序(hoare版本)6.2 快速排序(挖坑法)6.3 快速排序(前后指针法)6.4 快速排序(非递归)6.5 快速排序(优化)7. 归并排序7.1 归并排序(递归实现)7.2 归并排序非递归实现8. 计数排序排序…

Docker搭建PHP运行环境

目录 Docker 安装 PHP Docker 安装 Nginx ​编辑运行nginx容器 nginx安装成功 Nginx PHP 部署PHP项目 启动 PHP&#xff1a; 启动 nginx&#xff1a; 查看正在运行的容器: 访问域名测试搭建结果 Docker相关命令描述 Docker 安装 PHP 这里我们拉取官方的镜像,标签…

代码随想录算法训练营第8天 344.反转字符串、541. 反转字符串II、剑指Offer58-II.左旋转字符串

代码随想录算法训练营第8天 344.反转字符串、541. 反转字符串II、剑指Offer58-II.左旋转字符串 反转字符串 力扣题目链接(opens new window) 编写一个函数&#xff0c;其作用是将输入的字符串反转过来。输入字符串以字符数组 char[] 的形式给出。 对于字符串&#xff0c;我…

Linux驱动开发基础__Linux 系统对中断处理的演进

目录 1 Linux 对中断的扩展&#xff1a;硬件中断、软件中断 2 中断处理原则 1&#xff1a;不能嵌套 3 中断处理原则 2&#xff1a;越快越好 4 要处理的事情实在太多&#xff0c;拆分为&#xff1a;上半部、下半部 5 下半部要做的事情耗时不是太长&#xff1a;tasklet…

154. 滑动窗口

文章目录QuestionIdeasCodeQuestion 给定一个大小为 n≤106 的数组。 有一个大小为 k 的滑动窗口&#xff0c;它从数组的最左边移动到最右边。 你只能在窗口中看到 k 个数字。 每次滑动窗口向右移动一个位置。 以下是一个例子&#xff1a; 该数组为 [1 3 -1 -3 5 3 6 7]&…

知识点滴 - 数据库视图概念

视图是数据库中一个非常简单的概念&#xff0c;写过SQL的人几乎大致了解视图。本文除了在回顾视图的本质及相关操作知识时&#xff0c;会重点阐述它蕴含的分层思想在数据分析工作中的作用。 1&#xff0c;视图的本质与作用 视图是一个数据库中的虚拟表&#xff0c;它的本质是S…

模板特化与static成员初始化

我们知道在 c 的类中&#xff0c;如果有static成员数据&#xff0c;则需要在类外进行定义&#xff0c;而类内那只是声明。这个在类模板中也是一样的&#xff0c;需要在类外进行定义。普通类模板的 static 数据的初始化&#xff0c;如下代码&#xff1a; template <class T&…

SpringBoot在Controller层接收参数的常用方法(超详细)

前言 在工作中&#xff0c;比如要实现一个功能&#xff0c;前端传什么参数&#xff0c;后端的controller层中怎么接收参数 &#xff0c;封装成了什么实体对象&#xff0c;有些参数是在URL上使用&#xff0c;有些参数是在body上使用&#xff0c;service层中做了什么逻辑&#xf…

数据结构(根据王道整理)

数据结构 文章目录数据结构线性结构与非线性结构链表kmp算法栈二叉树完全二叉树二叉树的存储结构二叉树的访问树的深度二叉树的层次遍历由遍历序列构造二叉树已知后序跟中序建立二叉树线索二叉树序言&#xff08;土办法解决找前驱&#xff09;线索二叉树存储结构中序线索二叉树…

几道基础的二叉树、树的题

几道基础的二叉树、树的题LeetCode144.二叉树的前序遍历思路及实现方法一&#xff1a;递归方法二&#xff1a;迭代LeetCode145.二叉树的后序遍历思路及实现方法一&#xff1a;递归方法二&#xff1a;迭代LeetCode94.二叉树的中序遍历思路及实现方法一&#xff1a;递归方法二&am…

数据结构(2)树状数组

活动 - AcWing 参考&#xff1a;《算法竞赛进阶指南》-lyd 目录 一、概念 1.主要功能 2.实现方式 3. 二、例题 1.树状数组和逆序对 2.树状数组和差分 3. 两层差分 4. 结合二分 一、概念 1.主要功能 树状数组可以完成的功能主要有&#xff1a; 维护序列的前缀和单…

pytest-pytest插件之测试覆盖率pytest-cov

简介 测试覆盖率是指项目代码被测试用例覆盖的百分比&#xff0c;使用pytest-cov插件可以统计测试覆盖率 添加链接描述 安装插件pytest-cov pip install pytest-cov用法 基本用法 –cov的参数是要统计代码覆盖率的源码&#xff0c;我将源码放在mysrc中&#xff0c;test_s…

qiankun微应用加载第三方js跨域报错

当我们在qiankun微应用&#xff0c;引入第三方js脚本时会产生跨域问题并报错&#xff0c;看qiankun的解释&#xff1a;常见问题 - qiankunqiankun会把静态资源的加载拦截&#xff0c;改用fetch方式获取资源&#xff0c;所以要求这些资源支持跨域。虽然qiankun也提供了解决方案&…