【嗨兴科技】Android 3.0.1 图解配置NDK开发环境以及Hello Word To JNI方法总结

前言:

因为最近项目需要,在做一个图像处理及无线打印的项目,项目中用到了有关OPENCV及对图像处理用到的C/C++算法程序,所以不得不使用JNI来作为技术方案啦(其实如果不是形式所逼,我是非常不愿意使用JNI的,毕竟是JNI的诸多副作用一直让我耿耿于怀),在此过程中确实遇到不少坑洞,笔者觉得有必要填补一下,但愿能为后来者铺平道路;

那么,究竟什么是JNI ? 大家看先看下面这段官方的说明:

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。

JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。


官方Android平台架构图:

【嗨兴科技】Android 3.0.1 图解配置NDK开发环境以及Hello Word To JNI方法总结



正如同行人总结所述:Android上层的Application和ApplicationFramework都是使用Java编写,底层包括系统和使用众多的Libraries都是C/C++编写的,所以上层Java要调用底层的C/C++函数库必须通过Java的JNI来实现。


==只有当你必须在同一进程中调用本地代码时,再使用JNI。


那么NDK的好处有哪些?如下:

1、代码的安全性保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大;只用于公司内部核心技术的算法封装; 
2、可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的; 
3、提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率; 
4、便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。


NDK以及所需构建工具介绍:

CMake:外部构建工具,项目中对应着的文件名是:CMakeLists.txt,在工程目录的src\下面,与app model的build.gradle文件和main目录在同层级;
               CMake是一个开源的跨平台系列工具,旨在构建,测试和打包软件。

                 CMake用于使用简单的平台和编译器独立的配置文件来控制软件编译过程,并生成可以在选择的编译环境中使用的本地makefile和工作空间。

                 CMack工具套件由Kitware创建,以响应对开源项目(如ITK和VTK)的强大的跨平台构建环境的需求。

                 官方地址:https://cmake.org/ 有兴趣可以简单了解下~

LLDB:Android Studio 上面调试本地代码的工具;
              LLDB是下一代高性能调试器。它被构建为一组可重用的组件,可以高度利用较大的LLVM项目中的现有库,例如Clang表达式解析器和LLVM反汇编程序。

                在Mac OS X中,LLDB是Xcode中的默认调试器,支持在桌面和iOS设备和模拟器上调试C,Objective-C和C ++。

              LLDB项目中的所有代码都可以使用标准的 LLVM许可证(一种开放源代码“BSD风格”)许可证。

              LLDB目前将调试信息转换成clang类型,以便它可以利用clang编译器基础架构。这允许LLDB在表达式中支持最新的C,C++,Objective C和Objective C ++语言特性和运行时间,而无需重新实现任何此功能。在编写表达式的函数,拆卸指令和提取指令详细信息等时,还可以利用编译器来处理                 所有ABI细节。

                主要优点包括

  1. C,C ++,Objective C的最新语言支持 ;
  2. 可以声明局部变量和类型的多行表达式;
  3. 支持时使用JIT表达式;
  4. 当JIT不能使用时,评估表达式中间表示(IR)

     官方地址:http://lldb.llvm.org/


下面进入正题,如何配置NKD?(手动下载NDK,或通过Android Studio的Project Structure/SDK Location-->Android NDK Location最下面的Download链接下载)


--(请开启你的Android studio ,我的版本 是3.0.1)

【嗨兴科技】Android 3.0.1 图解配置NDK开发环境以及Hello Word To JNI方法总结


下载中截图:

【嗨兴科技】Android 3.0.1 图解配置NDK开发环境以及Hello Word To JNI方法总结


下载完成后,把NDK的目录填进来:



点击OK;

====到此,关于NDK下载安图解完毕~

我们看一下ndk目录各个作用,简单了解下。

  • docs: 帮助文档
  • build/tools:linux的批处理文件
  • platforms:编译c代码需要使用的头文件和类库
  • prebuilt:预编译使用的二进制可执行文件
  • sample:jni的使用例子
  • source:ndk的源码
  • toolchains:工具链
  • ndk-build.cmd:编译打包c代码的一个指令,需要配置系统环境变量

下面,我们还要确认配置的构建工具以及调试工具是否OK:

在Android studio工具栏中,点击SDK Manager,确认是否已经选择和下载安装了CMake以及LLDB;
截图:


如果没有安装,请勾选后点击Apply下载安装,安装完成后点击OK关闭该窗口;

配置环境变量部分:

找到安装SDK或NDK的目录(我的SDK都安装在:D:\Developer\android-sdk-windows\ndk-bundle)

所以,环境变量的NDK_HOME:D:\Developer\android-sdk-windows\ndk-bundle
                            path: .....;%NDK_HOME%;

截图:


====到此,基本配置已完成,但是如何查验NDK是否成功安装了呢?这里我通过windows的CMD命令行来验证:

使用命令:ndk-build


==================================================================================================
下面带着大家在自己的项目中配置NDK的DEMO验证下:
==================================================================================================
1. 在当前app的src/main目录下新建目录,命名为cpp,再在该cpp目录下新建一个C++ 的测试文件:native-lib.cpp,代码如下:
    #include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_hingin_l1_hiprint_Display_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "xxx Hello from C++";
    return env->NewStringUTF(hello.c_str());
    
2. 在cpp下的src\目录下新建一个文件,命名:CMakeLists.txt,内容如下:
# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)
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

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

3. 配置app下的build.gradle文件,
首先:在android标下的defaultConfig字标签新增:
externalNativeBuild {
    cmake {
        cppFlags "-fexceptions"
    }
}
其次:在android标签同级新增:
externalNativeBuild {
    cmake {
        path "CMakeLists.txt"
    }
}

4. 新建Activity和对应的布局文件:
    display.xml文件的代码:
    <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical">


    <TextView
        android:id="@+id/DisplayName"
        android:layout_width="374dp"
        android:layout_height="40dp"
        android:layout_marginTop="16dp"
        android:background="@color/colorAccent"
        android:text="NDK 测试调用C程序接口:"
        android:textColor="@android:color/background_light"
        android:textSize="30sp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginStart="8dp" />

    <Button
        android:id="@+id/btn_c_api"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:text="调用C接口"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginEnd="288dp"
        android:layout_marginLeft="16dp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="76dp" />

    <TextView
        android:id="@+id/id_c_textview"
        android:layout_width="367dp"
        android:layout_height="134dp"
        android:text="调用C接口成功后返回值:"
        android:layout_marginTop="152dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginStart="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginEnd="8dp" />

</android.support.constraint.ConstraintLayout>
    
Display.java文件的代码:
......
/**
 * Created by Administrator on 2018/2/1.
 */

public class Display extends Activity {

    private Button mButton;

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.display);
        Intent intent = getIntent();
        Bundle bundle = intent.getExtras();

        mButton = (Button) findViewById(R.id.btn_c_api);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(Display.this, "Button btn_c_api is onclicked!", Toast.LENGTH_LONG).show();
                TextView text = (TextView) findViewById(R.id.id_c_textview);
                text.setText("name:" + stringFromJNI());
            }
        });

    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();//注意:这行代码如果是红色的(说明java未能调用到native-lib的包,需要前面静态代码块中的System.loadLibrary("native-lib");编译后才会被调用到),需要编译下程序接口解决该问题;

}
问题说明:
上面的代码中:如果 public native String stringFromJNI(); 这行代码是红色的(说明java未能调用到native-lib的包,需要前面静态代码块中的System.loadLibrary("native-lib");编译后才会被调用到),需要编译下程序接口解决该问题;
编译按钮如下:

5. 将Display.java配置到AndroidManifest.xml中:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.hingin.l1.hiprint">

    <application android:allowBackup="true" android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true" android:theme="@style/AppTheme">
        <activity android:name=".OpenCVActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity android:name=".Display">
            <intent-filter>
                <action android:name="android.intent.action.EDIT" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="vnd.cmath.name/vnd.cn.cmath" />
            </intent-filter>
        </activity>

    </application>

</manifest>

最后,给大家附上整体目录结构以及运行成功后的界面截图:

--工程目录:



--测试界面运行成功后的截图: