# For more information about using CMake with Android Studio, read the # documentation: https://d.android.com/studio/projects/add-native-code.html # Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.4.1) # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. # Gradle automatically packages shared libraries with your APK. add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp ) # Searches for a specified prebuilt library and stores the path as a # variable. Because CMake includes system libraries in the search path by # default, you only need to specify the name of the public NDK library # you want to add. CMake verifies that the library exists before # completing its build. find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) # Specifies libraries CMake should link to your target library. You # can link multiple libraries, such as libraries you define in this # build script, prebuilt third-party libraries, or system libraries. target_link_libraries( # Specifies the target library. native-lib # Links the target library to the log library # included in the NDK. ${log-lib} )
上面的完成的有注釋的內容,但其中最核心的也就幾句,下面分別做介紹:
cmake_minimum_required(VERSION 3.4.1)
用來設置在編譯本地庫時我們需要的最小的cmake版本,AndroidStudio自動生成,我們幾乎不需要自己管。
add_library( # Sets the name of the library. native-lib # Sets the library as a shared library. SHARED # Provides a relative path to your source file(s). src/main/cpp/native-lib.cpp )
add_library
用來設置編譯生成的本地庫的名字為native-lib
,SHARED
表示編譯生成的是動態鏈接庫
(這個概念前面已經提到過了),src/main/cpp/native-lib.cpp
表示參與編譯的文件的路徑,這里面可以寫多個文件的路徑。
find_library
是用來添加一些我們在編譯我們的本地庫的時候需要依賴的一些庫,由於cmake已經知道系統庫的路徑,所以我們這里只是指定使用log
庫,然后給log
庫起別名為log-lib
便於我們后面引用,此處的log
庫是我們后面調試時需要用來打log日志的庫,是NDK為我們提供的。
target_link_libraries
是為了關聯我們自己的庫和一些第三方庫或者系統庫,這里把我們把自己的庫native-lib
庫和log
庫關聯起來。
NDK自定義配置
1 . 添加多個參與編譯的C/C++文件
首先,我們發現我們上面的例子都是涉及到一個C++文件,那么我們實際的項目不可能只有一個C++文件,所以我們首先要改變CMakeLists.txt
文件,如下 :
add_library( HelloNDK
SHARED
src/main/cpp/HelloNDK.c src/main/cpp/HelloJNI.c)
簡單吧,簡單明了,但是這里要注意的是,你在寫路徑的時候一定要注意當前的CMakeLists.txt
在項目中的位置,上面的路徑是相對於CMakeLists.txt
寫的。
2 . 我們想編譯出多個so庫
大家會發現,我們上面這樣寫,由於只有一個CMakeLists.txt
文件,所以我們會把所有的C/C++文件編譯成一個so庫,這是很不合適的,這里我們就試着學學怎么編譯出多個so庫。
先放上我的項目文件夾結構圖:
然后看看我們每個CMakeLists.txt
文件是怎么寫的:
one文件夾內的CMakeLists.txt
文件的內容:
ADD_LIBRARY(one-lib SHARED one-lib.c) target_link_libraries(one-lib log)
two文件夾內的CMakeLists.txt
文件的內容:
ADD_LIBRARY(two-lib SHARED two-lib.c) target_link_libraries(two-lib log)
app目錄下的CMakeLists.txt
文件的內容
# Sets the minimum version of CMake required to build the native library. cmake_minimum_required(VERSION 3.4.1) add_library( HelloNDK SHARED src/main/cpp/HelloNDK.c src/main/cpp/HelloJNI.c) find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log ) target_link_libraries(HelloNDK log) ADD_SUBDIRECTORY(src/main/cpp/one) ADD_SUBDIRECTORY(src/main/cpp/two)
通過以上的配置我們可以看出CMakeLists.txt
文件的配置是支持繼承的,所以我們在子配置文件中只是寫了不同的特殊配置項的配置,最后在最上層的文件中配置子配置文件的路徑即可,現在編譯項目,我們會在 <項目目錄>\app\build\intermediates\cmake\debug\obj\armeabi 下面就可以看到生成的動態鏈接庫。而且是三個動態鏈接庫
3 . 更改動態鏈接庫生成的目錄
我們是不是發現上面的so庫的路徑太深了,不好找,沒事,可以配置,我們只需要在頂層的CMakeLists.txt
文件中加入下面這句就可以了
#設置生成的so動態庫最后輸出的路徑 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI})
然后我們就可以在app/src/main下看到jniLibs
目錄,在其中看到我們的動態鏈接庫的文件夾和文件(這里直接配置到了系統默認的路徑,如果配置到其他路徑需要在gradle文件中使用jinLibs.srcDirs = ['newDir']
進行指定)。
NDK錯誤調試
在開發的過程中,難免會遇到bug,那怎么辦,打log啊,下面我們就談談打log和看log的姿勢。
1 . 在C/C++文件中打log
(1) 在C/C++文件中添加頭文件
#include <android/log.h>
- 1
上面是打印日志的頭文件,必須添加
(2) 添加打印日志的宏定義和TAG
//log定義 #define LOG "JNILOG" // 這個是自定義的LOG的TAG #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG,__VA_ARGS__) // 定義LOGD類型 #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG,__VA_ARGS__) // 定義LOGI類型 #define LOGW(...) __android_log_print(ANDROID_LOG_WARN,LOG,__VA_ARGS__) // 定義LOGW類型 #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG,__VA_ARGS__) // 定義LOGE類型 #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,LOG,__VA_ARGS__) // 定義LOGF類型
上面的日志級別和Android中的log是對應的。
(3) 經過上面兩步,我們就可以打印日志啦
int len = 5; LOGE("我是log %d", len);
現在我們就可以在logcat中看到我們打印的日志啦。
2 . 查看報錯信息
首先我們先手動寫一個錯誤,我們在上面的C文件中找一個函數,里面寫入如下代碼:
int * p = NULL; *p = 100;
上面是一個空指針異常,我們運行程序,發現崩潰了,然后查看控制台,只有下面一行信息:
libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 17481
完全看不懂上面的信息好吧,這個也太不明顯了,下面我們就學習一下如何將上面的信息變得清楚明了
我們需要用到是ndk-stack
工具,它在我們的ndk根目錄下,它可以幫助我們把上面的信息轉化為更為易懂更詳細的報錯信息,下面看看怎么做:
(1) 打開AndroidStudio中的命令行,輸入adb logcat > log.txt
上面這句我們是使用adb命令捕獲log日志並寫入log.txt文件,然后我們就可以在項目根目錄下看到log.txt文件
(2) 將log.txt打開看到報錯信息,如下:
F/libc (17481): Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 17481 (dekong.ndkdemo1) I/DEBUG ( 67): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** I/DEBUG ( 67): Build fingerprint: 'generic/vbox86p/vbox86p:5.0/LRX21M/genymotion08251046:userdebug/test-keys' I/DEBUG ( 67): Revision: '0' I/DEBUG ( 67): ABI: 'x86' I/DEBUG ( 67): pid: 17481, tid: 17481, name: dekong.ndkdemo1 >>> com.codekong.ndkdemo1 <<< I/DEBUG ( 67): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 I/DEBUG ( 67): eax 00000000 ebx f3494fcc ecx ffa881a0 edx 00000000 I/DEBUG ( 67): esi f434e2b0 edi 00000000 I/DEBUG ( 67): xcs 00000023 xds 0000002b xes 0000002b xfs 00000007 xss 0000002b I/DEBUG ( 67): eip f3492a06 ebp ffa88318 esp ffa88280 flags 00210246 I/DEBUG ( 67): I/DEBUG ( 67): backtrace: I/DEBUG ( 67): #00 pc 00000a06 /data/app/com.codekong.ndkdemo1-2/lib/x86/libHelloNDK.so (Java_com_codekong_ndkdemo1_MainActivity_updateFile+150) I/DEBUG ( 67): #01 pc 0026e27b /data/dalvik-cache/x86/data@app@com.codekong.ndkdemo1-2@base.apk@classes.dex I/DEBUG ( 67): #02 pc 9770ee7d <unknown> I/DEBUG ( 67): #03 pc a4016838 <unknown> I/DEBUG ( 67): I/DEBUG ( 67): Tombstone written to: /data/tombstones/tombstone_05
現在的報錯信息還是看不懂,所以我們需要使用ndk-stack
轉化一下:
(3) 繼續在AndroidStudio中的命令行中輸入如下命令(在這之前,我們必須要將ndk-stack的路徑添加到環境變量,以便於我們在命令行中直接使用它)
ndk-stack -sym app/build/intermediates/cmake/debug/obj/x86 -dump ./log.txt
上面的-sym
后面的參數為你的對應平台(我是Genymotion模擬器,x86平台)的路徑,如果你按照上面的步驟改了路徑,那就需要寫改過的路徑,-dump
后面的參數就是我們上一步得出的log.txt文件,執行結果如下:
********** Crash dump: ********** Build fingerprint: 'generic/vbox86p/vbox86p:5.0/LRX21M/genymotion08251046:userdebug/test-keys' pid: 17481, tid: 17481, name: dekong.ndkdemo1 >>> com.codekong.ndkdemo1 <<< signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0 Stack frame I/DEBUG ( 67): #00 pc 00000a06 /data/app/com.codekong.ndkdemo1-2/lib/x86/libHelloNDK.so (Java_com_codekon g_ndkdemo1_MainActivity_updateFile+150): Routine Java_com_codekong_ndkdemo1_MainActivity_updateFile at F:\AndroidFirstCode\NDK Demo1\app\src\main\cpp/HelloJNI.c:32 Stack frame I/DEBUG ( 67): #01 pc 0026e27b /data/dalvik-cache/x86/data@app@com.codekong.ndkdemo1-2@base.apk@classes.d ex Stack frame I/DEBUG ( 67): #02 pc 9770ee7d <unknown>: Unable to open symbol file app/build/intermediates/cmake/debug/ obj/x86/<unknown>. Error (22): Invalid argument Stack frame I/DEBUG ( 67): #03 pc a4016838 <unknown>: Unable to open symbol file app/build/intermediates/cmake/debug/ obj/x86/<unknown>. Error (22): Invalid argument Crash dump is completed
尤其是上面的一句:
g_ndkdemo1_MainActivity_updateFile+150): Routine Java_com_codekong_ndkdemo1_MainActivity_updateFile at F:\AndroidFirstCode\NDK Demo1\app\src\main\cpp/HelloJNI.c:32
准確指出了發生錯誤的行數