AndroidStudio3.0+ 配置Ndk和OpenCV4.0

原由

我這我的喜歡沒事就去招聘網站刷刷招聘信息,也不全是爲了找工做,由於我我的以爲從招聘信息中,我能夠了解到行業的大方向,瞭解如今市場上的用人單位都須要一個程序員具有什麼樣的素質和技能,程序員這個職業,一生都須要學習和進步,若是技術固步自封,不深刻底層或者不拓展技術視野,那麼成爲一個十年開發一年經驗的程序員就不遠了,因而我打開了招聘網站,隨便刷了刷就發現了問題。javascript

問題就是,對於初級程序員,市面上的公司要求很少,除了BAT,基本上回寫bug就行,可是若是到了必定歲數,你想找一些中高級的Android程序員的工做時,市面上的公司就會有一些特殊的需求了,這需求其實就是門檻,如今我貼幾張招聘簡章的截圖,你們看看:php

招聘簡章1

招聘簡章2

招聘簡章3

需求各式各樣,可是我發現一個共同的問題,都須要NDK或者C/C++的技能,看來NDK是將來一個學習的熱點和趨勢,不只音視頻、各類硬件的調用、核心算法的實現以及一些照片的處理都須要用到JNI開發。html

可能你會問了,我是個Java程序員,學這C++開發至關於跨行了,其實也對,Android都沒整明白呢,就跑去弄什麼NDK,可是仍是那句老話,技多不壓身,多學習學習接觸一下確定沒壞處,最起碼簡單的NDK開發框架會搭建,知道一些基本的配置和用法,就算之後項目裏用到了基礎的配置和調試也能用獲得啊。java

開始搭建NDK

我用的AndroidStudio的版本是3.2.1,在建立Project時若是選中了支持C++,AndroidStudio就會自動去網上幫咱們下載NDK的開發環境,具體狀況能夠經過File->Setting->Appearance & Behavior->System Setting ->Android SDK->SDK Tools查看,主要看的就是NDK和CMake兩項是否安裝:android

以後就新建項目,選擇C++選項: ios

而後選擇SDK版本,這個隨意: c++

在嚮導的 Customize C++ Support 部分,您可使用下列選項自定義項目:程序員

  • C++ Standard:使用下拉列表選擇您但願使用哪一種 C++ 標準。選擇 Toolchain Default 會使用默認的 CMake 設置。算法

  • Exceptions Support:若是您但願啓用對 C++ 異常處理的支持,請選中此複選框。若是啓用此複選框,Android Studio 會將 -fexceptions 標誌添加到模塊級 build.gradle 文件的 cppFlags 中,Gradle 會將其傳遞到 CMake。typescript

  • Runtime Type Information Support:若是您但願支持 RTTI,請選中此複選框。若是啓用此複選框,Android Studio 會將 -frtti 標誌添加到模塊級 build.gradle 文件的 cppFlags 中,Gradle 會將其傳遞到 CMake。

以後就能夠看到本身項目的目錄結構和平時的Android項目有所不一樣:

仔細觀察能夠發現,多了一個cpp包,並且還多了個CMakeLists.txt,打開local.properties還會發現多了個參數,其實就是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
複製代碼
  • 在 cpp 組中,您能夠找到屬於項目的全部原生源文件、標頭和預構建庫。對於新項目,Android Studio 會建立一個示例 C++ 源文件 native-lib.cpp,並將其置於應用模塊的 src/main/cpp/ 目錄中。本示例代碼提供了一個簡單的 C++ 函數 stringFromJNI(),此函數能夠返回字符串「Hello from C++」。

  • 在 External Build Files 組中,您能夠找到 CMake 或 ndk-build 的構建腳本。與 build.gradle 文件指示 Gradle 如何構建應用同樣,CMake 和 ndk-build 須要一個構建腳原本瞭解如何構建您的原生庫。對於新項目,Android Studio 會建立一個 CMake 構建腳本 CMakeLists.txt,並將其置於模塊的根目錄中。

咱們來看看這個文件:

# 有關使用CMake在Android Studio的更多信息,請閱讀文檔:https://d.android.com/studio/projects/add-native-code.html

# 設置CMake的最低版本構建本機所需庫
cmake_minimum_required(VERSION 3.4.1)

# 建立並命名庫,將其設置爲靜態的
# 或共享,並提供其源代碼的相對路徑。
# 你能夠定義多個library庫,並使用CMake來構建。
# Gradle會自動將包共享庫關聯到你的apk程序。

add_library( # 設置庫的名稱
             native-lib
             # 將庫設置爲共享庫。
             SHARED
             # 爲源文件提供一個相對路徑。
             src/main/cpp/native-lib.cpp )
# 搜索指定預先構建的庫和存儲路徑變量。由於CMake包括系統庫搜索路徑中默認狀況下,只須要指定想添加公共NDK庫的名稱,在CMake驗證庫以前存在完成構建
find_library( # 設置path變量的名稱
              log-lib
              # 在CMake定位前指定的NDK庫名稱
              log )
# 指定庫CMake應該連接到目標庫中,能夠連接多個庫,好比定義庫,構建腳本,預先構建的第三方庫或者系統庫
target_link_libraries( # 指定目標庫
                       native-lib
                       # 目標庫到日誌庫的連接 包含在NDK
                       ${log-lib} )
複製代碼

好了,至此,咱們的NDK就算是配置完成了,如今開始嘗試加入OpenCV。

引入OpenCV

OpenCV是一個基於BSD許可(開源)發行的跨平臺計算機視覺庫,能夠運行在Linux、Windows、Android和Mac OS操做系統上。它輕量級並且高效——由一系列 C 函數和少許 C++ 類構成,同時提供了Python、Ruby、MATLAB等語言的接口,實現了圖像處理和計算機視覺方面的不少通用算法。

知道了概念,如今咱們來引入它

第一步:下載OpenCV的Android包

下載地址

最新版是4.0.1,而後咱們點擊那個Android pack,會下載好一個壓縮包,解壓縮後是這樣的:

include文件

在下載好的OpenCV壓縮包中,打開路徑下的 OpenCV-android-sdk\sdk\native\jni 有一個include文件夾,把這個文件夾複製粘貼至咱們的OpenCVTest項目中,路徑爲src/main/cpp

jni文件

而後是動態庫(.so文件),打開路徑下的 OpenCV-android-sdk\sdk\native ,有一個libs 文件夾,這個文件夾裏面是全部版本的abi的so文件。複製粘貼到咱們的項目中,路徑爲 src/main/jniLibs 這個文件夾須要本身手動去建立。 注意:

  1. 不管是include仍是libs的路徑均可以自定義,習慣上是這樣放,但其實只要在以後的CMakeList配置文件裏面設置正確就沒有問題。   2. 值得一提的是,OpenCV在最新版本中把動態庫和靜態庫分開了,分別放在libs和staticlbs兩個文件夾中,以前是放在一個文件夾裏的。咱們測試Demo僅須要動態庫和頭文件便可。

最後配置好以後文件結構如圖所示(注意,這是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'
複製代碼

cppFlags是配置預處理的設置,abiFilters是控制系統查找.SO的文件類型的過濾器,不一樣的CPU架構對應不一樣的.so文件,abiFilters關鍵字可以指定Android 所支持的CPU架構,通常是以上4種,最終這些.so文件會被打包進APK,因此能夠根據本身的項目進行選擇,好比在AS模擬器上開發APP選一個 x86 就能夠了,若是是手機端,通常是arm架構,選 armeabi-v7a 便可。

而後同步項目便可。

CMakeList.txt文件

這個文件也是NDK開發最最最關鍵的文件,AS採用CMake腳本語法配置C編譯器的環境,若是你以前有過使用CMAKE的經驗,或許這並不是難題,但對於初學者而言,CMAKE的腳本語法,仍是略過於生澀,並且AS對該文件的配置並不友好,竟然沒有代碼提示,因而不得不查不少文檔。但好在,NDK的開發大多不是大型的C++項目,也不太須要過於複雜的設置(好比OpenCV源代碼的CMAKE文件,大約有幾千行的樣子 Orz~)

  OpenCV配置CMakeList文件的方式以下:

# For more information about using CMake with Android Studio, read the
# 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的動態庫
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 函數設置了include文件夾的路徑 add_library 函數設置庫名和類型,其中libopencv_java3 是用戶自定義的變量名,先後保持一致便可,SHARE 表示引入的庫是動態連接庫 set_target_properties 設置了OpenCV的動態連接庫的路徑 target_link_libraries 具備依賴關係的動態庫連接到指定目標上,連接順序需符合gcc連接規則,這裏咱們把libopencv_java4log連接到了native-lib上。 更詳細的CMAKE配置語法,能夠參考CMAKE官方文檔,咱們這裏僅配置動態連接庫,以上四個函數足夠了。

放入指定圖片

將上圖存儲後放入路徑:src/main/res/drawable 下進行測試

編寫測試Demo

  • MainActivity.java

    package com.dhcc.www.ndkapplication;
    
    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名方法名」的組合方式,這裏必定要寫對了,否則會報錯:

      這個錯其實就是引入失敗或者找不到方法的錯誤報告,我一開始複製的是別人的代碼,找了半天才發現是這裏的問題,沒辦法,誰讓本身不懂呢.....

    • activity_main.xml

      <?xml version="1.0" encoding="utf-8"?>
      <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>
      複製代碼

運行效果

若是一切正常,能夠看到以下效果圖:

點擊灰度圖時:

點擊色圖時:

至此,整個教程完畢!

相關學習連接:

Android NDK學習筆記:Android Studio3.1+CMAKE+OpenCV3.4配置

AndroidStudio3.0 NDK開發- 如何在已有項目中進行NDK開發

相關文章
相關標籤/搜索