Android JNI(二)——实战JNI之Hello World

上一篇我们讲解了ndk以及jni基础,接下来以实战方式讲解几种常见的jni调用方式,带你走进jni世界。

大纲

今天会详细讲解几种jni的实现方式,实战NDK实现 "hello world"之旅。
在这里插入图片描述

环境搭建

既然是Android ndk开发,那么肯定少不了Android开发工具。还好有Android studio,这是一款不错的开发工具,已经集成了不少的开发插件以及组件给我们使用,今天讲解的,它全部都能满足。
Android studio 版本如下:
在这里插入图片描述
安装完Android studio后,就是安装配置NDK以及对应的编译工具,这里可以直接打开Android studio的setting文件,找到sdk mannger进行下载,如下图所示:
在这里插入图片描述
好了,配置好上图的开发环境了,就可以开始我们愉快的编程之旅了。

一:传统方式实战

具体流程如下:

(一) 创建项目

首先在Android Studio创建一个Android项目,包名为com.example.myndk

(二) 创建引用本地库的工具类

然后创建一个class为NDKTools,代码如下:

package com.example.myndk;

public class NDKTools {
    public static native String getStringFromNDK();
}

(三) 修改android相关的activity以及activity相对应的UI

修改MainActivity,在里面调用NDKTools的getStringFromNDK()方法。

package com.example.myndk;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = findViewById(R.id.tv_ndk);
        String msg=NDKTools.getStringFromNDK();
        Log.i("TAG","this is from c++ msg:"+msg);
        tv.setText(msg);
    }
}

MainActivity对应的xml如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_ndk"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="ndk开发调用"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

(四) 获取classes文件

在Android Studio中点击Build中的Make Project或者Rebuild Project进行编译来获取中间文件。如下图:
在这里插入图片描述
编译完成后,我们就可以获取class文件如下图:
在这里插入图片描述

(五) 进入相应目录

点击Android Studio下面的Terminal,然后跳到NDKDemo/app/build/intermediates/classes/debug下(其中NDKDemo为是项目的根目录),在Terminal执行pwd确认目录

(六) 获取.h文件

在NDKDemo/app/build/intermediates/classes/debug下执行下面的命令javah -jni gebilaolitou.ndkdemo.NDKTools。如果没有问题,则会在NDKDemo/app/build/intermediates/classes/debug下面生成gebilaolitou_ndkdemo_NDKTools.h文件

代码内容如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class gebilaolitou_ndkdemo_NDKTools */

#ifndef _Included_gebilaolitou_ndkdemo_NDKTools
#define _Included_gebilaolitou_ndkdemo_NDKTools
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com.example.myndk.NDKTools
 * Method:    getStringFromNDK
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_myndk_NDKTools_getStringFromNDK
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

(七) 增加对应的.c文件

在工程main目录下创建一个名字为jni目录,然后将刚才的.h文件剪切过来。在jni目录下新建一个c文件。命名为native-lib.c。此时项目目录如下:
在这里插入图片描述

扫描二维码关注公众号,回复: 8518073 查看本文章

(八) 编写native-lib.c文件

#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myndk_NDKTools_getStringFromNDK(JNIEnv *env, jclass clazz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

(九) 添加并编写Android.mk文件

同样在jni目录下,添加一个Android.mk文件,在Android.mk文件里面编写如下内容:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := ndkdemotest-jni

LOCAL_SRC_FILES := ndkdemotest.c

include $(BUILD_SHARED_LIBRARY)

关于Android.mk语言后面会单独写一篇文章进行讲解,这里重点说上面代码的内容:

  • LOCAL_PATH := $(call my-dir):每个Android.mk文件必须以定义开始。它用于在开发tree中查找源文件。宏my-dir则由Build System 提供。返回包含Android.mk目录路径。
  • include $(CLEAR_VARS) :CLEAR_VARS变量由Build System提供。并指向一个指定的GNU Makefile,由它负责清理很多LOCAL_xxx。例如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_STATIC_LIBRARIES等等。但不是清理LOCAL_PATH。这个清理是必须的,因为所有的编译控制文件由同一个GNU Make解析和执行,其变量是全局的。所以清理后才能便面相互影响。
  • LOCAL_MODULE := ndkdemotest-jni:LOCAL_MODULE模块必须定义,以表示Android.mk中的每一个模块。名字必须唯一且不包含空格。Build System 会自动添加适当的前缀和后缀。例如,demo,要生成动态库,则生成libdemo.so。但请注意:如果模块名字被定义为libabd,则生成libabc.so。不再添加前缀。
  • LOCAL_SRC_FILES := ndkdemotest.c:这行代码表示将要打包的C/C++源码。不必列出头文件,build System 会自动帮我们找出依赖文件。缺省的C++ 源码的扩展名为.cpp。
  • include $(BUILD_SHARED_LIBRARY):BUILD_SHARED_LIBRARY是Build System提供的一个变量,指向一个GUN Makefile Script。它负责收集自从上次调用include $(CLEAR_VARS)后的所有LOCAL_xxxxinx。并决定编译什么类型
  • BUILD_STATIC_LIBRARY:编译为静态库
  • BUILD_SHARED_LIBRARY:编译为动态库
  • BUILD_EXECUTABLE:编译为Native C 可执行程序
  • BUILD_PREBUILT:该模块已经预先编译

注意,这里不编写Android.mk会提示如下问题:

Error:Execution failed for task ':app:compileDebugNdk'.
> Error: Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio.  Please switch to a supported build system.
  Consider using CMake or ndk-build integration. For more information, go to:
   https://d.android.com/r/studio-ui/add-native-code.html#ndkCompile
   To get started, you can use the sample ndk-build script the Android
   plugin generated for you at:
   /Users/gebilaolitou/AndroidStudioProjects/JNIDemo/app/build/intermediates/ndk/debug/Android.mk
  Alternatively, you can use the experimental plugin:
   https://developer.android.com/r/tools/experimental-plugin.html
  To continue using the deprecated NDK compile for another 60 days, set 
  android.deprecatedNdkCompileLease=1523001628930 in gradle.properties

这个错误是什么意思了,简单翻译过来就是:

错误:执行app:compileDebugNdk任务失败
错误:不再支持android.useDeprecatedNdk标志,并且将会在未来的Android Studio版本中删除这个标志。请切换到CMake构建系统或者ndk-build中集成。更多的信息请参考https://d.android.com/r/studio-ui/add-native-code.html#ndkCompile。您可以使用Android的示例ndk-build脚本在以下位置生成的插件:
/Users/gebilaolitou/AndroidStudioProjects/JNIDemo/app/build/intermediates/ndk/debug/Android.mk。另外,你也可以使用实验性插件https://developer.android.com/r/tools/experimental-plugin.html
如果你还想继续再使用已经被弃用的NDK编译60天,你需要再gradle.properties中设置android.deprecatedNdkCompileLease=1523001628930

如此一来,我们就必须设置我们需要设Android.mk文件。

(十) 修改相应的配置文件

首先检查local.properties文件中是否有NDK路径,如果有没有NDK路径,则添加NDK路径,比如我的如下:

ndk.dir=C:\\Android\\sdk\\ndk-bundle
sdk.dir=C:\\Android\\sdk
注意:不同环境的路径配置不同,我这里是window开发环境。

其次修改app module目录下的build.gradle中的内容,如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.example.myndk"
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        ndk{
            moduleName "ndkdemotest-jni"
            abiFilters "armeabi", "armeabi-v7a", "x86"

        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguardrules.pro'
        }
        sourceSets.main {
            jni.srcDirs = []
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

这样就有了so文件(此时还没生成so文件)

(十一) 修改引用类

最后在NDKTools类中添加静态初始化代码,如下:

package com.example.myndk;
public class NDKTools {
    public static native String getStringFromNDK();
    static {
        System.loadLibrary("ndkdemotest-jni");
    }
}

最后run一下即可看到神奇的效果,如下图:
在这里插入图片描述

二:传统方式搭建存在的问题

这种传统方式,配置的地方比较多,如果gradle.properties文件中没有配置android.useAndroidX=true,编译时会包一下错误:

Error:Execution failed for task ':app:compileDebugNdk'.
> Error: Your project contains C++ files but it is not using a supported native build system.
  Consider using CMake or ndk-build integration. For more information, go to:
   https://d.android.com/r/studio-ui/add-native-code.html
  Alternatively, you can use the experimental plugin:
   https://developer.android.com/r/tools/experimental-plugin.html

因此,如果遇到这类错误,很简单,只需要在gradle.properties文件中配置android.useAndroidX=true即可:

android.useAndroidX=true

三:传统方式的so文件

大家可能会有疑问,那so去哪里了,我们平时使用第三方的sdk的so的时候,会要粘贴复制到项目里面,而我们上所述整个过程,并没有出现.so这个文件,那么这个.so去哪里了?

其实Android Studio自动帮我们把so放到apk里面,如果我们想找也能找到,如下图:
在这里插入图片描述
好了,至此,传统方式的ndk调用已经完成。
上面这套方式是传统的Android Studio的模式,那有没有更简单的方式,当然会有,那下面我们就继续来看一下更简单的方式

四:通过CMake工具演示流程如下

(一) 首先确保你本地有CMake,我们来看下SDK Tools

打开Android studio的设置,找到Android SDK 选项,确认是否安装了CMake,如果没有,直接安装即可,如下图所示:
在这里插入图片描述
直接选中CMake,点击ok,等待下载完毕即可。

(二) 新建项目,勾选Customize C++ Support,自定义项目

在这里插入图片描述
此时,你会发现有三个选项,下面详细讲解一下这三个选项代表的意思:

  1. C++ Standard:即C++标准,使用下拉列表选择你希望使用的C++的标准,选择Toolchain Default 会使用默认的CMake设置。
  2. Exceptions Support:如果你希望启用对C++异常处理的支持,请选择此复选框。如果启动此复选框,Android Studio 会将-fexceptions标志添加到模块级build.gradle文件的cppFlags中,Gradle会将其传递到CMake。
  3. Runtime Type Information Support:如果开发者希望支持RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将-frtti标志添加到模块级build.gradle文件的cppFlags中,Gradle会将其传递到CMake。

更具实际情况选择,建议用默认就好,最后点击 Finish即可完成。

(三) 检查 Android 目录

在Android Studio 完成新项目的创建后,请从IDE左侧打开Project 矿口并选择Android 视图。如下图所示,Android Studio 将添加cpp和External Build Files 组:
在这里插入图片描述
简单介绍下这两个多出来的文件夹:

  • 在 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,并将其置于模块根目录中。

至此,便可以直接运行了,点击run一下项目,看下结果。如下:
在这里插入图片描述
可能你比较疑惑,可以这么简单,下面我列出默认代码,你就不会疑惑,就是如此简单,使用cmker

默认activity代码

package com.example.myndk;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Example of a call to a native method
        TextView tv = findViewById(R.id.tv_ndk);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

MainActivity 对应的xml如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:textColor="@color/colorAccent"
        android:textSize="22sp"
        android:id="@+id/tv_ndk"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ndk开发调用"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

CMakeLists.txt配置文件如下

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

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).
        native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

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)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-lib

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

关于cmake语法可以自行百度。

native-lib.cpp文件如下

#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myndk_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

native-lib.cpp文件,如果不懂对应语法意思,可参考本人上一篇ndk基础,这里不做重复。

(四)修改native-lib.cpp

修改native-lib.cpp文件,把"Hello from C++"改成任何一句话,点击运行看看效果。

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myndk_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "带你走进 C++,走进ndk开发的世界!";
    return env->NewStringUTF(hello.c_str());
}

点击run,看一下,改变后的结果如下图所示:
在这里插入图片描述
我们看到对应的文字已经修改了,之前内容是“from Hello c++”。

五:CMake工具背后原理

我们看打了,我们什么都没做,就自动实现了C++的实现,它的背后原理是什么那?下面我们一起来探究一下。

#### (一)CMake的入口

 大家都知道,程序能运行是有入口,就像Java,java的入口是main函数,那么CMake的入口了?

首先和大家说下我是怎么想象的,首先我们在点击Android Studio中的run按钮的时候,它是执行Gradle来进行打包的,所以说关于CMake的是怎么植入进去的,一定在项目的build.gradle,有相应的入口。

通过上面的思想,我们能举一反三得到什么?对的,就是类似于这种操作,一般都是在build.gradle里面实现的,因为在目前Android Studio就是通过Gradle是实现的

那我们就来看下它的build.gradle里面的代码,如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.example.myndk"
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }

        sourceSets.main {
            jni.srcDirs = []
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

和我们平时搭建的项目差不多,就是多出来一块内容,externalNativeBuild。那这里我们重点说下externalNativeBuild:

#### (二) externalNativeBuild
我们在build.gradle里面看到,有两个地方用到了externalNativeBuild,一个是在defaultConfig里面,是一个是在defaultConfig外面。

  • 在defaultConfig外面的externalNativeBuild里面的cmake指明了CMakeList.txt的路径(在本项目下,和是build.gradle在同一个目录里面)。
  • 在defaultConfig里面的externalNativeBuild里面的cmake主要填写的是CMake的命令参数。即由arguments中的参数最后转化成一个可执行的CMake的命令,可以在defaultConfig外面的 externalNativeBuild - cmake,指明了 CMakeList.txt 的路径;
    defaultConfig 里面的 externalNativeBuild - cmake,主要填写 CMake 的命令参数。即由 arguments 中的参数最后转化成一个可执行的 CMake 的命令,可以在 app/externalNativeBuild/cmake/debug/{abi}/cmake_build_command.txt中查到,如下图所示;
    在这里插入图片描述
    点开文件,你会发现文件内容如下:
arguments : 
-H/Users/gebilaolitou/Desktop/codeLib/CMakeNDKDemo/app
-B/Users/gebilaolitou/Desktop/codeLib/CMakeNDKDemo/app/.externalNativeBuild/cmake/debug/x86
-DANDROID_ABI=x86
-DANDROID_PLATFORM=android-23
-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/Users/gebilaolitou/Desktop/codeLib/CMakeNDKDemo/app/build/intermediates/cmake/debug/obj/x86
-DCMAKE_BUILD_TYPE=Debug
-DANDROID_NDK=/Users/gebilaolitou/Library/Android/sdk/ndk-bundle
-DCMAKE_CXX_FLAGS=
-DCMAKE_TOOLCHAIN_FILE=/Users/gebilaolitou/Library/Android/sdk/ndk-bundle/build/cmake/android.toolchain.cmake
-DCMAKE_MAKE_PROGRAM=/Users/gebilaolitou/Library/Android/sdk/cmake/3.6.4111459/bin/ninja
-GAndroid Gradle - Ninja
jvmArgs : 

更多的可以填写的命令参数和含义可以参见Android NDK-CMake文档 ,可能需要翻墙才能可以。
ok上面既然提到了CMakeLists.txt,那我们就来看下CMakeLists.txt

(三) CMakeLists.txt

CMakeLists.txt这个文件主要定义了哪些文件需要编译,以及和其他库的关系等,那让我们来看下我们项目中的CMakeLists.txt的内容

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

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).
        native-lib.cpp)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

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)

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
        native-lib
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib})

由于默认生成的有很多是注释,而且是英文,方便大家直观的看,我们除去注释:

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} )

CMakeLists.txt我们看到这里主要是分为四个部分,下面我们就依次来看下

  • cmake_minimum_required(VERSION 3.4.1):指定CMake的最小版本
  • add_library:创建一个静态或者动态库,并提供其关联的源文件路径,开发者可以定义多个库,CMake会自动去构建它们。Gradle可以自动将它们打包进APK中。
    • 第二个参数——SHARED:是库的类别,是动态的还是静态的
    • 第二个参数——SHARED:是库的类别,是动态的还是静态的
    • 第三个参数——src/main/cpp/native-lib.cpp:是库的源文件的路径
  • find_library:找到一个预编译的库,并作为一个变量保存起来。由于CMake在搜索库路径的时候会包含系统库,并且CMake会检查它自己之前编译的库的名字,所以开发者需要保证开发者自行添加的库的名字的独特性。
    • 第一个参数——log-lib:设置路径变量的名称
    • 第一个参数—— log:指定NDK库的名子,这样CMake就可以找到这个库
  • target_link_libraries:指定CMake链接到目标库。开发者可以链接多个库,比如开发者可以在此定义库的构 建脚本,并且预编译第三方库或者系统库。
    - 第一个参数——native-lib:指定的目标库
    - 第一个参数——${log-lib}:将目标库链接到NDK中的日志库,

这其实是一个最基础的CMakeLists.txt ,其实CMakeLists.txt里面可以非常强大,比如自定义命令、查找文件、头文件包含、设置变量等等。这里推荐CMake的官网文档,不过是英文的,不好阅读,大家可以参考中文的CMake手册

上面分析完毕CMakeLists.txt,我们就大致的知道了CMake整体的构建流程,那我们就来看下CMake的运转流程。

(四) CMake的运转流程

  • 1、Gradle 调用外部构建脚本CMakeLists.txt
  • 2、CMake 按照构建脚本的命令将 C++ 源文件 native-lib.cpp 编译到共享的对象库中,并命名为 libnative-lib.so ,Gradle 随后会将其打包到APK中
  • 3、运行时,应用的MainActivity 会使用System.loadLibrary()加载原生库。应用就是可以使用库的原生函数stringFromJNI()。

注意:

  • Instant Run 与使用原生的项目不兼容
  • 如果想看Gradle是否将原生库打包到APK中,可以使用Analyze APK来检测。

六:CMake的应用

我们在做日常需求的时候,往往会遇到一个问题,即在已有的项目中,添加C库,这样就不能通过上面的创建流程,来使用CMake。那怎么办?没关系,CMake也提供这样的功能一样可以实现,我们删除原有默认生成ndk的文件。如下图所示:
在这里插入图片描述
好了,我们开始一步一步手动搭建吧。

(一) 创建源文件

即在main目录下新建一个目录,我们就叫cpp好了。然后在该目录下创建一个C++ Source File(右键点击您刚刚创建的目录,然后选择 New > C/C++ Source File)。我们将其命名为newnative-lib。
创建后,目录如下:
在这里插入图片描述

(二) 创建CMake构建脚本

因为目前这个项目没有CMake的构建脚本,所以咱们需要自行创建一个并包含适当的CMake命令。CMake构建脚本是一个纯文本的文件,而且这个名字必须是是CMakeLists.txt

注意:CMake构建脚本是一个纯文本的文件,而且这个名字必须是是CMakeLists.txt

要常创建一个可以用作CMake构建脚本的纯文本文件,请按以下步骤操作:

  • 1、从Android Studio左侧打开Project窗格并从下拉菜单中选择Project视图。
  • 2、右键点击 模块的根目录并选择 New——> File
  • 3、输入CMakeLists.txt作为文件并点击OK

注意:在第二步时,这个位置不是不固定的,位置可以随意,但是配置构建脚本时,需要将这个位置写入构建脚本

创建后,目录如下:
在这里插入图片描述

(三) 向CMake脚本文件写入数据

由于之前讲过CMakeLists.txt文件内容,这里就不在重复叙述,直接拷贝修改即可,内容如下。

cmake_minimum_required(VERSION 3.4.1)

add_library( # Sets the name of the library.
        newnative-lib
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        src/main/cpp/newnative-lib.cpp )

find_library( # Defines the name of the path variable that stores the
        # location of the NDK library.
        log-lib

        # Specifies the name of the NDK library that
        # CMake needs to locate.
        log )


target_link_libraries( # Specifies the target library.
        newnative-lib
        # Links the log library to the target library.
        ${log-lib} )

(四) 向Gradle 关联到原生库

要将Gradle关联到原生库,需要提供一个指向CMake或ndk-build 脚本文件的路径。在构建应用时,Gradle会以依赖项的形式运行CMake或ndk-build,并将共享的库打包到APK中。Gradle还是用构建脚本来了解将那些文件添加到Android 项目中。如果原生文件还没有构建脚本,需要创建CMake构建脚本。

关联到原生库有两种方式,一种是通过Android Studio,一种是手动,其实其背后的东西是一致的,我们就一一来说明

1、通过Android Studio 实现

  1. 1、从IDE 左侧打开Project 窗格 并选择 Android 视图
  2. 2、右键点击想要关联到原生库的模块(咱们这里是app 模块),并从菜单中选择 Link C++ Project with Gradle
  3. 3、在下拉菜单中选择CMake。使用Project Pat来为外部的CMake项目指定刚刚的``CMakeLists.txt`脚本文件
  4. 4、点击OK。

2、手动实现
要手动配置Gradle 以关联到原生库,需要将externalNativeBuild{} 块添加到模块级 build.gradle 文件中,并使用cmake {}对其进行配置

apply plugin: 'com.android.application'
android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    defaultConfig {
        applicationId "com.example.myndk"
        minSdkVersion 15
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

(五) 编写native-lib.cpp

native-lib.cpp的代码如下:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myndk_NDKTools_getStringFromNDK(
        JNIEnv *env, jobject /* this */) {
    std::string hello = "哈哈哈……我是隔壁老王,我想和你做邻居";
    return env->NewStringUTF(hello.c_str());
}

然后在NDKTools.java添加引用,如下:

package com.example.myndk;
public class NDKTools {
    static {
        System.loadLibrary("native-lib.cpp");
    }
    public static native String getStringFromNDK();
}

七:使用experimental-plugin插件

经过简单学习,你会发现,其实ndk开发有俩件比较麻烦的事情,就是编写.mk文件,比如Android.mk和Application.mk,不过还好,Android studio给我们提供了experimental-plugin插件,Android Studio的插件gradle-experimental就是用来解决这个问题的。所以使用gradle-experimental插件可以不用再编写.mk文件情况下进行NDK开发。

gradle-experimental是Android Studio的一个实验性的项目,是基于gradle的一个插件,主要用来自动化NDK的配置实现,无需自己编写Android.mk和Android.mk,对于调试NDK项目也更加友好,不过现在已经 不支持 ,详细请看Experimental Plugin User Guide

如下图所示:
在这里插入图片描述

截取官网中给出的重要信息如下:

Note to experimental Android plugin users: The experimental plugin will no longer be supported after version 0.11.0 (released October 25, 2017). That’s because the experimental plugin is designed around a Software Component Model that Gradle announced they will no longer support (read their blog post here). Gradle has backported many features from the component model, which are now available with Android plugin 3.0.0, such as variant-aware dependency resolution, and api and implementation dependency configurations. Gradle is working on backporting built-in support for compiling C/C++ code, and the Android plugin will integrate that support when it becomes available. Until then, you can either keep using experimental plugin 0.11.0 with Android Studio 3.0 or later, or migrate to Android Studio’s support for using external native build tools.

翻译成中文后,意思大致如下:

面向实验性Android插件用户的注意事项: 在版本0.11.0(2017年10月25日发布)之后,将不再支持实验性插件。这是因为实验性插件是围绕 Gradle宣布将不再支持的软件组件模型设计的 (请在此处阅读其博客文章)。Gradle从组件模型向后移植了许多功能,这些功能现在可以在Android插件3.0.0中使用,例如 变体感知依赖项解析, api和实现依赖项配置。Gradle正在努力向后移植对C / C ++代码的内置支持,Android插件将在可用时集成该支持。在此之前,您可以在Android Studio 3.0或更高版本上继续使用实验性插件0.11.0,也可以 迁移到Android Studio对使用外部本机构建工具的支持 。

好了,自此jni实战Hello World已经讲完,后续会持续讲解Java如何和Native互相调用.

发布了49 篇原创文章 · 获赞 66 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/ljx1400052550/article/details/103830438