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开发

相关文章
相关标签/搜索