起因
我這個人喜歡沒事就去招聘網站刷刷招聘信息,也不全是為了找工作峭咒,因為我個人覺得從招聘信息中税弃,我可以了解到行業(yè)的大方向,了解現(xiàn)在市場上的用人單位都需要一個程序員具備什么樣的素質和技能讹语,程序員這個職業(yè)钙皮,一輩子都需要學習和進步蜂科,如果技術固步自封顽决,不深入底層或者不拓展技術視野短条,那么成為一個十年開發(fā)一年經驗的程序員就不遠了,于是我打開了招聘網站才菠,隨便刷了刷就發(fā)現(xiàn)了問題茸时。
問題就是,對于初級程序員赋访,市面上的公司要求不多可都,除了BAT,基本上回寫bug就行蚓耽,但是如果到了一定歲數(shù)渠牲,你想找一些中高級的Android程序員的工作時,市面上的公司就會有一些特殊的需求了步悠,這需求其實就是門檻签杈,現(xiàn)在我貼幾張招聘簡章的截圖,大家看看:
需求各式各樣鼎兽,但是我發(fā)現(xiàn)一個共同的問題答姥,都需要NDK或者C/C++的技能,看來NDK是未來一個學習的熱點和趨勢谚咬,不僅音視頻鹦付、各種硬件的調用、核心算法的實現(xiàn)以及一些照片的處理都需要用到JNI開發(fā)择卦。
可能你會問了敲长,我是個Java程序員,學這C++開發(fā)相當于跨行了秉继,其實也對潘明,Android都沒整明白呢,就跑去弄什么NDK,但是還是那句老話秕噪,技多不壓身钳降,多學習學習接觸一下肯定沒壞處,最起碼簡單的NDK開發(fā)框架會搭建腌巾,知道一些基本的配置和用法遂填,就算以后項目里用到了基礎的配置和調試也能用得到啊。
開始搭建NDK
我用的AndroidStudio的版本是3.2.1澈蝙,在創(chuàng)建Project時如果選中了支持C++吓坚,AndroidStudio就會自動去網上幫我們下載NDK的開發(fā)環(huán)境,具體情況可以通過File->Setting->Appearance & Behavior->System Setting ->Android SDK->SDK Tools查看灯荧,主要看的就是NDK和CMake兩項是否安裝:
之后就新建項目礁击,選擇C++選項:
然后選擇SDK版本,這個隨意:
在向導的 Customize C++ Support 部分,您可以使用下列選項自定義項目:
C++ Standard:使用下拉列表選擇您希望使用哪種 C++ 標準哆窿。選擇 Toolchain Default 會使用默認的 CMake 設置链烈。
Exceptions Support:如果您希望啟用對 C++ 異常處理的支持,請選中此復選框挚躯。如果啟用此復選框强衡,Android Studio 會將 -fexceptions 標志添加到模塊級 build.gradle 文件的 cppFlags 中,Gradle 會將其傳遞到 CMake码荔。
Runtime Type Information Support:如果您希望支持 RTTI漩勤,請選中此復選框。如果啟用此復選框缩搅,Android Studio 會將 -frtti 標志添加到模塊級 build.gradle 文件的 cppFlags 中越败,Gradle 會將其傳遞到 CMake。
之后就可以看到自己項目的目錄結構和平時的Android項目有所不同:
仔細觀察可以發(fā)現(xiàn)硼瓣,多了一個cpp包眉尸,而且還多了個CMakeLists.txt,打開local.properties還會發(fā)現(xiàn)多了個參數(shù)巨双,其實就是ndk的指向路徑:
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Wed Apr 03 09:21:33 CST 2019
ndk.dir=E\:\\AndroidSDK\\adt-bundle-windows-x86_64-20140321\\sdk\\ndk-bundle
sdk.dir=E\:\\AndroidSDK\\adt-bundle-windows-x86_64-20140321\\sdk</pre>
在 cpp 組中噪猾,您可以找到屬于項目的所有原生源文件、標頭和預構建庫筑累。對于新項目袱蜡,Android Studio 會創(chuàng)建一個示例 C++ 源文件 native-lib.cpp,并將其置于應用模塊的 src/main/cpp/ 目錄中慢宗。本示例代碼提供了一個簡單的 C++ 函數(shù) stringFromJNI()坪蚁,此函數(shù)可以返回字符串“Hello from C++”。
在 External Build Files 組中镜沽,您可以找到 CMake 或 ndk-build 的構建腳本敏晤。與 build.gradle 文件指示 Gradle 如何構建應用一樣,CMake 和 ndk-build 需要一個構建腳本來了解如何構建您的原生庫缅茉。對于新項目嘴脾,Android Studio 會創(chuàng)建一個 CMake 構建腳本 CMakeLists.txt,并將其置于模塊的根目錄中蔬墩。
我們來看看這個文件:
有關使用CMake在Android Studio的更多信息,請閱讀文檔:https://d.android.com/studio/projects/add-native-code.html
?
好了译打,至此,我們的NDK就算是配置完成了拇颅,現(xiàn)在開始嘗試加入OpenCV奏司。
引入OpenCV
OpenCV是一個基于BSD許可(開源)發(fā)行的跨平臺計算機視覺庫,可以運行在Linux樟插、Windows韵洋、Android和Mac OS操作系統(tǒng)上竿刁。它輕量級而且高效——由一系列 C 函數(shù)和少量 C++ 類構成,同時提供了Python搪缨、Ruby食拜、MATLAB等語言的接口,實現(xiàn)了圖像處理和計算機視覺方面的很多通用算法勉吻。
知道了概念,現(xiàn)在我們來引入它
第一步:下載OpenCV的Android包
最新版是4.0.1旅赢,然后我們點擊那個Android pack齿桃,會下載好一個壓縮包,解壓縮后是這樣的:
include文件
在下載好的OpenCV壓縮包中煮盼,打開路徑下的 OpenCV-android-sdk\sdk\native\jni 有一個include文件夾短纵,把這個文件夾復制粘貼至我們的OpenCVTest項目中,路徑為src/main/cpp
jni文件
然后是動態(tài)庫(.so文件)僵控,打開路徑下的 OpenCV-android-sdk\sdk\native 香到,有一個libs 文件夾,這個文件夾里面是所有版本的abi的so文件报破。復制粘貼到我們的項目中悠就,路徑為 src/main/jniLibs 這個文件夾需要自己手動去創(chuàng)建。
注意:
無論是include還是libs的路徑都可以自定義充易,習慣上是這樣放梗脾,但其實只要在之后的CMakeList配置文件里面設置正確就沒有問題。
值得一提的是盹靴,OpenCV在最新版本中把動態(tài)庫和靜態(tài)庫分開了炸茧,分別放在libs和staticlbs兩個文件夾中,之前是放在一個文件夾里的稿静。我們測試Demo僅需要動態(tài)庫和頭文件即可梭冠。
最后配置好之后文件結構如圖所示(注意,這是Project方式看的):
配置Gradle
最終的配置文件如下:
apply plugin: 'com.android.application'
?
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.dhcc.www.ndkapplication"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
externalNativeBuild {
cmake {
cppFlags "-std=c++11 -frtti -fexceptions"
abiFilters 'armeabi-v7a'
}
}
ndk{
abiFilters 'armeabi-v7a'
}
}
sourceSets{
main{
jniLibs.srcDirs = ['src/main/jniLibs/libs']
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
ndk{
abiFilters 'armeabi-v7a'
}
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
?
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
}
這里有幾點要講一下:
externalNativeBuild {
cmake {
cppFlags "-std=c++11 -frtti -fexceptions"
abiFilters 'armeabi-v7a'
}
}
ndk{
abiFilters 'armeabi-v7a'
}
abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'</pre>
cppFlags是配置預處理的設置改备,abiFilters是控制系統(tǒng)查找.SO的文件類型的過濾器控漠,不同的CPU架構對應不同的.so文件,abiFilters關鍵字能夠指定Android 所支持的CPU架構悬钳,一般是以上4種润脸,最終這些.so文件會被打包進APK,所以可以根據(jù)自己的項目進行選擇他去,比如在AS模擬器上開發(fā)APP選一個 x86 就可以了毙驯,如果是手機端,一般是arm架構灾测,選 armeabi-v7a 即可爆价。
然后同步項目即可垦巴。
CMakeList.txt文件
這個文件也是NDK開發(fā)最最最關鍵的文件,AS采用CMake腳本語法配置C編譯器的環(huán)境铭段,如果你之前有過使用CMAKE的經驗骤宣,或許這并非難題,但對于初學者而言序愚,CMAKE的腳本語法憔披,還是略過于生澀,而且AS對該文件的配置并不友好爸吮,居然沒有代碼提示芬膝,于是不得不查很多文檔。但好在形娇,NDK的開發(fā)大多不是大型的C++項目锰霜,也不太需要過于復雜的設置(比如OpenCV源代碼的CMAKE文件,大約有幾千行的樣子 Orz~)
OpenCV配置CMakeList文件的方式如下:
# documentation: https://d.android.com/studio/projects/add-native-code.html
?
# 設置CMAKE的版本號
cmake_minimum_required(VERSION 3.4.1)
?
# 設置include文件夾的地址
include_directories(${CMAKE_SOURCE_DIR}/src/main/cpp/include)
?
# 設置opencv的動態(tài)庫
add_library(libopencv_java4 SHARED IMPORTED)
set_target_properties(libopencv_java4 PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/src/main/jniLibs/libs/${ANDROID_ABI}/libopencv_java4.so)
?
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 )
?
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( # Specifies the target library.
native-lib libopencv_java4
?
# Links the target library to the log library
# included in the NDK.
${log-lib} )
include_directories 函數(shù)設置了include文件夾的路徑 add_library 函數(shù)設置庫名和類型桐早,其中l(wèi)ibopencv_java3 是用戶自定義的變量名癣缅,前后保持一致即可,SHARE 表示引入的庫是動態(tài)鏈接庫 set_target_properties 設置了OpenCV的動態(tài)鏈接庫的路徑 target_link_libraries 具有依賴關系的動態(tài)庫鏈接到指定目標上哄酝,鏈接順序需符合gcc鏈接規(guī)則友存,這里我們把libopencv_java4和log鏈接到了native-lib上。 更詳細的CMAKE配置語法陶衅,可以參考CMAKE官方文檔爬立,我們這里僅配置動態(tài)鏈接庫,以上四個函數(shù)足夠了万哪。
放入指定圖片
將上圖存儲后放入路徑:src/main/res/drawable 下進行測試
編寫測試Demo
MainActivity.java
?
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
?
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
?
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
?
private Button btn_1;
private Button btn_2;
private ImageView imageView;
private Bitmap bitmap;
?
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_1 = (Button)findViewById(R.id.button_1);
imageView = (ImageView)findViewById(R.id.image);
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.lufei);
imageView.setImageBitmap(bitmap);
btn_1.setOnClickListener(this);
?
btn_2 = (Button)findViewById(R.id.button_2);
btn_2.setOnClickListener(this);
}
public void showImage(){
bitmap = BitmapFactory.decodeResource(getResources(),R.drawable.lufei);
imageView.setImageBitmap(bitmap);
}
?
public void gray(){
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] piexls = new int[w*h];
bitmap.getPixels(piexls,0,w,0,0,w,h);
int[] resultData =Bitmap2Grey(piexls,w,h);
Bitmap resultImage = Bitmap.createBitmap(w,h, Bitmap.Config.ARGB_8888);
resultImage.setPixels(resultData,0,w,0,0,w,h);
imageView.setImageBitmap(resultImage);
}
?
@Override
public void onClick(View view){
switch(view.getId()){
case R.id.button_1:showImage();break;
case R.id.button_2:gray();break;
}
}
?
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native int[] Bitmap2Grey(int[] pixels,int w,int h);
?
@Override
public void onResume(){
super.onResume();
}
}
native-lib.cpp文件
#include <jni.h>
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;
?
extern "C" JNIEXPORT jintArray
?
JNICALL
Java_com_dhcc_www_ndkapplication_MainActivity_Bitmap2Grey(
JNIEnv *env,
jobject /* this */,jintArray buf,jint w,jint h) {
jint *cbuf;
jboolean ptfalse = false;
cbuf = env->GetIntArrayElements(buf, &ptfalse);
if(cbuf == NULL){
return 0;
}
?
Mat imgData(h, w, CV_8UC4, (unsigned char*)cbuf);
// 注意侠驯,Android的Bitmap是ARGB四通道,而不是RGB三通道
cvtColor(imgData,imgData,COLOR_BGRA2GRAY);
cvtColor(imgData,imgData,COLOR_GRAY2BGRA);
?
int size=w * h;
jintArray result = env->NewIntArray(size);
env->SetIntArrayRegion(result, 0, size, (jint*)imgData.data);
env->ReleaseIntArrayElements(buf, cbuf, 0);
return result;
}
這里要注意:Java_com_dhcc_www_ndkapplication_MainActivity_Bitmap2Grey 這個名字每個項目的包名不同,寫法也不同奕巍,其主要是 “Java包名Activity名方法名”的組合方式吟策,這里一定要寫對了,不然會報錯: 這個錯其實就是引入失敗或者找不到方法的錯誤報告的止,我一開始復制的是別人的代碼檩坚,找了半天才發(fā)現(xiàn)是這里的問題,沒辦法诅福,誰讓自己不懂呢.....
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
?
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
?
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
app:srcCompat="@drawable/lufei" />
</LinearLayout>
?
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
?
<Button
android:id="@+id/button_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="灰度圖" />
?
<Button
android:id="@+id/button_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="色圖" />
</LinearLayout>
?
</LinearLayout>
運行效果
如果一切正常匾委,可以看到如下效果圖:
點擊灰度圖時:
點擊色圖時:
至此,整個教程完畢氓润!