通过CMake在AndroidStudio项目中引入JNI编程

早期的Android工程(Eclipse时代)以及Gradle2.2之前,Android只能通过ndk-build命令结合.mk文件来编译本地库。

从Gradle2.2开始,我们可以使用CMake方式来引入JNI编程,下面就开始介绍。

下载NDK和构建工具

一共有以下三种组件需要下载:

  • NDK(The Android Native Development Kit):让你能在Android上面使用C和C++代码的工具集。
  • CMake: 外部构建工具。
  • LLDB: Android Studio上面调试本地代码的工具。

我们通过SDK Manager来安装上述组件:

  1. 打开一个项目,从菜单栏中选择Tools > Android > SDK Manager
  2. Android SDK目录下,选择需要下载的组件执行下载和安装

    这里写图片描述

创建新项目

创建新项目很简单。

勾选include C++ Support,意思就是支持C++代码,如图:

这里写图片描述

剩下的步骤都和默认的一样,创建完成之后发现,默认给出一个“Hello from C++”的JNI例子,main目录下出现了一个和java并列的文件夹cpp

这里写图片描述

分别看一下MainActivitynative-lib.cpp的内容,

MainActivity

package com.tsnt.jni.androidjnidemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
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 = (TextView) findViewById(R.id.sample_text);
    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();

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

native-lib.cpp

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

extern "C"
jstring
Java_com_tsnt_jni_androidjnidemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

很显然在MainActivity中调用了native方法–stringFromJNI()

如果没配置NDK目录,需要配置:

这里写图片描述

然后把项目跑起来,成功运行:

这里写图片描述

在已有项目中引入JNI

相比较于创建新项目,我们要做的就是去创建缺少的文件。

创建cpp文件夹,和cpp文件

app\src\main目录下创建和java文件夹并列的cpp文件夹,然后选择C/C++ Source File

这里写图片描述

这里写图片描述

至于如何使用JNI编程,请参考Android JNI编程入门

创建CMake构建脚本

在app目录下创建一个名为CMakeLists的txt文件

这里写图片描述

新建项目时默认的CMakeLists是这样的:

# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

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 it 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).
             # Associated headers in the same location as their source
             # file are automatically included.
             src/main/cpp/native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because system libraries are included 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 the
# 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} )

分为如下四部分:

1.最低版本声明

cmake_minimum_required(VERSION 3.4.1)

这个很简单,意思就是CMake最小版本使用的是3.4.1

2.增加本地库

add_library( native-lib  SHARED  src/main/cpp/native-lib.cpp )

括号里有三个参数:

1.第一个参数native-lib表示库名

在项目中,如果需要使用这个so文件,引用的名称就是这个。值得注意的是,实际上生成的so文件名称是libnative-lib。当run项目或者build项目时,在Module级别的build\intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main目录下会生成相应的so库文件,像这样:

这里写图片描述

2.第二个参数SHARED表示库的类型

  • STATIC:静态库,是目标文件的归档文件,在链接其它目标的时候使用。
  • SHARED:动态库,会被动态链接,在运行时被加载。
  • MODULE:模块库,是不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数动态链接。
    具体可以去查看C++静态库与动态库的相关知识。

3.第三个参数src/main/cpp/native-lib.cpp表示库的源文件

怎么增加其它的预构建的库呢?比如import-lib是一个外部库。

其实和添加本地库(native library)类似。由于预编译库是已经构建好的,你想就要使用 IMPORTED标志去告诉CMake,你只需要将其导入到你的项目中即可:

add_library( imported-lib SHARED IMPORTED )

一些库会根据不同的 CPU 使用不同的包,或者是 Application Binary Interfaces(ABI),并且将他们归类到不同的目录中。这样做的好处是,可以充分发挥特定的 CPU 架构。你可以使用 ANDROID_ABI 路径变量,将多个 ABI 版本的库添加到你的 CMake 构建脚本中。这个变量使用了一些 NDK 默认支持的 ABI,以及一些需要手动配置到 Gradle 的 ABI。然后你需要使用 set_target_properties() 命令去指定库的路径,就像下面的代码那样:

add_library(...)
set_target_properties( # Specifies the target library.
                       imported-lib

                       # Specifies the parameter you want to define.
                       PROPERTIES IMPORTED_LOCATION

                       # Provides the path to the library you want to import.
                       imported-lib/src/${ANDROID_ABI}/libimported-lib.so )
编译NDK自带的库

NDK同样也包含一些只包含源码的library,这些就需要你去编译,然后链接到你的本地库(native library)。你可以在CMake构建脚本中使用add_library()命令将源码编译进本地库。这时就需要提供你的本地NDK安装路径,通常将该路径保存在ANDROID_NDK变量中,这样Android Studio可以自动为你识别。

下面的命令告诉CMake去构建android_native_app_glue.c,将其导入静态库中,然后将其链接至native-lib:

add_library( app-glue
             STATIC
             ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c )

# You need to link static libraries against your shared native library.
target_link_libraries( native-lib app-glue ${log-lib} )

3.定位预构建库

find_library(log-lib  log )

find_library()命令添加到你的CMake构建脚本中,这样就可以定位NDK library的位置,并将其位置存储在一个变量之中。你可以在构建脚本的其他地方使用这个变量,来代指 NDK library。以上的示例代码将Android-specific log support library的位置存储到变量log-lib 中:

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 )

4.将预构建库与你本地库相关联

target_link_libraries( native-lib  ${log-lib} )

关联本地库与Gradle

使用图形化界面

你可以使用图形化界面来将Gradle与外部的CMake或者ndk-build项目关联起来:

  1. 打开 IDE 左边的 Project 面板,选择Android视图。

  2. 右键点击你想链接本地库的module,比如app module,然后从菜单中选择Link C++ Project with Gradle。你应该能看见一个和下图很像的对话框。

  3. 在下拉菜单中,选择CMake或者ndk-build。
    a. 如果你选择CMake需要在Project Path中指定CMakeLists.txt脚本文件的路径
    b. 如果你选择ndk-build需要在Project Path中指定Android.mk脚本文件的路径
    这里写图片描述

  4. 点击ok,就完成了。

手动配置

module层级的build.gradle文件中添加externalNativeBuild {}代码块,并且在该代码块中配置cmake {}ndkBuild {},如下:

android {
    ...
    defaultConfig {...}
    buildTypes {...}

    // Encapsulates your external native build configurations.
    externalNativeBuild {

        // Encapsulates your CMake build configurations.
        cmake {

            // Provides a relative path to your CMake build script.
            path "CMakeLists.txt"
        }
    }
}

可选配置

你可以在你的 module 层级的build.gradle文件中的defaultConfig {}代码块中,添加 externalNativeBuild {}代码块,为 CMake 或 ndk-build 配置一些额外参数。当然,你也可以在你的构建配置中的其他每一个生产渠道重写这些属性。

比如,如果你的 CMake 或者 ndk-build 项目中定义了多个本地库,你想在某个生产渠道使用这些本地库中的几个,你就可以使用 targets 属性来构建和打包。下面的代码展示了一些你可能会用到的属性:

android {
  ...
  defaultConfig {
    ...
    // This block is different from the one you use to link Gradle
    // to your CMake or ndk-build script.
    externalNativeBuild {

      // For ndk-build, instead use ndkBuild {}
      cmake {

        // Passes optional arguments to CMake.
        arguments "-DCMAKE_VERBOSE_MAKEFILE=TRUE"

        // Sets optional flags for the C compiler.
        cFlags "-D_EXAMPLE_C_FLAG1", "-D_EXAMPLE_C_FLAG2"

        // Sets a flag to enable format macro constants for the C++ compiler.
        cppFlags "-D__STDC_FORMAT_MACROS"
      }
    }
  }

  buildTypes {...}

  productFlavors {
    ...
    demo {
      ...
      externalNativeBuild {
        cmake {
          ...
          // Specifies which native libraries to build and package for this
          // product flavor. If you don't configure this property, Gradle
          // builds and packages all shared object libraries that you define
          // in your CMake or ndk-build project.
          targets "native-lib-demo"
        }
      }
    }

    paid {
      ...
      externalNativeBuild {
        cmake {
          ...
          targets "native-lib-paid"
        }
      }
    }
  }

  // You use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}
    // or ndkBuild {...}
  }
}

指定ABI(Application Binary Interfaces)

一般情况下,Gradle会将你的本地库构建成 .so 文件,然后将其打包到你的 APK 中。如果你想 Gradle 构建并打包某个特定的 ABI 。你可以在你的 module 层级的 build.gradle 文件中使用 ndk.abiFilters 标签来指定他们:

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      cmake {...}
      // or ndkBuild {...}
    }

    ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your APK.
      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                   'arm64-v8a'
    }
  }
  buildTypes {...}
  externalNativeBuild {...}
}

大多数情况,你只需要像上面的代码那样,在 ndk {} 代码块中指定 abiFilters 即可。如果你想控制 Gradle 构建、依赖你希望的东西,你就需要在 defaultConfig.externalNativeBuild.cmake {} 代码块或 defaultConfig.externalNativeBuild.ndkBuild {} 代码块中,配置其他的 abiFilters 标签。Gradle 会构建这些 ABI 配置,但是只会将 defaultConfig.ndk {} 代码块中指定的东西打包到 APK 中。


demo地址AndroidJNIDemo

参考:

  1. Android NDK探究奥秘一:Android Studio创建第一个JNI项目
  2. 在 Android Studio 2.2 中愉快地使用 C/C++
  3. Android Studio 2.2 更方便地创建JNI项目-CMake
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值