NDK入门(一) 在Android Studio中创建并运行第一个ndk程序hello world

这篇博客介绍了如何在Android Studio中使用NDK创建并运行第一个C++程序。从下载NDK和构建工具开始,通过向导创建支持C++的项目,配置CMakeLists.txt,到编译运行程序,最后解析关键的源代码文件和构建过程,适合NDK初学者。

在Android Studio中创建并运行第一个ndk程序hello world

简介

最近开始自学NDK,所有做一个笔记用来记录。另外本文适合没有ndk基础的朋友学习使用。
感谢官方中文文档的翻译者。
官方文档: https://developer.android.com/studio/projects/add-native-code.html#existing-project

前言

因为使用的是ndk-bundle,根据官方介绍推荐搭配使用 Android Studio 2.2 或更高版本与 Android Plugin for Gradle 版本 2.2.0 或更高版本。

下载 NDK 和构建工具

  • 需要安装的组件(这段是官网的解释)
Android 原生开发工具包 (NDK): 这套工具集允许您为 Android 使用 C 和 C++ 代码,并提供众多平台库,
让您可以管理原生 Activity 和访问物理设备组件,例如传感器和触摸输入。
CMake: 一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。
LLDB: 一种调试程序,Android Studio 使用它来调试原生代码。
  • 使用sdk-manager安装组件

    这里写图片描述

  • 配置NDK环境变量
    在环境变量中新建NDK_ROOT,并将下载好的ndk-bundle的路径配置进去,并在path中添加%NDK_ROOT%;

    这里写图片描述

开始hello world

创建一个支持 C++ / C 的项目(重要)
  1. 在向导的 Configure your new project 部分,选中 Include C++ Support 复选框。
    这里写图片描述

  2. 点击 Next。

  3. 正常填写所有其他字段并完成向导接下来的几个部分。
  4. 在向导的 Customize C++ Support 部分,您可以使用下列选项自定义项目:
    • C++ Standard:使用下拉列表选择您希望使用哪种 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置。
    • Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。
    • Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

这里写图片描述
5. 点击 Finish。

注:如果您的现有项目使用已弃用的 ndkCompile 工具,则应先打开 build.properties 文件,在里面添加如下代码

android.useDeprecatedNdk = true
编译运行

运行结果:
这里写图片描述

关键文件和代码

因为添加了默认支持,所以项目里自动生成了对应这个demo的代码和文件 , 如果没有添加就需要手动添加 。

MainActivity(包含native方法,native方法调用,加载so库)

主要是native方法,native库,还有就是使用。需要注意的是在static中使用的库名一定要和CMakeLists中库名字一致,也要和cpp文件夹中的c++源文件名字一致。

public class MainActivity extends AppCompatActivity {

    // 在应用开始的时候,加载CPP生成的native-lib库
       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 = (TextView) findViewById(R.id.sample_text);
        // 调用native方法,最终执行的地方是在native层中
        tv.setText(stringFromJNI());
    }

    /**
     * native 方法,用于调用cpp文件中的方法。无具体实现
     */
    public native String stringFromJNI(); 
}

native-lib.cpp (刚才那个native方法指向的真正运行的逻辑和方法)

这个c++源文件位于app Module中,main文件夹下。因为是自动生成的demo,所以名字默认是native-lib,如果自己写得方法,注意名字和CMakeLists中定义的一致。

这里写图片描述

代码以及解释:

#include <jni.h>  //jni库
#include <string> //string ku

using namespace std; //std命名空间

extern "C" //按照C的规则翻译函数名
JNIEXPORT jstring JNICALL
//格式是java_包名_native所在的文件_native方法
Java_cn_yefeidd_hellojni_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    string hello = "Hello from C++"; 
    return env->NewStringUTF(hello.c_str()); //返回hello from c++ 字符串
}

gradle 配置

指定CMake配置文件路径

android {
   externalNativeBuild {
        cmake {
            path "CMakeLists.txt"  //默认用cmake进行编译,这里指定编译文件路径
        }
    }
}

CMakeList.txt CMake配置文件

这里具体的构建方法参考此官方链接:

Cmake构建方式

CMakeList具体内容

# 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.设置库的名字,名字需要跟你在cpp文件中C++的文件一样。同时会在build文件中生成libnative-lib.so文件
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s). 设置cpp文件的路径
             src/main/cpp/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. 使用是ndk自带的日志库。
              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.  //如果奥是用log-lib,需要link到我们自己的库native-lib
                       native-lib

                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )
CmakeList官方文档的中文翻译

创建 CMake 构建脚本

如果您的原生源文件还没有 CMake 构建脚本,则您需要自行创建一个并包含适当的 CMake 命令。CMake 构建脚本是一个纯文本文件,您必须将其命名为 CMakeLists.txt。本部分介绍了您应包含到构建脚本中的一些基本命令,用于在创建原生库时指示 CMake 应使用哪些源文件。

注:如果您的项目使用 ndk-build,则不需要创建 CMake 构建脚本。提供一个指向您的 Android.mk 文件的路径,将 Gradle 关联到您的原生库。

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

从 IDE 的左侧打开 Project 窗格并从下拉菜单中选择 Project 视图。
右键点击 您的模块 的根目录并选择 New > File。
注:您可以在所需的任意位置创建构建脚本。不过,在配置构建脚本时,原生源文件和库的路径将与构建脚本的位置相关。

输入“CMakeLists.txt”作为文件名并点击 OK。
现在,您可以添加 CMake 命令,对您的构建脚本进行配置。要指示 CMake 从原生源代码创建一个原生库,请将 cmake_minimum_required() 和 add_library() 命令添加到您的构建脚本中:

# Sets the minimum version of CMake required to build your native library.
# This ensures that a certain set of CMake features is available to
# your build.

cmake_minimum_required(VERSION 3.4.1)

# Specifies a library name, specifies whether the library is STATIC or
# SHARED, and provides relative paths to the source code. You can
# define multiple libraries by adding multiple add.library() commands,
# and CMake builds them for you. When you build your app, Gradle
# automatically packages shared libraries with your APK.

add_library( # Specifies 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 )

使用 add_library() 向您的 CMake 构建脚本添加源文件或库时,Android Studio 还会在您同步项目后在 Project 视图下显示关联的标头文件。不过,为了确保 CMake 可以在编译时定位您的标头文件,您需要将 include_directories() 命令添加到 CMake 构建脚本中并指定标头的路径:

add_library(...)
# Specifies a path to native header files.
include_directories(src/main/cpp/include/)

CMake 使用以下规范来为库文件命名:
lib库名称.so

例如,如果您在构建脚本中指定“native-lib”作为共享库的名称,CMake 将创建一个名称为 libnative-lib.so 的文件。不过,在 Java 代码中加载此库时,请使用您在 CMake 构建脚本中指定的名称:

static {
    System.loadLibrary(“native-lib”);
}

注:如果您在 CMake 构建脚本中重命名或移除某个库,您需要先清理项目,Gradle 随后才会应用更改或者从 APK 中移除旧版本的库。要清理项目,请从菜单栏中选择 Build > Clean Project。

Android Studio 会自动将源文件和标头添加到 Project 窗格的 cpp 组中。使用多个 add_library() 命令,您可以为 CMake 定义要从其他源文件构建的更多库

添加 NDK API

Android NDK 提供了一套实用的原生 API 和库。通过将 NDK 库包含到项目的 CMakeLists.txt 脚本文件中,您可以使用这些 API 中的任意一种。

预构建的 NDK 库已经存在于 Android 平台上,因此,您无需再构建或将其打包到 APK 中。由于 NDK 库已经是 CMake 搜索路径的一部分,您甚至不需要在您的本地 NDK 安装中指定库的位置 - 只需要向 CMake 提供您希望使用的库的名称,并将其关联到您自己的原生库。

将 find_library() 命令添加到您的 CMake 构建脚本中以定位 NDK 库,并将其路径存储为一个变量。您可以使用此变量在构建脚本的其他部分引用 NDK 库。以下示例可以定位 Android 特定的日志支持库并将其路径存储在 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 )

为了确保您的原生库可以在 log 库中调用函数,您需要使用 CMake 构建脚本中的 target_link_libraries() 命令关联库:

find_library(...)

# Links your native library against one or more other native libraries.
target_link_libraries( # Specifies the target library.
                       native-lib

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

NDK 还以源代码的形式包含一些库,您在构建和关联到您的原生库时需要使用这些代码。您可以使用 CMake 构建脚本中的 add_library() 命令,将源代码编译到原生库中。要提供本地 NDK 库的路径,您可以使用 ANDROID_NDK 路径变量,Android Studio 会自动为您定义此变量。

以下命令可以指示 CMake 构建 android_native_app_glue.c,后者会将 NativeActivity 生命周期事件和触摸输入置于静态库中并将静态库关联到 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} )

添加其他预构建库
添加预构建库与为 CMake 指定要构建的另一个原生库类似。不过,由于库已经预先构建,您需要使用 IMPORTED 标志告知 CMake 您只希望将库导入到项目中:

add_library( imported-lib
             SHARED
             IMPORTED )

然后,您需要使用 set_target_properties() 命令指定库的路径,如下所示。

某些库为特定的 CPU 架构(或应用二进制接口 (ABI))提供了单独的软件包,并将其组织到单独的目录中。此方法既有助于库充分利用特定的 CPU 架构,又能让您仅使用所需的库版本。要向 CMake 构建脚本中添加库的多个 ABI 版本,而不必为库的每个版本编写多个命令,您可以使用 ANDROID_ABI 路径变量。此变量使用 NDK 支持的一组默认 ABI,或者您手动配置 Gradle 而让其使用的一组经过筛选的 ABI。例如:

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 )

为了确保 CMake 可以在编译时定位您的标头文件,您需要使用 include_directories() 命令,并包含标头文件的路径:

include_directories( imported-lib/include/ )

注:如果您希望打包一个并不是构建时依赖项的预构建库(例如在添加属于 imported-lib 依赖项的预构建库时),则不需要执行以下说明来关联库。

要将预构建库关联到您自己的原生库,请将其添加到 CMake 构建脚本的 target_link_libraries() 命令中:

target_link_libraries( native-lib imported-lib app-glue ${log-lib} )

在您构建应用时,Gradle 会自动将导入的库打包到 APK 中。您可以使用 APK 分析器验证 Gradle 将哪些库打包到您的 APK 中。如需了解有关 CMake 命令的详细信息,请参阅 CMake 文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值