折騰了幾乎一天,記錄一下。
最大的坑是官網給出的
1.進入ceres源代碼目錄下的jni目錄
2.EIGEN_PATH="指向eigen庫目錄,即包含EIGEN文件夾的那個文件夾” ndk-build
這方法的確編譯出了一個近700M的靜態庫,但使用時總是未定義鏈接錯誤。現在想想跟以前編譯OpenCV時遇到的錯誤相似,
解決的辦法也相似,通過cmake調用ndk的工具鏈編譯。
我編譯動態庫的命令
/home/hk/Android/Sdk/cmake/3.10.2.4988404/bin/cmake -DCMAKE_TOOLCHAIN_FILE=/home/hk/Android/Sdk/ndk/20.0.5594570/build/cmake/android.toolchain.cmake -DEIGEN_INCLUDE_DIR=/home/hk/ndk/eigen-git-mirror-3.3.6 -DANDROID_ABI=arm64-v8a -DANDROID_STL=c++_shared -DANDROID_NATIVE_API_LEVEL=android-27 -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -DBUILD_EXAMPLES=OFF -DMINIGLOG=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-s" /home/hk/ndk/ceres-solver-master
注意一定要在一行,否則可能配置錯誤,生成Lunix格式的庫。可先在文本文檔里寫好,然后復制到終端。這里用到的cmake是android studio安裝的
,本來裝的是3.5版本,這里要求高版本。
詳細的解釋下
#這部分是指定工具鏈,工具鏈在ndk里
-DCMAKE_TOOLCHAIN_FILE=/home/hk/Android/Sdk/ndk/20.0.5594570/build/cmake/android.toolchain.cmake
#這部分指定Eigen庫路徑,要求3.3.4以上
-DEIGEN_INCLUDE_DIR=/home/hk/ndk/eigen-git-mirror-3.3.6
#這部分指定abi,我是arm64-v8a
-DANDROID_ABI=arm64-v8a
#這部分指定STL,這里可能要跟項目用的一致,就是要跟項目編譯c++文件的那個STL一致,若選擇c++_static應該也可以,一致就行,項目選擇STL在
#gradle里設置,這在下面介紹
-DANDROID_STL=c++_shared
#指定android版本27對應android 8.1.0
-DANDROID_NATIVE_API_LEVEL=android-27
#ON就是動態庫,OFF就是靜態庫
-DBUILD_SHARED_LIBS=ON
#不編譯測試,沒意義,不可能在LINUX上運行的,庫是android的格式
-DBUILD_TESTING=OFF
#不編譯列子
-DBUILD_EXAMPLES=OFF
#不依賴glog源碼了,這里是重點,這里選擇ON會使用ceres目錄下/ceres-solver-master/internal/ceres/miniglog里的代碼代替glog
#當庫放入android項目時需要將這個目錄里的頭文件也加入項目,比如可直接將這個目錄加入項目的頭文件路徑
-DMINIGLOG=ON
#非調試版本
-DCMAKE_BUILD_TYPE=Release
#不懂
-DCMAKE_C_FLAGS="-s"
#指定ceres目錄,即CMakeLists.txt所在的目錄路徑
/home/hk/ndk/ceres-solver-master
配置成功的特點是
.........
-- Check for working C compiler: /home/hk/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/clang
-- Check for working C compiler: /home/hk/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/clang -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /home/hk/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++
-- Check for working CXX compiler: /home/hk/Android/Sdk/ndk/20.0.5594570/toolchains/llvm/prebuilt/linux-x86_64/bin/clang++ -- works
.........
即cmake選擇的默認編譯器是clang和clang++
配置好后sudo make
生成好后將.so庫放入app下的libs里,我是新建arm64-v8a文件夾,然后放在里面的。
libs里新建include,頭文件都放在這里。重點是,差個 config.h,cmake生成庫的那個目錄里搜索下,有個新生成的config.h,將其放到指定地方即可。指定的地方錯誤提示會指出。
注意,這個libs文件夾沒有跟jniLibs相關聯,里面的.so不會放入.apk,關聯在gradle里設置,下面會給出完整的配置。
看看libs文件夾的內容
gradle配置
apply plugin: 'com.android.application' android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "com.example.myapplication" minSdkVersion 27 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" //項目的ndk配置需和使用的庫一致,項目的配置在這里設置 externalNativeBuild { cmake { cppFlags "-std=c++11" //arguments '-DANDROID_STL=c++_static' arguments '-DANDROID_STL=c++_shared' abiFilters "arm64-v8a" } } //指定jniLibs目錄只有這個目錄里的.so動態庫會被加載到.apk sourceSets { main { jniLibs.srcDirs = ['libs'] } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } externalNativeBuild { cmake { path "src/main/cpp/CMakeLists.txt" version "3.10.2" } } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.constraintlayout:constraintlayout:1.1.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' }
cmake配置
# Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.4.1) #要求支持c++11 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) #設置第三方庫目錄路徑參數 set(libs_path ${CMAKE_SOURCE_DIR}/../../../libs) #引入glog頭文件 include_directories( ${libs_path}/include ) ##########添加Ceres靜態庫 begin######### add_library( ceres #庫名字 去掉了 lib 與 .a SHARED #必須的 IMPORTED #必須的 ) #指定Ceres靜態庫位置 set_target_properties( ceres PROPERTIES IMPORTED_LOCATION ${libs_path}/arm64-v8a/libceres.so ) #引入Ceres的頭文件 include_directories( ${libs_path}/include ) ##########添加Ceres靜態庫 end######### ##########添加Eigen庫 begin######### #引入Eigen的頭文件 include_directories( ${libs_path}/include ) ##########添加Eigen庫 begin######### ##########添加添加native-lib動態庫 begin######### add_library( native-lib SHARED native-lib.cpp ) #查找要鏈接的android log庫 find_library( log-lib log ) #鏈接庫 target_link_libraries( native-lib ${log-lib} ceres #openmap庫 #若編譯Ceres時使用了 -DCERES_USE_OPENMP #則在使用Ceres時必須鏈接上OpenMap庫 #omp ) ##########添加添加native-lib動態庫 end#########
測試代碼就是ceres的hello ceres示列,將其放入jni函數里
#include <jni.h> #include <string> #include "ceres/ceres.h" #include "glog/logging.h" using ceres::AutoDiffCostFunction; using ceres::CostFunction; using ceres::Problem; using ceres::Solver; using ceres::Solve; // A templated cost functor that implements the residual r = 10 - // x. The method operator() is templated so that we can then use an // automatic differentiation wrapper around it to generate its // derivatives. struct CostFunctor { template <typename T> bool operator()(const T* const x, T* residual) const { residual[0] = 10.0 - x[0]; return true; } }; extern "C" JNIEXPORT jstring JNICALL Java_com_example_myapplication_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { std::string hello = "Hello Ceres"; // The variable to solve for with its initial value. It will be // mutated in place by the solver. double x = 0.5; const double initial_x = x; // Build the problem. Problem problem; // Set up the only cost function (also known as residual). This uses // auto-differentiation to obtain the derivative (jacobian). CostFunction* cost_function = new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor); problem.AddResidualBlock(cost_function, NULL, &x); // Run the solver! Solver::Options options; options.minimizer_progress_to_stdout = true; Solver::Summary summary; Solve(options, &problem, &summary); std::cout << summary.BriefReport() << "\n"; std::cout << "x : " << initial_x << " -> " << x << "\n"; return env->NewStringUTF(summary.BriefReport().c_str()); }