本篇基於上篇構建好的a靜態庫和so動態庫,若本身有a或so那麼能夠直接看本篇了,若沒有那麼建議先去看上篇----如何將現有的cpp代碼集成到項目中java
將so動態庫、a靜態庫、以及對應的頭文件,集中到一個文件夾中,本文由於是基於上篇的,那麼這些文件就放在了,以下圖:android
都放在了Project/export文件夾中,且在裏面將so和a分開,分別放在了,libajsoncpp和libsojsoncpp文件夾中,在每一個文件夾中,又有include文件夾來放庫所須要的頭文件,lib中放so以及a庫文件。c++
咱們首先來連接咱們較爲熟悉的so動態庫,而後再來連接a靜態庫。git
準備工做github
將../app/src/main/cpp文件夾中的jsoncpp文件夾刪除,以防咱們用的不是庫,而是…源碼了(針對按着第二篇 將 源碼拷貝到項目中的同窗)。json
將 ../app/src/main/cpp文件夾下的CMakeLists.txt內全部內容刪除,以防和本文的CMakeLists.txt中的配置不一樣。微信
將 ../app/build.gradle 修改以下:架構
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
}
buildTypes {
...
}
sourceSets {
main {
// 根據實際狀況具體設置,因爲本項目的lib放在 project/export/libsojsoncpp/lib 中 故如此設置
jniLibs.srcDirs = ['../export/libsojsoncpp/lib']
}
}
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
}
...
複製代碼
寫app/src/main/cpp/CMakeLists.txt文件app
cmake_minimum_required(VERSION 3.4.1)
# 設置變量 找到存放資源的目錄,".."表明上一級目錄
set(export_dir ${CMAKE_SOURCE_DIR}/../../../../export)
# 添加.so動態庫(jsoncpp)
add_library(lib_so_jsoncpp SHARED IMPORTED)
# 連接
set_target_properties(
# 庫名字
lib_so_jsoncpp
# 庫所在的目錄
PROPERTIES IMPORTED_LOCATION ${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}/libjsoncpp.so)
add_library(
native_hello
SHARED
native_hello.cpp
)
# 連接頭文件
target_include_directories(
native_hello
PRIVATE
# native_hello 須要的頭文件
${export_dir}/libsojsoncpp/include
)
# 連接項目中
target_link_libraries(
native_hello
android
log
# 連接 jsoncpp.so
lib_so_jsoncpp
)
複製代碼
嗯,此次看起來配置較多了,可是…,別慌 別慌 問題不大.jpg(自行腦部表情包) 咱們來一條一條的看ide
cmake_minimum_required(VERSION 3.4.1) 這個就不用解釋了吧,就是設置下CMake的最小版本而已。
set(....) 由於考慮到用到export 文件夾的次數較多,並且都是絕對路徑,因此就來設置一個變量來簡化啦。export_dir 就是變量的名字,${CMAKE_SOURCE_DIR} 是獲取當前CMakeLists.txt 所在的路徑,而後 一路 "../"去找到 咱們存放資源文件的 export 文件夾。
add_library(lib_so_jsoncpp SHARED IMPORTED) 這個見名之意啦,就是添加庫文件,後面的三個參數 "lib_so_jsoncpp" 庫名字;"SHARED" 由於咱們要導入 so 動態庫,因此就是 SHARED 了; "IMPORTED" 而後導入;
set_target_properties 接下來就是這句了,後面的參數較多,較長,就不拷貝到這裏了。咱們在 上句 已經添加庫了,可是…庫是空的呀(注意後面是 imported),什麼都沒有,只是一個名字 + 類型,因此接下來就得須要它來將名字和真實的庫連接起來,我已經在上面的CMakeLists.txt中寫上註釋了,這裏只說下在前面沒有提到過的"${ANDROID_ABI}",這是啥?上面的語句將此拼接到了裏面,可是我真實的路徑中沒有這個文件夾呀,去看下../libsojsoncpp/lib/下是啥,以下:
嗯啦,就是那一堆架構,因此…這個值就表明這些了(默認,所有類型)。
而後接下來就又是一個add_library 可是這個是帶資源的了。沒啥好說的了.
target_include_directories 咱們有庫了,可是沒有對應的頭文件咋行,因此這句就是連接庫對應的頭文件了。
target_link_libraries 最後將所需的頭文件,連接到項目中就能夠啦!
最後,Build/Make Module 'app'.
調用代碼
cpp層的代碼實際上是不用改,直接用咱們上次 拷貝 源碼的方式就行,可是爲了方便直接看本篇的同窗,仍是貼下 native_hello.cpp 內的代碼以下:
//
// Created by xong on 2018/9/28.
//
#include<jni.h>
#include <string>
#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());
}
複製代碼
對應的Java層代碼以下:
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);
}
複製代碼
調用代碼:
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", "so");
String parseJson = NativeFun.parseJsonCode(outPutJson);
tv_native_content.setText("生成的Json:\n" + outPutJson + "\n解析:" + parseJson);
}
}
複製代碼
結果:
嗯!集成成功,那麼下面咱們來集成下a靜態庫。
咱們仍是基於上面連接so動態庫的修改。
首先修改 ../app/build.gradle 文件以下:
apply plugin: 'com.android.application'
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {
arguments '-DANDROID_STL=c++_static'
}
}
}
...
// 刪除 或註釋
// sourceSets {
// main {
// jniLibs.srcDirs = ['../export/libsojsoncpp/lib']
// }
// }
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
}
複製代碼
只是將 集成 so時添加的 sourceSets 標籤刪除(或註釋啦!)。
其次修改 ../app/main/src/cpp/CMakeLists.txt 以下:
cmake_minimum_required(VERSION 3.4.1)
# 設置變量 找到存放資源的目錄,".."表明上一級目錄
set(export_dir ${CMAKE_SOURCE_DIR}/../../../../export)
# 添加.so動態庫(jsoncpp)
# add_library(lib_so_jsoncpp SHARED IMPORTED)
add_library(lib_a_jsoncpp STATIC IMPORTED)
# 連接
#set_target_properties(
# lib_so_jsoncpp
# PROPERTIES IMPORTED_LOCATION ${export_dir}/libsojsoncpp/lib/${ANDROID_ABI}/libjsoncpp.so)
set_target_properties(
lib_a_jsoncpp
PROPERTIES IMPORTED_LOCATION ${export_dir}/libajsoncpp/lib/${ANDROID_ABI}/libjsoncpp.a)
add_library(
native_hello
SHARED
native_hello.cpp
)
# 連接頭文件
#target_include_directories(
# native_hello
# PRIVATE
# # native_hello 須要的頭文件
# ${export_dir}/libsojsoncpp/include
#)
target_include_directories(
native_hello
PRIVATE
# native_hello 須要的頭文件
${export_dir}/libajsoncpp/include
)
# 連接項目中
target_link_libraries(
native_hello
android
log
# 連接 jsoncpp.so
# lib_so_jsoncpp
lib_a_jsoncpp
)
複製代碼
在上個集成so的配置上修改,如上,修改的地方都一 一 對應好了,基本上和集成so沒區別。
調用
Java層不需修改,調用時傳的參數以下:
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", "a");
String parseJson = NativeFun.parseJsonCode(outPutJson);
tv_native_content.setText("生成的Json:\n" + outPutJson + "\n解析:" + parseJson);
}
}
複製代碼
結果:
能夠看到type變成了 "a",這樣的話,靜態庫也就算集成成功了。
有的人會說了,你這都用的json,並且返回 type 是你傳進去的呀,你就是集成 so 傳 a 那麼就算集成a了?
另外一個咱們怎麼會知道打到apk中的是so動態庫,仍是a靜態庫呢?不是都說了麼,Android中只支持調用so動態庫,不支持a靜態庫的,那麼這…集成a靜態庫這不是扯淡麼?
OK,接下來就來解釋這一系列的問題,首先咱們要知道什麼是靜態庫什麼又是動態庫。
參考Linux下的庫
抽取出主要的:
靜態庫
連接時間: 靜態庫的代碼是在編譯過程當中被載入程序中;
連接方式: 目標代碼用到庫內什麼函數,就去將該函數相關的數據整合進目標代碼;
優勢: 是在編譯後的執行程序不在須要外部的函數庫支持;
缺點: 若是所使用的靜態庫發生更新改變,程序必須從新編譯。
動態庫
連接時間: 動態庫在編譯的時候並無被編譯進目標代碼,而是在運行時用到該庫中函數時纔去調用;
連接方式: 動態連接,用到該庫內函數時就去加載該庫;
優勢: 動態庫的改變並不影響程序,即不須要從新編譯;
缺點: 由於函數庫並無整合進程序,因此程序的運行環境必須依賴該庫文件。
再精簡一點:
靜態庫是一堆cpp文件,每次都須要編譯才能運行,在本身的代碼中用到哪一個,就從這一堆cpp中取出本身須要的進行編譯;
動態庫是將一堆cpp文件都編譯好了,運行的時候不會對cpp進行從新編譯,當須要到庫中的某個函數時,就會將庫加載進來,不須要時就不用加載,前提,這個庫必須存在。
因此,就能夠回答上述的疑問了,對,沒錯Android的確是只能調用so動態庫,咱們的集成的a靜態庫,用到靜態庫中的函數時,就會去靜態庫中拿對應的元數據,而後將這些數據再打入到打入到咱們的最終要調用的so動態庫中,在上述中就是native_hello.so了。
而後咱們在集成so動態庫時在../app/build.gradle 中加了一個標籤哈,以下:
sourceSets {
main {
jniLibs.srcDirs = ['../export/libsojsoncpp/lib']
}
}
複製代碼
通過上面的解釋,再來理解這句就不難了,上面已經說過了:
當須要到庫中的某個函數時,就會將庫加載進來,不須要時就不用加載,前提,這個庫必須存在。
因此啦,native_hello.so依賴於jsoncpp.so,即jsoncpp.so必須存在,那麼加這個的意思就是,將jsoncpp.so打入apk中。咱們能夠將上面的集成so動態庫的apk用 jadx 查看一下,以下:
上面的結論沒錯咯,裏面確實有兩個so,一個jsoncpp.so另外一個就是咱們本身的native_hello.so;
那麼咱們再來看一下集成a靜態庫的吧!以下:
這就是區別啦!
so方式,只要你代碼中涉及了,那麼它就要存在,即便你只是調用,後續不使用它,它也存在。
a方式,只須要在編碼過程當中,保持它存在就好,用幾個函數,就從a中取幾個函數。
jstring name = "xong";
const char* ccp_name = env->GetStringUTFChars(name, NULL);
env->ReleaseStringUTFChars(name, ccp_name);
複製代碼
現象:
寫JNI時這兩個不陌生吧,不少人都會說,用了GetStringUTFChars 必須 調用 ReleaseStringUTFChars 講資源釋放掉,但 調用完ReleaseStringUTFChars會發現 ccp_name 還能夠訪問,即並無釋放掉資源。
問題:
歡迎在下方的評論進行探討。
原本想將這一篇分紅三篇來寫的,想了又想,Android開發嘛,不必對native很那麼瞭解,因此就壓縮壓縮,壓縮成一篇了。
在這三篇中,咱們發現,寫CMakeLists.txt也不是那麼很麻煩,並且不少都是重複的,都是能夠用腳原本生成的,好比 add_library 中添加的資源文件,固然其餘的也同樣啦,那麼這個 CMakeLists.txt 是否是能夠寫一個小腳本呢?我感受能夠。
另外一個,如何用Camke構建a靜態庫、so動態庫,以及如何集成,在Google的sample中都有的,再貼一下連接:android_ndk , 並且添加的時間也挺長的了,可是,百度到的文章仍是 5年前的,真的是…不知道說啥了,仍是多看些Google Github比較好。哈哈哈哈~
最後,上述三篇文章中涉及的源碼均已上傳到GitHub,連接:UseCmakeBuildLib
END