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

xong發表於2018-09-30

雖然本篇和上篇沒很大的關係,但…還是建議先去看下上篇----初次使用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. 最終如下:

    關於在Android中使用CMake你所需要了解的一切(二)
  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一下

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

    What Are you!!!出錯了,告訴在json_tool.h的第10行出錯了,那行吧,點進去瞅一眼,如下:

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

    哦,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);
        }
    }
    複製程式碼

    結果如下圖:

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

編譯成庫檔案

OK,整合成功,但是太複雜,太麻煩了,每次要寫那麼一堆配置檔案,少一個都不行,還要挨個的去改 include 想想都可怕,那麼可不可以把這個jsoncpp打成庫呢?那麼我們就要考慮如下:

  1. 必須使用CMake,不使用編寫mk的方式;
  2. 在任何系統上都可以,不可在編譯庫的時候切換到其他系統;

好吧,基於以上兩點,百度搜了一波,發現…GG,沒有符合的哎,用CMake就得去Linux下面,且需要自己構建工具鏈,要不然就是…mk…。再去Google搜一搜,發現還是這樣的,難道,不存在?再去GitHub搜,發現沒有相關的,無可奈何,去Google提供的sample中找一找吧,哈!還真有發現,連結:hello-libs

編譯so動態庫

  1. 修改cpp目錄為:

    關於在Android中使用CMake你所需要了解的一切(二)
  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動態庫。編譯後如下:

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

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目錄下生成:

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

這樣編譯.a靜態庫和.so動態庫就完成了。

下篇我們講如何連結 生成的so動態庫和a靜態庫,以及動態庫和靜態庫的區別;點選調轉到下一篇:

CMake連結a靜態庫以及so動態庫及動態庫和靜態庫的區別

Demo連結:UseCmakeBuildLib


END

相關文章