使用Android Studio開發(fā)時使用C庫來實現(xiàn)某些功能绢慢,例如需要使用libmobi庫來解析mobi格式的文件
一、在已有項目中添加
1.在項目的app(或module)目錄下的src/main新建cpp文件夾(在java同級)
2.在cpp目錄下添加CMakeLists.txt文件
CMakeLists.txt
# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.
cmake_minimum_required(VERSION 3.10.2)
# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add.library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.
#定義一個變量
set(LIBRARY_NAME parser)
#設置要編譯的庫
add_library( # Specifies the name of the library.
${LIBRARY_NAME}
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
)
# Specifies a path to native header files.
# include_directories(src/main/cpp/include/)
#主要是找NDK中的庫
find_library( # Defines the name of the path variable that stores the
# location of the NDK library.
log-lib
# Specifies the name of the NDK library that
# CMake needs to locate.
log )
# Links your native library against one or more other native libraries.
#將本機庫鏈接到一個或多個其他本機庫(其實就是將這些庫鏈接起來叹侄,不然不能調方法)
target_link_libraries( # Specifies the target library.
${LIBRARY_NAME}
# Links the log library to the target library.
${log-lib} )
3.在app目錄下的build.gradle中添加以下代碼
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
cppFlags ''
}
}
}
...
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.10.2'
}
}
ndkVersion "16.1.4479499"
}
ndkVersion "16.1.4479499" 指定ndk的版本,可以改成你需要的版本
4.在cpp目錄下新建temp.cpp和temp.h并在CMakeLists.txt的add_library中添加
cpp文件可添加多個昨登,如下:
5.在需要使用的地方先loadLibrary趾代,然后定義jni方法
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
private external fun stringFromJNI(): String
companion object {
init {
System.loadLibrary("parser")
}
}
}
在stringFromJNI方法上使用快捷提示option+enter (Mac os),并選擇Create Jni function
然后會在temp.cpp中會自動創(chuàng)建對應的方法丰辣,如下
TODO位置就可以寫c++代碼
使用libmobi庫請直接看第三部分
二撒强、在新建項目中添加
File->new->new Project禽捆,選擇Native C++,然后next->finish
這樣就直接完成了飘哨,IDE會直接幫你創(chuàng)建一個native-lib.cpp
如果要指定ndk版本胚想,如下
android {
...
externalNativeBuild {
cmake {
path file('src/main/cpp/CMakeLists.txt')
version '3.10.2'
}
}
ndkVersion "16.1.4479499"
}
三、使用libmobi庫
1.進入libmobi GitHub倉庫libmobi
2.復制libmobi中src目錄下所有文件
3.在自己的項目的cpp文件夾下新建libmobi文件夾并粘貼剛才復制的所有文件
4.在CMakeLists.txt中添加
由于libmobi使用了NDK的zlib芽隆,所以需要在CMakeLists.txt有如下修改
5.在temp.cpp的jni方法里寫c++代碼調用libmobi的解析方法
extern "C" JNIEXPORT jstring JNICALL
Java_com_test_temp_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz) {
jstring file_path = (jstring) "you_file_path";
jstring parser_out_path = (jstring)"parser_out_path";
//parserMobi方法是從mobitool.c的loadfilename(const char *fullpath)中拷貝的
return parserMobi(env, Jstring2CStr(env, file_path),parser_out_path);
}
Jstring2CStr方法放在最后了
parserMobi()就是自定義解析mobi格式文件的方法浊服,方法的具體代碼比較多就不發(fā)了,這個方法的主要邏輯是從libmobi的tools文件夾下的mobitool.c中的loadfilename(const char *fullpath)方法中拷貝的胚吁,具體的解析方法可以看mobitool.c的main(int argc, char *argv[])方法牙躺,例如main方法有如下的介紹:
mobitool.c本身就是一個命令行工具,每個參數(shù)都有自己的作用腕扶,根據(jù)自己的需求找到對應參數(shù)在mian方法中的處理然后參考孽拷。例如’-s‘就是解析源文件,到mobitool.c的main方法找到如下代碼
/**
@brief Main
*/
int main(int argc, char *argv[]) {
...
int c;
while((c = getopt(argc, argv, "cd" PRINT_EPUB_ARG "imo:" PRINT_ENC_ARG "rs" PRINT_RUSAGE_ARG "vx7")) != -1)
switch(c) {
...
case 'r':
dump_rec_opt = 1;
break;
case 's':
dump_parts_opt = 1;
break;
...
default:
exit_with_usage(argv[0]);
}
...
ret = loadfilename(filename);
...
return ret;
}
可知是將dump_parts_opt賦值為1半抱,最后執(zhí)行了loadfilename(filename)方法乓搬,再到loadfilename方法中找到dump_parts_opt相關的地方
/**
@brief Main routine that calls optional subroutines
@param[in] fullpath Full file path
*/
int loadfilename(const char *fullpath) {
...
} else if (dump_parts_opt || create_epub_opt) {
printf("\nReconstructing source resources...\n");
/* Initialize MOBIRawml structure */
/* This structure will be filled with parsed records data */
MOBIRawml *rawml = mobi_init_rawml(m);
if (rawml == NULL) {
printf("Memory allocation failed\n");
mobi_free(m);
return ERROR;
}
/* Parse rawml text and other data held in MOBIData structure into MOBIRawml structure */
mobi_ret = mobi_parse_rawml(rawml, m);
if (mobi_ret != MOBI_SUCCESS) {
printf("Parsing rawml failed (%s)\n", libmobi_msg(mobi_ret));
mobi_free(m);
mobi_free_rawml(rawml);
return ERROR;
}
if (create_epub_opt && !dump_parts_opt) {
...
} else {
printf("\nDumping resources...\n");
/* Save parts to files */
ret = dump_rawml_parts(rawml, fullpath);
if (ret != SUCCESS) {
printf("Dumping parts failed\n");
}
}
/* Free MOBIRawml structure */
mobi_free_rawml(rawml);
}
...
return ret;
}
代碼比較長大部分都省略了,關鍵就在這兩個if判斷時根據(jù)dump_parts_opt值做了什么邏輯代虾,然后就可以參考這部分代碼自定義自己的解析方法,(是不是可以考慮將整個mobitool.c拷貝到項目,然后將main方法改造成可調用的方法激蹲?注:不修改是不行的,有main方法的話無法編譯)
Jstring2CStr方法
char *Jstring2CStr(JNIEnv *env, jstring jstr) {
char *rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("GB2312");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte *ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0) {
rtn = (char *) malloc(alen + 1); //new char[alen+1];
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}