知其然,知其因此然;方能以不變,應其萬變。----張風捷特烈
java
前言
筆者看了一些NDK的項目。一些教程不是HelloWord就是直接整FFmpeg或OpenCV,可謂一個天一個地,並且目錄結構和Android3.5的默認結構並非太一致,一直沒找到什麼合心的文章。故寫此文鏈接這天地,來總結一下在NDK開發以前你應知道的東西。linux
在此以前,先劃分三類人,若是不認清本身是什麼角色(垃圾)就去玩NDK,你會很糟心:
user
: 純粹.so連接庫使用者(伸手黨)
creator
: 純粹ndk開發者,創做.so連接庫(創做家)
designer
: 在現有的.so上本身開發.so連接庫實現特定功能(程序設計師)android
本文內容
1.本文將以user、creator、designer三者的視角來看NDK
2.AndroidStdio3.5的默認目錄結構
3.有現成的C++代碼,如何讓Android調用它?
4.arm64-v8a、armeabi-v7a、x8六、x86_64分別是幹嗎的?
5.動態連接庫.so是什麼鬼,如何從c/c++生成.so?
6.libs,jniLibs,jin目錄到底該怎麼放?如何自定義文件放置的位置?
7.一些讓人糟心的異常
複製代碼
前置知識
也許你很怕C++,就像你
在新手村被3級的boss虐到心理陰影
,可是你如今已經50級了,還怕曾經虐你的3級的boss嗎? 建議閱讀: [- C++趣玩篇1 -] 從打印開始提及 ,這篇對本文很重要, 是簡單,也頗有趣。如今狀況如此:上篇中C++實現了一個打印臉的類,我想在Android中使用它。ios
純粹.so使用者
(User)當你只是單純的使用
動態連接庫.so
中的已有功能,也就是傳說中的伸手黨。
那你與NDK只是擦肩而過,並不須要理會C/C++,也不須要建立一個NDK的項目,甚至連JNI都有現成的。
你所須要作的只是在main下新建jniLibs
,通過測試,其爲默認的.so成放置地
,此時gradle文件你能夠一字不動。
c++
俗話說
拿人家手短,吃人家嘴軟
。因爲JNI是根據包名找到C/C++函數的,使用時必須和creator定義的接口徹底一致(包括包名)。算法
---->[com.toly1994.jni_creator.Facer]--by 張風捷特烈-----
package com.toly1994.jni_creator;
public class Facer {
public static native String getFacer( String top, String bottom, String brow, String eyes);
}
複製代碼
這個庫是等會要創造的,這裏先來演示。
System.loadLibrary
指定庫名
其中庫全名爲libtoly_facer-lib.so
,加載時toly_facer-lib
便可
這樣在上一篇[- C++趣玩篇1 -] 從打印開始提及中實現的打印類就能夠在Android中使用。bash
public class MainActivity extends AppCompatActivity {
static {//加載類庫
System.loadLibrary("toly_facer-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView=findViewById(R.id.hello);
textView.setTextSize(30);
//經過native接口getFacer使用類庫中C++方法
textView.setText(Facer.getFacer("-", "-", "~", "X"));
}
}
複製代碼
OK,如今80%的人問題解決了。(手動搞笑)微信
純粹ndk開發者
(Creator)若是你有
現成的C++代碼
想要直接用在Android上,或者想要手擼個什麼高效的框架
,
或者想要讓你的源碼不容易破解
,那麼廢話很少說,就開整吧。哥敬你是條好漢。
如今你須要建立一個Native C++
的Android項目。這裏就來實現toly_facer-lib
架構
上一篇中已經完成了C++類app
頭文件
--->[app/src/main/cpp/Facer.h]----
//
// Created by 張風捷特烈 on 2019/10/3.
//
#include <iostream>
using namespace std;
#ifndef TOLYC_FACER_H
#define TOLYC_FACER_H
class Facer {
public:
Facer(const string &top="#", const string &bottom="#", const string &brow="~", const string &eyes=".");
~Facer();
public:
string top;
string bottom;
string brow;
string eyes;
public:
void printFace() ;
string getFace();
};
#endif //TOLYC_FACER_H
複製代碼
cpp實現文件
--->[app/src/main/cpp/Facer.cpp]----
//
// Created by 張風捷特烈 on 2019/10/3.
//
#include "Facer.h"
Facer::Facer(
const string &top,
const string &bottom,
const string &brow,
const string &eyes) : top(top),
bottom(bottom),
brow(brow),
eyes(eyes) {}
void Facer::printFace() {
cout<< getFace() << endl;
}
Facer::~Facer() {
}
string Facer::getFace() {
string result;
for (int i = 0; i < 10; ++i) {
i != 9 ? result+=top : result+=top+"\n";
}
result+= "| " +brow + " " + brow + " |" +"\n";
result+= "| " +eyes + " " + eyes + " |" +"\n";
result+= "| -} |\n";
for (int i = 0; i < 10; ++i) {
i != 9 ? result+=bottom : result+=bottom+"\n";
}
return result;
}
複製代碼
新建
Native C++
的項目以後,main文件夾下會有cpp文件夾,這就是C++代碼的家
若是直接將兩個Facer文件拷貝進去,會飄紅。由於尚未在CmakeLists中進行配置
cmake_minimum_required(VERSION 3.4.1)
add_library(toly_facer-lib SHARED
toly_facer-lib.cpp Facer.h Facer.cpp)#直接加入文件
find_library(log-lib log)
target_link_libraries(toly_facer-lib ${log-lib})
複製代碼
固然也許你確定懶得一個個添加,能夠加載cpp文件夾下的全部.cpp和.c文件
cmake_minimum_required(VERSION 3.4.1)
#定義全局 my_source_path 變量
file(GLOB my_source_path
${CMAKE_SOURCE_DIR}/*.cpp
${CMAKE_SOURCE_DIR}/*.c)
add_library(toly_facer-lib
SHARED ${my_source_path})
find_library(log-lib log)
target_link_libraries(toly_facer-lib ${log-lib})
複製代碼
此方法所屬類名、包名對user都相當重要。對於creator隨意啦,就是任性
---->[src/main/java/com/toly1994/jni_creator/Facer.java]----
package com.toly1994.jni_creator;
public class Facer {
public static native String getFacer( String top, String bottom, String brow, String eyes);
}
複製代碼
C++與Java的相互做用,就是Java進行輸入,經
C++
轉化將有價值的東西傳給Java端
---->[src/main/cpp/toly_facer-lib.cpp]----
#include <jni.h>
#include <string>
#include "Facer.h"
extern "C"
JNIEXPORT jstring JNICALL
Java_com_toly1994_jni_1creator_Facer_getFacer(JNIEnv *env, jclass clazz, jstring top,
jstring bottom, jstring brow, jstring eyes) {
Facer facer(//使用 env->GetStringUTFChars將jstring轉化爲string
env->GetStringUTFChars(top, 0),
env->GetStringUTFChars(bottom, 0),
env->GetStringUTFChars(brow, 0),
env->GetStringUTFChars(eyes, 0)
);
return env->NewStringUTF(facer.getFace().c_str());
}
複製代碼
基本上流程就是這樣。
arm64-v8a、armeabi-v7a、x8六、x86_64
arm 架構注重的是續航能力:大部分的移動設備
x86 架構注重的是性能:大部分的臺式機和筆記本電腦
arm64-v8a :第8代、64位ARM處理器
armeabi-v7a :第7代及以上的 ARM處理器
x86:x86 架構的 CPU(Intel 的 CPU)
x86_64:x86 架構的64位 CPU(Intel 的 CPU)
複製代碼
默認會編譯出四種.so文件
能夠經過
app下的build.gradle
來指定編譯的.so類型
注意只有這四種類中
,之前不少項目中存在abiFilters 'armeabi'
但如今會崩
android {
defaultConfig {
externalNativeBuild {
cmake {
abiFilters 'armeabi-v7a', 'arm64-v8a'
}
}
}
複製代碼
這樣清一下項目,再編譯出來的只有
'armeabi-v7a', 'arm64-v8a'
此時運行到模擬器上,會發現找不到類庫,則說明模擬器去X86的。運行到真機無誤,則說明真機是arm的
.so
文件是什麼?若是說
.dll
估計你會說:哦,好像見過
。
其實.so
和.dll
並無本質的區別,它們都是一個C++實現的功能團
。
只不過.so
是用在linux上的,.dll
是用在Windows上的。
現在操做系統三足鼎立,固然少不了MacOS,相似的在MacOS中有.dylib
文件。
它們都是C++
的動態連接庫(Dynamic Link Library )
而Android做爲Linux的一員,
C++
編譯出的.so
即是瓜熟蒂落
那如何將C++編譯成.so庫
?這即是NDK在作的事,也是上面在作的事。
打包時gradle會將對應的.so包打到apk裏,而後.so就能在linux裏愉快的玩耍了。
我的建議
習慣優於配置
,用默認挺好的。若是你是很是有個性的...也能夠在gradle裏進行制定
雖然你也許不會用到,可是看一下,看到要認得,不至一臉蒙圈。
對於使用者,能夠隨意指定盛放.so
的文件夾,須要在app下的build.gradle
配置
android {
...
sourceSets {
main {
jniLibs.srcDirs = ['target']//指定so庫的位置,加載so庫
}
}
}
複製代碼
對於創造者,也可使用
jni.srcDirs
來指定C++代碼盛放的位置
sourceSets.main{
jni.srcDirs = ["src/main/cpp"]
}
複製代碼
俗話說
難的不是重寫,而是對爛代碼的重構
,有時候修改比創做更難
已有的.so文件
但功能上又須要定製,因而第三類就誕生了,也是最頭疼的
其實FFmpeg和OpenCV等都是這第三類,用已存在事物去構建新事物,即是設計。
算法和核心代碼已經實現,咱們須要作的是結合業務進行接口封裝及方法調用
這裏我就用OpenCV的使用來進行演示: 你須要建立的是Native C++項目
(Opencv下載什麼的,不廢話了,詳見:OpenCV專題1 - AndroidStudio的JNI工程及引用OpenCV)
這時,你是設計者,兼具創造者和使用者兩重角色。但比純粹的創造要簡單,比純粹的使用要難。
這時能夠經過CmakeLists
去連接到OpenCV的.so文件,這樣你就可使用OpenCV的頭文件進行功能實現
cmake_minimum_required(VERSION 3.4.1)
include_directories(include)#引入include文件夾
#定義全局 my_source_path 變量
# file(GLOB my_source_path ${CMAKE_SOURCE_DIR}/*.cpp ${CMAKE_SOURCE_DIR}/*.c)
aux_source_directory(. my_source_path) #上行的簡化:將本文件夾下文件加入
add_library(toly_cv SHARED ${my_source_path})
#添加動態連接庫
add_library(lib_opencv SHARED IMPORTED)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libopencv_java4.so) #so文件位置
## 在ndk中查找log庫 取別名log-lib
find_library(log-lib log)
# 在ndk中查找jnigraphics庫 取別名jnigraphics-lib jnigraphics
find_library(graphics jnigraphics)
target_link_libraries(
toly_cv
lib_opencv
jnigraphics
log
)
複製代碼
你能夠定義一個JNI接口來暴露你在C++層實現的方法,再打包成.so供他人使用
這即是開源的魅力,好比下面的灰色圖像,使用者能夠拿着打出的.so包經過TolyCV來使用
---->[com.toly1994.jni_designer.TolyCV]----
public class TolyCV {
public static native Bitmap grayBitmap(Bitmap bitmap,Bitmap.Config argb8888);
}
---->[src/main/cpp/toly_cv.cpp]----
#include <jni.h>
#include <string>
#include "bitmap_utils.h"
#include <opencv2/imgproc/types_c.h>
extern "C"
JNIEXPORT jobject JNICALL
Java_com_toly1994_jni_1designer_TolyCV_grayBitmap(JNIEnv *env, jclass clazz, jobject bitmap,jobject argb8888) {
Mat srcMat;
Mat dstMat;
bitmap2Mat(env, bitmap, &srcMat);
cvtColor(srcMat, dstMat, CV_BGR2GRAY);//將圖片的像素信息灰度化盛放在dstMat
return createBitmap(env,dstMat,argb8888);//使用dstMat建立一個Bitmap對象
}
複製代碼
筆者也並不是一路暢通無阻,走的坑也挺多,下面幾個坑來給你說說
仔細排查
CmakeLists
,多是.so文件的路徑不對
仔細排查
CmakeLists
,多是你的C++代碼文件路徑不對
說明你的庫加載異常,看看你的庫名有沒有寫對
說明你的JNI接口和.so比匹配,自行匹配放到相應包名下
待續...
因此,在決心奮戰NDK的時候,先認清本身是什麼角色(
垃圾
),纔好分類。
Creator太過遙遠,我就想作個安安靜靜的Designer。
我一直在找一篇這樣的文章,可是沒找到。因此本身寫了一篇,但願對你有所幫助。
我是張風捷特烈
,若是有什麼想要交流的,歡迎留言。也能夠加微信:zdl1994328