引擎初始化首先是在Java层调用native的初始化方法,Java层调用如下:
private void initPinyinEngine() {
byte usr_dict[];
usr_dict = new byte[MAX_PATH_FILE_LENGTH];
// Here is how we open a built-in dictionary for access through
// a file descriptor...
AssetFileDescriptor afd = getResources().openRawResourceFd(R.raw.dict_pinyin);
if (Environment.getInstance().needDebug()) {
Log.i("foo", "Dict: start=" + afd.getStartOffset()
+ ", length=" + afd.getLength() + ", fd="
+ afd.getParcelFileDescriptor());
}
if (getUsrDictFileName(usr_dict)) {
inited = nativeImOpenDecoderFd(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength(), usr_dict);
}
try {
afd.close();
} catch (IOException e) {
}
}
根据构建后的二进制文件获取 一个文件描述符,将该文件描述符传入native进行初始化,其实就是load之前build中产生的一些数组、树结构以及一些值,native经过一些列调用,最终是调用用了matrixsearch类的init方法:
bool MatrixSearch::init_fd(int sys_fd, long start_offset, long length,
const char *fn_usr_dict) {
if (NULL == fn_usr_dict)
return false;
if (!alloc_resource())
return false;
LOGD("init_fd sys_fd == %d , start_offset == %l, and length == %l", sys_fd, start_offset, length);
if (!dict_trie_->load_dict_fd(sys_fd, start_offset, length, 1, kSysDictIdEnd))
return false;
LOGD("fn_usr_dict == %c", *fn_usr_dict);
if (!user_dict_->load_dict(fn_usr_dict, kUserDictIdStart, kUserDictIdEnd)) {
delete user_dict_;
user_dict_ = NULL;
} else {
user_dict_->set_total_lemma_count_of_others(NGram::kSysDictTotalFreq);
}
LOGD("size of dict_trie == %d", sizeof(dict_trie_));
reset_search0();
inited_ = true;
return true;
}
调用了两个load_dict,一个是dict_trie的,用来加载sys_fd,这个sys_fd就是build后生成的二进制文件dict_pinyin.dat文件,这里使用其文件描述符来进行加载操作,另一个是调用user_dict的load_dict方法,此方法用来加载用户词典,fn_usr_dict变量为:/data/user/0/com.android.inputmethod.latin/files/user_dict.dat文件,这个dat文件是用户在使用输入法过程中产生的用户字典,初次安装应用的话用户字典初始状态为空,因此初始化的过程主要逻辑在加载系统词典:
bool DictTrie::load_dict_fd(int sys_fd, long start_offset,
long length, LemmaIdType start_id,
LemmaIdType end_id) {
if (start_offset < 0 || length <= 0 || end_id <= start_id)
return false;
FILE *fp = fdopen(sys_fd, "rb");
if (NULL == fp)
return false;
if (-1 == fseek(fp, start_offset, SEEK_SET)) {
fclose(fp);
return false;
}
free_resource(true);
dict_list_ = new DictList();
if (NULL == dict_list_) {
fclose(fp);
return false;
}
SpellingTrie &spl_trie = SpellingTrie::get_instance();
NGram &ngram = NGram::get_instance();
if (!spl_trie.load_spl_trie(fp) || !dict_list_->load_list(fp) ||
!load_dict(fp) || !ngram.load_ngram(fp) ||
ftell(fp) < start_offset + length ||
total_lma_num_ > end_id - start_id + 1) {
free_resource(true);
fclose(fp);
return false;
}
fclose(fp);
return true;
}
主要加载逻辑都在第四个if语句中:
1、spl_trie.load_spl_trie()从stream中读取相关数据结构并组织拼音树结构。
2、dict_list_->load_list(fp)从fp中读取单汉字列表scis以及buf_413个合法音节数组等,load_list的过程和save_list的流程相同,按照save的顺序去读取对应的数组:scis_num_、start_pos_、start_id_、scis_hz_、scis_splid_和buf_。
3、load_dict(fp)从fp中恢复lma_node_num_le0_、lma_node_num_ge1_、lma_idx_buf_len_、top_lmas_num_、lma_idx_buf_、nodes_ge1_和root_。
4、ngram.load_ngram(fp)从fp中加载build的时候save的数组和数值:idx_num_、freq_codes_、lma_freq_idx_,关于ngram信息构建流程可以参考前面的Google原生输入法LatinIME词库构建流程分析(三)--N-gram信息构建
综上,load_dict的流程其实就是把build过程中的一些数组和数值从二进制文件流中按照保存次序一次读取出来,在search或者predict的过程中,综合这些数据结构实现输入文字的查找、预测过程。