0%

JNI 和 NDK 编程(二):NDK 的开发流程

1. 传统 NDK 开发流程概述

  1. 下载并配置 NDK

    • 为 NDK 配置环境变量
    • 使用 ndk-build 命令编译 so 库
  2. 创建一个 Android 项目,并声明所需的 native 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.szy.JniTestApp;

    import ...

    public class MainActivity extends ActionBarActivity {

    static {
    System.loadLibrary("jni-test");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView textView = (TextView) findViewById(R.id.msg);
    textView.setText(get());
    set("Hello World From JniTestApp");
    }

    public native String get();
    public native void set(String str);
    }
  3. 实现 Android 项目中所声明的 native 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // test.cpp
    #include <jni.h>
    #include <stdio.h>

    #ifdef __cplusplus
    extern "C" {
    #endif

    jstring Java_com_szy_JniTestApp_MainActivity_get(JNIEnv *env, jobject thiz) {
    printf("invoke get in C++\n");
    return env -> NewStringUTF("Hello from JNI in libjni-test.so");
    }

    void Java_com_szy_JniTestApp_MainActivity_set(JNIEnv *env, jobject thiz, jstring string) {
    printf("invoke set from C++\n");
    char* str = (char*) env -> GetStringUTFChars(string, NULL);
    printf("%s\n", str);
    env -> ReleaseStringUTFChars(string, str);
    }

    #ifdef __cplusplus
    }
    #endif
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Android.mk
    # Copyright (C) 2009 The Android Open Source Project
    # blablabla

    LOCAL_PATH := $(call my-dir)

    includes $(CLEAR_VARS)

    LOCAL_MODULE := jni-test
    LOCAL_SRC_FILES := test.cpp

    include $(BUILD_SHARED_LIBRARY)
    1
    2
    // Application.mk
    APP_ABI := armeabi // armeabi 是在移动设备中占据主要地位的 CPU 架构平台类型
  4. 切换到 jni 目录的父目录,然后通过 ndk-build 命令编译产生 so 库

    • 需要注意的是,ndk-build 命令会默认指定 jni 目录为本地源码的目录,如果源码存放的目录名不是 jni,那么 ndk-build 则无法成功编译

    • app/src/main 中创建一个名为 jniLibs 的目录,将生成的 so 库复制到 jniLibs 目录中,然后通过 AndroidStudio 编译运行即可

    • 在上面的步骤中,需要将 NDK 编译的 so 库放置到 jniLibs 目录下,这个是 AndroidStudio 所识别的默认目录。如果想使用其他目录,可修改 build.gradle 文件

      1
      2
      3
      4
      5
      6
      android {
      ...
      sourceSets.main {
      jniLibs.srcDir 'src/main/jni_libs'
      }
      }
    • 除了手动使用 ndk-build 命令创建 so 库,还可以通过 AS 来自动编译产生 so 库,需要配置 build.gradle 文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      android {
      ...
      defaultConfig {
      applicationId "com.szy.JniTestApp"
      minSdkVersion 8
      targetSdkVersion 22
      versionCode 1
      versionName "1.0"

      ndk {
      moduleName "jni-test"
      }
      }
      }
    • 接着需要将 JNI 的代码放在 app/src/main/jni 目录下,注意存放 JNI 的代码的目录名必须为 jni。如果不想使用 jni 这个名词,可以如下修改

      1
      2
      3
      4
      5
      6
      android {
      ...
      sourceSets.main {
      jni.srcDirs 'src/main/jni_src'
      }
      }
    • 实际开发中只需要打 armeabi 平台的 so 库即可,如下修改 build.gradle 配置

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      android {
      ...
      productFlavors {
      arm {
      ndk {
      abiFilter "armeabi"
      }
      }

      x86 {
      ndk {
      abiFilter "x86"
      }
      }
      }
      }

2. AS 推荐 NDK 开发流程概述

  1. 开发环境搭建

    • 需要安装的工具:NDK、Cmake/ndk-build、LLDB

    • 配置特定版本的 Cmake

      1
      2
      3
      4
      5
      6
      7
      8
      9
      android {
      ...
      externalNativeBuild {
      cmake {
      ...
      version "cmake-version"
      }
      }
      }
    • 添加 Cmake 到 path 环境变量

    • 配置特定版本的 NDK

      1
      2
      3
      android {
      ndkVersion "major.minor.build" // e.g., ndkVersion '21.3.6528147'
      }
  2. 创建支持 C/C++ 的项目

    • 在向导的 Choose your project 部分中,选择 Native C++ 项目类型
    • 在向导的 Customize C++ Support 部分中,选择 Toolchain Default 使用默认的 Cmake 设置
  3. 配置 Cmake 脚本文件

    • Cmake 是一个纯文本文件,必须命名为:CMakeLists.txt
    • 添加 cmake_minimum_required()add_library() 命令
    • 添加 include_directories() 命令 使编译时 Cmake 找到头文件
    • Cmake 库文件命名规范:liblibrary-name.so
    • 添加 find_library() 命令,以找到 NDK 库并将其路径存储为一个变量
  4. 构建并运行应用(GUI 或命令行)

    • Gradle 调用外部构建脚本 CMakeLists.txt
    • CMake 按照构建脚本中的命令将 C++ 源代码文件 native-lib.cpp 编译到共享的对象库即 so 中,并将其命名为 libnative-lib.so,Gradle 随后会将后者打包到 APK 中
    • 运行时,应用的 MainActivity 会使用 System.loadLibrary() 加载原生库,然后应用就可以使用库的原生函数 stringFromJNI()
    • MainActivity.onCreate() 会调用 stringFromJNI(),后者会返回 Hello from C++,并使用它来更新 TextView
  5. 使用 APK Analyzer 查验 Gradle 打包到 APK 中的 so 库

    • Build > Build Bundles/APK(s) > Build APK(s)
    • Build > Analyzer APK
    • 从 app/build/outputs/apk 目录中选择 APK
-------------------- 本文结束感谢您的阅读 --------------------