关于在Android中使用CMake你所需要了解的一切(二)

4,746 阅读4分钟

虽然本篇和上篇没很大的关系,但…还是建议先去看下上篇----初次使用CMake构建native项目


如何将现有的cpp代码集成到项目中

​ 这个在写JNI的时候就很常见了,比如json库,C++自己是没有提供json库的,然后我们在写JNI的时候,通常需要和上层交互数据,较简单的就是json了,那么就拿json来做讲解吧。首先来找一个json库啦!

jsoncpp本篇就用这个json库来做讲解吧,首先把代码clone下来。

源码集成进项目中

  1. 将include里的json文件夹拷贝到../app/src/main/cpp目录下;

  2. 将src/lib_json里面的文件除去 CMakeLists.txt拷贝到../app/src/main/cpp目录下;

  3. 最终如下:

  4. 修改../app/src/main/cpp/CMakeLists.txt如下:

    cmake_minimum_required(VERSION 3.4.1)
    add_library(
            native_hello
            SHARED
            json_tool.h
            json_reader.cpp
            json_valueiterator.inl
            json_value.cpp
            json_writer.cpp
            version.h.in
            # 下面的cpp若是没有则新建一个,本文基于上篇文章
            native_hello.cpp
    )
    target_link_libraries(
            native_hello
            android
            log
    )
    
  5. make build一下

    What Are you!!!出错了,告诉在json_tool.h的第10行出错了,那行吧,点进去瞅一眼,如下:

    哦,include错了,应该改为 #include “json/config.h” 那就改吧!cv过来的所有文件都得去查看一下(试想一下,若是cv过来的文件有1W个,咋办…,改完这个,将来别的地方在引用,又忘了那个曾经是改过的了,停!stop,我眼疼!)。

编写测试代码

  1. 打开../app/src/main/cpp/native_hello.cpp 更改如下:

    //
    // Created by xong on 2018/9/28.
    //
    #include<jni.h>
    #include "json/json.h"
    #define XONGFUNC(name)Java_com_xong_andcmake_jni_##name
    
    extern "C" JNIEXPORT
    jstring JNICALL
    XONGFUNC(NativeFun_outputJsonCode)(JNIEnv *env, jclass thiz,
                                        jstring jname, jstring jage, jstring jsex, jstring jtype)
    {
        Json::Value root;
        const char *name = env->GetStringUTFChars(jname, NULL);
        const char *age = env->GetStringUTFChars(jage, NULL);
        const char *sex = env->GetStringUTFChars(jsex, NULL);
        const char *type = env->GetStringUTFChars(jtype, NULL);
        root["name"] = name;
        root["age"] = age;
        root["sex"] = sex;
        root["type"] = type;
        
        env->ReleaseStringUTFChars(jname, name);
        env->ReleaseStringUTFChars(jage, age);
        env->ReleaseStringUTFChars(jsex, sex);
        env->ReleaseStringUTFChars(jtype, type);
    
        return env->NewStringUTF(root.toStyledString().c_str());
    }
    
    extern "C" JNIEXPORT
    jstring JNICALL
    XONGFUNC(NativeFun_parseJsonCode)(JNIEnv *env, jclass thiz,
                                       jstring jjson)
    {
        const char *json_str = env->GetStringUTFChars(jjson, NULL);
        std::string out_str;
    
        Json::CharReaderBuilder b;
        Json::CharReader *reader(b.newCharReader());
        Json::Value root;
        JSONCPP_STRING errs;
        bool ok = reader->parse(json_str, json_str + std::strlen(json_str), &root, &errs);
        if (ok && errs.size() == 0) {
            std::string name = root["name"].asString();
            std::string age = root["age"].asString();
            std::string sex = root["sex"].asString();
            std::string type = root["type"].asString();
            out_str = "name: " + name + "\nage: " + age + "\nsex:" + sex + "\ntype: " + type + "\n";
        }
        env->ReleaseStringUTFChars(jjson, json_str);
        return env->NewStringUTF(out_str.c_str());
    }
    
  2. 修改NativeFun类如下:

    package com.xong.andcmake.jni;
    
    /**
     * Create by xong on 2018/9/28
     */
    public class NativeFun {
    
        static {
            System.loadLibrary("native_hello");
        }
    
        public static native String outputJsonCode(String name, String age, String sex, String type);
    
        public static native String parseJsonCode(String json_str);
    }
    
    
  3. 测试:

    package com.xong.andcmake;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.widget.TextView;
    
    import com.xong.andcmake.jni.NativeFun;
    
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            TextView tv_native_content = findViewById(R.id.tv_native_content);
            String outPutJson = NativeFun.outputJsonCode("xong", "21", "man", "code");
            String parseJson = NativeFun.parseJsonCode(outPutJson);
            tv_native_content.setText("生成的Json:\n" + outPutJson + "\n解析:" + parseJson);
        }
    }
    

    结果如下图:

编译成库文件

OK,集成成功,但是太复杂,太麻烦了,每次要写那么一堆配置文件,少一个都不行,还要挨个的去改 include 想想都可怕,那么可不可以把这个jsoncpp打成库呢?那么我们就要考虑如下:

  1. 必须使用CMake,不使用编写mk的方式;
  2. 在任何系统上都可以,不可在编译库的时候切换到其他系统;

好吧,基于以上两点,百度搜了一波,发现…GG,没有符合的哎,用CMake就得去Linux下面,且需要自己构建工具链,要不然就是…mk…。再去Google搜一搜,发现还是这样的,难道,不存在?再去GitHub搜,发现没有相关的,无可奈何,去Google提供的sample中找一找吧,哈!还真有发现,链接:hello-libs

编译so动态库

  1. 修改cpp目录为:

  2. 修改../cpp/jsoncpp/目录中的CMakeLists.txt如下:

    cmake_minimum_required(VERSION 3.4.1)
    
    set(CMAKE_VERBOSE_MAKEFILE on)
    
    add_library(
    		# 库名字
    		jsoncpp
            # 库类型
    		SHARED
    		# 库包含的资源
            src/json_tool.h
            src/json_reader.cpp
            src/json_valueiterator.inl
            src/json_value.cpp
            src/json_writer.cpp
            src/version.h.in)
    
    # 导出目录 此处的设置 导出主目录在 Project/export文件夹内。
    set(export_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../export)
    
    set_target_properties(
    		# 库名字
            jsoncpp
            # 设置输出.so动态库的路径 
            PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}")
    
    add_custom_command(
    		# POST_BUILD 处 有三个值可选
            # 分别是:
            # PRE_BUILD:在 hello 运行其他规则前执行
            # PRE_LINK:在编译源文件之后但在 链接其他二进制文件 或 运行静态库的库管理器 或 归档工具 之前执行
            # POST_BUILD:最后执行
            TARGET jsoncpp POST_BUILD
            
            # 拷贝命令 将 ${CMAKE_CURRENT_SOURCE_DIR}/src/json/allocator.h 文件拷贝到 ${export_dir}/libsojsoncpp/include/json/ 文件夹内 且名字和之前的相同
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/allocator.h" "${export_dir}/libsojsoncpp/include/json/allocator.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/config.h" "${export_dir}/libsojsoncpp/include/json/config.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/forwards.h" "${export_dir}/libsojsoncpp/include/json/forwards.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/features.h" "${export_dir}/libsojsoncpp/include/json/features.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/value.h" "${export_dir}/libsojsoncpp/include/json/value.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/reader.h" "${export_dir}/libsojsoncpp/include/json/reader.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/writer.h" "${export_dir}/libsojsoncpp/include/json/writer.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/assertions.h" "${export_dir}/libsojsoncpp/include/json/assertions.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/autolink.h"  "${export_dir}/libsojsoncpp/include/json/autolink.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/json.h"  "${export_dir}/libsojsoncpp/include/json/json.h"
    
            COMMAND "${CMAKE_COMMAND}" -E copy "${CMAKE_CURRENT_SOURCE_DIR}/src/json/version.h"  "${export_dir}/libsojsoncpp/include/json/version.h"
    
            )
    
    
  3. 修改 ../cpp/CMakeLists.txt如下:

    cmake_minimum_required(VERSION 3.4.1)
    
    set(CMAKE_VERBOSE_MAKEFILE on)
    
    # 设置资源主目录 CMAKE_CURRENT_SOURCE_DIR 代表当前CMakeLists.txt 所在的目录
    set(lib_src_DIR ${CMAKE_CURRENT_SOURCE_DIR})
    
    # 设置CMake编译后文件的存放的临时目录
    set(lib_build_DIR $ENV{HOME}/tmp)
    
    # 将生成的临时文件放在 lib_build_DIR 中
    file(MAKE_DIRECTORY ${lib_build_DIR})
    
    # 添加子项目
    add_subdirectory(${lib_src_DIR}/jsoncpp ${lib_build_DIR}/jsoncpp)
    
    
  4. 修改 ../app/build.gradle如下:

    apply plugin: 'com.android.application'
    
    android {
        ...
        defaultConfig {
    		...
            externalNativeBuild {
                cmake {
                    // 这里的名字最好和 ../cpp/jsoncpp/CMakeLists.txt 中设置的名字相同
                    targets 'jsoncpp'
                }
            ...
            }
        }
    	...
        externalNativeBuild {
            cmake {
                path 'src/main/cpp/CMakeLists.txt'
            }
        }
    }
    

说明:点击Build/Make Project(或者 Make Module 'app') 会在 项目根目录下 新建 export 文件夹 在里面会存放 库所需的头文件和so动态库。编译后如下:

so和头文件都生成了,但是我们在写../cpp/jsoncpp/CMakeLists.txt 文件时,也发现了,将所需的头文件导出到指定目录时有点儿费劲,只是名字换了下,若是用代码来写的话,一个for循环就可以了,那么在CMakeLists.txt中,可不可以实现类似的呢?哈哈,那当然是肯定的了,最终修改如下:

cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

add_library(
		jsoncpp 
		SHARED
        src/json_tool.h
        src/json_reader.cpp
        src/json_valueiterator.inl
        src/json_value.cpp
        src/json_writer.cpp
        src/version.h.in)

set(export_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../export)

set_target_properties(
        jsoncpp
        PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}")

add_custom_command(
        TARGET jsoncpp POST_BUILD
        # 将 ${CMAKE_CURRENT_SOURCE_DIR}/src/json 文件夹下的文件 导出到 ${export_dir}/libajsoncpp/include/json/ 文件夹内
        COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/json" "${export_dir}/libsojsoncpp/include/json/")

将之前生成的export文件夹删除,重新build发现是可以的。

OK,动态库可以编译成功,那么讲道理,静态库也是一样的,来尝试下编译静态库库。

编译a静态库

在以上编译so动态库的前提下,修改../cpp/jsoncpp/CMakeLists.txt如下:

cmake_minimum_required(VERSION 3.4.1)

set(CMAKE_VERBOSE_MAKEFILE on)

add_library(
        jsoncpp
        # 将 库 类型 由 SHARED 修改为 STATIC
        STATIC
        src/json_tool.h
        src/json_reader.cpp
        src/json_valueiterator.inl
        src/json_value.cpp
        src/json_writer.cpp
        src/version.h.in)

set(export_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../export)

set_target_properties(
        jsoncpp
        # 将 LIBRARY_OUTPUT_DIRECTORY 修改为 ARCHIVE_OUTPUT_DIRECTORY
        # 方便查看 生成的a文件目录修改一下 即 将 libsojsoncpp 修改为 libajsoncpp
        PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${export_dir}/libajsoncpp/lib/${ANDROID_ABI}")

add_custom_command(
        TARGET jsoncpp POST_BUILD
        COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_CURRENT_SOURCE_DIR}/src/json" "${export_dir}/libajsoncpp/include/json/"
)

修改完成后,Build/Make Project(或者 Make Module 'app') 会在 Project/export目录下生成:

这样编译.a静态库和.so动态库就完成了。

下篇我们讲如何链接 生成的so动态库和a静态库,以及动态库和静态库的区别;点击调转到下一篇:

CMake链接a静态库以及so动态库及动态库和静态库的区别

Demo链接:UseCmakeBuildLib


END