安卓JNI精细化讲解,让你完全了解JNI(一):环境搭建与HelloWord

目录
一、基础概念
├──1.一、JNI
├──1.二、NDK
├──1.三、CMake与ndk-build
二、环境搭建
三、Native C++ 项目(HelloWord案例)
├── 3.一、项目建立(java、kotlin)
├── 3.二、CMake的应用(详细讲解)
├── 3.三、ndk-build的应用(详细讲解)java

一、基础概念

1.一、JNI

JNI(Java Native Interface)Java本地接口,使得Java与C/C++具备交互能力android

1.二、NDK

NDK(Native Development Kit) 本地开发工具包,容许使用原生语言(C和C++)来实现应用程序的部分功能macos

Android NDK开发的主要做用:bash

一、特定场景下,提高应用性能;
二、代码保护,增长反编译难度;
三、生成库文件,库可重复使用,也便于平台、项目间移植;

1.三、CMake与ndk-build

当咱们基于NDK开发出native功能后,一般须要编译成库文件,给Android项目使用。
目前,有两种主流的编译方式:__CMake__与ndk-buildapp

__CMake__与__ndk-build__是两种不一样的编译工具(与Android代码和C/C++代码无关)工具

CMake性能

CMake是Androidstudio2.2以后引入的跨平台编译工具(特色:简单易用,2.2以后是默认的NDK编译工具)

如何配置:
   一、建立CMakeLists.txt文件,配置CMake必要参数;
   二、使用gradle配置CMakeLists.txt以及native相关参数;

如何编译库文件:
   一、Android Studio执行Build便可;

ndk-build学习

ndk-build是NDK中包含的脚本工具(可在NDK目录下找到该工具,为了方便使用,一般配置NDK的环境变量)

如何配置:
   一、建立Android.mk文件,配置ndk-build必要参数;
   二、可选建立application.mk文件,配置ndk-build参数 (该文件的配置项可以使用gradle的配置替代);
   三、使用gradle配置Android.mk以及native相关参数;

二、如何编译库文件(两种方式):
   一、Android Studio执行Build便可(执行了:Android.mk + gradle配置);
   二、也可在Terminal、Mac终端、cmd终端中经过ndk-build命令直接构建库文件(执行了:Android.mk)

二、环境搭建

JNI安装
JNI 是JDK里的内容,电脑上正确安装并配置JDK便可 (JDK1.1以后就正式支持了);开发工具

NDK安装
可从官网自行下载、解压到本地,也可基于AndroidStudio下载解压到默认目录;gradle

编译工具安装
cmake 可基于AndroidStudio下载安装;
ndk-build 是NDK里的脚本工具,NDK安装好便可使用ndk-build;

当前演示,使用的Android Studio版本以下(当前最新版):

启动Android Studio --> 打开SDK Manager --> SDK Tools,以下图所示:

咱们选择NDK、CMake、LLDB(调试Native时才会使用),选择Apply进行安装,等安装成功后,NDK开发所依赖的环境也就都齐全了。

三、Native C++ 项目(HelloWord案例)

3.一、项目建立(java / kotlin)

新建项目,选择 Native C++,以下图:

新建立的项目,默认已包含完整的native 示例代码、cmake配置 ,以下图:

这样,咱们就能够本身定义Java native方法,并在cpp目录中写native实现了,很方便。

可是,当咱们写完native的实现代码,但愿运行APP,查看JNI的交互效果,此时,就须要使用编译工具了,我们仍是先看一下Android Studio默认的Native编译方式吧:CMake

3.二、CMake的应用

在CMake编译以前,我们应该先作哪些准备工做?

一、NDK环境是否配置正确?
-- 若是未配置正确是没法进行C/C++开发的,更不用说CMake编译了

二、C/C++功能是否实现? 
-- 这次演示主要使用系统默认建立的native-lib.cpp文件,关于具体如何实现:后续文章再详细讲解

三、CMakeLists.txt是否建立并正确配置? 
-- 该文件是CMake工具编译的基础,未配置或配置项错误,均会影响编译结果

四、gradle是否正确配置?
-- gradle配置也是CMake工具编译的基础,未配置或配置项错误,均会影响编译结果

除此以外,我们还应该学习CMake的哪些重要知识?

一、CMake工具编译生成的库文件默认在什么位置?apk中库文件又是在什么位置?
二、CMake工具如何指定编译生成的库文件位置?
三、CMake工具如何指定生成不一样CPU平台对应的库文件?

带着这些问题,我们开始CMake之旅吧:

3.2.一、NDK环境检查

编译前,建议先检查下工程的NDK配置状况(否则容易报一些乱七八糟的错误):
File --> Project Structure --> SDK Location,以下图(我本地的Android Studio默认没有给配置NDK路径,那么,须要本身手动指定一下):

3.2.二、C/C++功能实现

由于本节主讲CMake编译工具,代码就不单独写了,我们直接使用工程默认生成的native-liv.cpp,简单调整一下native实现方法的代码吧(修改返回文本信息):

因Native C++工程默认已配置好了CMakeLists.txt和gradle,因此我们可直接运行工程看效果,以下图:

JNI交互效果咱们已经看到了,说明CMake编译成功了。那么,这到底是怎么作到的呢?我们接着分析一下吧:

3.2.三、CMake生成的库文件与apk中的库文件

安卓工程编译时,会执行CMake编译,在 工程/app/build/.../cmake/ 中会产生对应的so文件,以下图:

继续编译安卓工程,会根据build中的内容,生成咱们的*.apk安装包文件。咱们找到、反编译apk安装包文件,查找so库文件。原来在apk安装包中,so库都被存放在lib目录中,以下图:

3.2.四、CMake是如何编译生成so库的呢?

在前面介绍CMake定义时,提到了CMake是基于CMakeLists.txt文件和gradle配置实现编译Native类的。那么,我们先来看一下CMakeLists.txt文件吧:

#cmake最低版本要求
cmake_minimum_required(VERSION 3.4.1)

#添加库
add_library(
        # 库名
        native-lib

        # 类型:
        # SHARED 是指动态库,对应的是.so文件
        # STATIC 是指静态库,对应的是.a文件
        # 其余类型:略
        SHARED

        # native类路径
        native-lib.cpp)

# 查找依赖库
find_library( 
        # 依赖库别名
        log-lib

        # 但愿加到本地的NDK库名称,log指NDK的日志库
        log)


# 连接库,创建关系( 此处就是指把log-lib 连接给 native-lib使用 )
target_link_libraries( 
        # 目标库名称(native-lib 是我们要生成的so库)
        native-lib

        # 要连接的库(log-lib 是上面查找的log库)
        ${log-lib})

实际上,CMakeList.txt可配置的内容远不止这些,如:so输出目录,生成规则等等,有须要的同窗可查下官网。

接着,我们再看一下app的gradle又是如何配置CMake的呢?

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.1"
    defaultConfig {
        applicationId "com.qxc.testnativec"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        //定义cmake默认配置属性
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    
    //定义cmake对应的CMakeList.txt路径(重要)
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
        }
    }
}

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

实际上,gradle可配置的cmake内容也远不止这些,如:abi、cppFlags、arguments等,有须要的同窗可查下官网。

3.2.五、如何指定库文件的输出目录?

若是但愿将so库生成到特定目录,并让项目直接使用该目录下的so,应该如何配置呢?
比较简单:须要在CMakeList.txt中配置库的输出路径信息,即:

CMAKE_LIBRARY_OUTPUT_DIRECTORY

# cmake最低版本要求
cmake_minimum_required(VERSION 3.4.1)

# 配置库生成路径
# CMAKE_CURRENT_SOURCE_DIR是指 cmake库的源路径,一般是build/.../cmake/
# /../jniLibs/是指与CMakeList.txt所在目录的同级目录:jniLibs (若是没有会新建)
# ANDROID_ABI 生成库文件时,采用gradle配置的ABI策略(即:生成哪些平台对应的库文件)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

# 添加库
add_library( # 库名
        native-lib

        # 类型:
        # SHARED 是指动态库,对应的是.so文件
        # STATIC 是指静态库,对应的是.a文件
        # 其余类型:略
        SHARED

        # native类路径
        native-lib.cpp)

# 查找依赖库
find_library(
        # 依赖库别名
        log-lib

        # 但愿加到本地的NDK库名称,log指NDK的日志库
        log)


# 连接库,创建关系( 此处就是指把log-lib 连接给native-lib使用 )
target_link_libraries(
        # 目标库名称(native-lib就是我们要生成的so库)
        native-lib

        # 要连接的库(上面查找的log库)
        ${log-lib})

还须要在gradle中配置 jniLibs.srcDirs 属性(即:指定了lib库目录):

sourceSets {
        main {
            jniLibs.srcDirs = ['jniLibs']//指定lib库目录
        }
    }

接着,从新build就会在cpp相同目录级别位置生成jniLibs目录,so库也在其中了:

注意事项:
一、配置了CMAKE_CURRENT_SOURCE_DIR,并不是表示编译时直接将so生成在该目录中,实际编译时,so文件仍然是
先生成在build/.../cmake/中,而后再拷贝到目标目录中的

二、若是只配置了CMAKE_CURRENT_SOURCE_DIR,并未在gradle中配置 jniLibs.srcDirs,也会有问题,以下:
More than one file was found with OS independent path 'lib/arm64-v8a/libnative-lib.so'

此问题是指:在编译生成apk时,发现了多个so目录,android studio不知道使用哪个了,因此须要我们
告诉android studio当前工程使用的是jniLibs目录,而非build/.../cmake/目录
3.2.五、如何生成指定CPU平台对应的库文件呢?

咱们能够在cmake中设置abiFilters,也可在ndk中设置abiFilters,效果是同样的:

defaultConfig {
        applicationId "com.qxc.testnativec"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
                abiFilters "arm64-v8a"
            }
        }
    }
defaultConfig {
        applicationId "com.qxc.testnativec"
        minSdkVersion 21
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
        ndk {
            abiFilters "arm64-v8a"
        }
    }

按照新的配置,咱们从新运行工程,以下图:

再反编译看下工程,果真只有arm64-v8a的so库了,不过库文件在apk中仍然是放在lib目录,而非jniLibs(其实也很好理解,jniLibs只是咱们本地的目录,便于咱们管理库文件,真正生成apk时,仍然会按照lib目录放置库文件),以下图:

至此,CMake的主要技术点都讲完了,接下来我们看下NDK-Build吧~

3.三、ndk-build的应用

在ndk-build编译以前,我们又应该先作哪些准备工做?

一、ndk-build环境变量是否正确配置?
-- 若是未配置,是没法在cmd、Mac终端、Terminal中使用ndk-build命令的(会报错:找不到命令)

二、NDK环境是否配置正确?
-- 若是未配置正确是没法进行C/C++开发的,更不用说ndk-build编译了

三、C/C++功能是否实现?
-- 这次演示主要使用系统默认建立的native-lib.cpp文件,关于具体如何实现:后续文章再详细讲解
-- 注意:为了与CMake区分,我们新建一个“jni”目录存放C/C++文件、配置文件吧

四、Android.mk是否建立并正确配置? 
-- 该文件是ndk-build工具编译的基础,未配置或配置项错误,均会影响编译结果

五、gradle是否正确配置?(可选项,若是经过cmd、Mac终端、Terminal执行ndk-build,可忽略)
-- gradle配置也是ndk-build工具编译的基础,未配置或配置项错误,均会影响编译结果

六、Application.mk是否建立并正确配置?(可选项,通常配ABI、版本,这些项都可在gradle中配置)
-- 该文件也是ndk-build工具编译的基础,未配置或配置项错误,均会影响编译结果

除此以外,我们还应该学习ndk-build的哪些重要知识?

一、ndk-build工具如何指定编译生成的库文件位置?
二、ndk-build工具如何指定生成不一样CPU平台对应的库文件?

带着这些问题,我们继续ndk-build之旅吧:

3.3.一、环境变量配置

介绍NDK-Build定义时,提到了其实它是NDK的脚本工具。那么,我们仍是先进NDK目录找一下吧,ndk-build工具的位置以下图:

若是咱们但愿任意状况下都能便捷的使用这种脚本工具,一般作法是配置其环境变量,不然咱们在cmd、Mac终端、Terminal中执行 ndk-build 命令时,会报错:“未找到命令”

配置NDK的环境变量,也很简单,以Mac电脑举例(若是是Windows电脑,网上也有不少关于配置环境变量的文章,若是有须要可自行查下):

一、打开命令终端,输入命令: open -e .bash_profile,打开bash_profile配置文件

二、写入以下内容(NDK_HOME指向 ndk-build 所在路径):
export NDK_HOME=/Users/xc/SDK/android-sdk-macosx/ndk/20.1.5948944
export PATH=$PATH:$NDK_HOME

三、生效.bash_profile配置
source .bash_profile

当咱们在cmd、Mac终端、Terminal中执行 ndk-build 命令时,若是出现下图所示内容,则表明配置成功了:

3.3.二、C/C++功能实现

我们使用比较经常使用的一种ndk-build方式吧:ndk-build + Android.mk + gradle配置

项目中新建jni目录,拷贝一份CMake的代码实现吧:

一、新建jni目录
二、拷贝cpp/native-lib.cpp 至 jni目录下
三、重命名为haha.cpp (与CMake区分)
四、调整一下native实现方法的文本(与CMake运行效果区分)
五、新建Android.mk文件

接着,编写Android.mk文件内容:

#表示Android.mk所在目录
LOCAL_PATH := $(call my-dir)

#CLEAR_VARS变量指向特殊 GNU Makefile,用于清除部分LOCAL_变量
include $(CLEAR_VARS)

#模块名称
LOCAL_MODULE    := haha
#构建系统用于生成模块的源文件列表
LOCAL_SRC_FILES := haha.cpp

#BUILD_SHARED_LIBRARY 表示.so动态库
#BUILD_STATIC_LIBRARY 表示.a静态库
include $(BUILD_SHARED_LIBRARY)

配置gradle:

apply plugin: 'com.android.application'
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.aaa.testnative"
        minSdkVersion 16
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        //定义ndkBuild默认配置属性
        externalNativeBuild {
            ndkBuild {
                cppFlags ""
            }
        }
    }
   
    //定义ndkBuild对应的Android.mk路径(重要)
    externalNativeBuild {
        ndkBuild{
            path "src/main/jni/Android.mk"
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

如今,native代码、ndk-build配置都完成了,我们运行看一下效果吧,以下图:

3.3.四、如何指定库文件的输出目录?

一般,可在Android.mk文件中配置NDK_APP_DST_DIR
指定源目录与输出目录(与CMake相似)

#表示Android.mk所在目录
LOCAL_PATH := $(call my-dir)

#设置库文件的输入目录
#输出目录 ../jniLibs/
#源目录 $(TARGET_ARCH_ABI)
NDK_APP_DST_DIR=../jniLibs/$(TARGET_ARCH_ABI)

#CLEAR_VARS变量指向特殊 GNU Makefile,用于清除部分LOCAL_变量
include $(CLEAR_VARS)

#模块名称
LOCAL_MODULE    := haha
#构建系统用于生成模块的源文件列表
LOCAL_SRC_FILES := haha.cpp

#BUILD_SHARED_LIBRARY 表示.so动态库
#BUILD_STATIC_LIBRARY 表示.a静态库
include $(BUILD_SHARED_LIBRARY)
3.3.五、如何生成指定CPU平台对应的库文件呢?

可在gradle中配置abiFilters(与Cmake相似)

externalNativeBuild {
            ndkBuild {
                cppFlags ""
                abiFilters "arm64-v8a"
            }
        }
externalNativeBuild {
            ndkBuild {
                cppFlags ""
            }
        }
  ndk {
            abiFilters "arm64-v8a"
        }
3.3.六、如何在Terminal中直接经过ndk-build命令构建库文件呢?

除了执行AndroidStudio的build命令,基于gradle配置 + Android.mk编译生成库文件,咱们还能够在cmd、Mac 终端、Terminal中直接经过ndk-build命令构建库文件,此处以Terminal为例进行演示吧:

先进入包含Android.mk文件的jni目录(Android Studio中可直接选中jni目录并拖拽到Terminal中,会自动跳转到该目录),再执行ndk-build命令,以下图:

一样,编译也成功了,以下图:

因是直接在Terminal中执行了ndk-build命令,因此只会根据Android.mk进行编译(不包含gradle配置内容,也就不会执行abiFilters过滤),生成了全部默认CPU平台的so库文件。

ndk-build命令其实也能够配上一些参数使用,此处就再也不详解了。平常开发时,仍是建议选择CMake做为Native编译工具,由于是安卓主推的,并且更简单一些。

相关文章
相关标签/搜索