Android Studio 3.X NDK 开发基础

Posted 福州-司马懿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Studio 3.X NDK 开发基础相关的知识,希望对你有一定的参考价值。

NDK 环境配置

下载 NDK,File --> Project Structure,根据自己的位置配置环境。到时候这些都会在 Androd Studio 中编程一个个环境变量的定义,通过 $$ 来指定(这个后面会介绍)。

新建项目,创建JNI目录

在项目上右键 --> 新建文件夹 --> 选择JNI文件夹

Traget Source Set 选 “main”,即可,然后会发现仅在 main 目录下面多了一个 jni,其他的包括 build.gradle 之类的都没变。


创建JNI类

public class JniInterface 
    static 
        System.loadLibrary("LibJni");
    
    public native String getMessage(String name, boolean isMale, int age);

创建命令

File --> Settings --> Tools --> External Tools 添加命令

android Studio 自带的环境变量可以使用右侧的 “Insert Macros” 添加

Program:	$JDKPath$\\bin\\javah.exe
Arguments:	-classpath . -jni -d $ModuleFileDir$\\src\\main\\jni $FileClass$
Working directory:	$ModuleFileDir$\\src\\main\\java

classpath 表示class的搜索路径
. 号表示当前路径
-d 用来指定生成的 .h 头文件放置的路径
-o 用来指定生成的 .h 头文件名称,默认以类全路径名称生成(包名 + 类名.h)
注意:-d 和 -o 只能使用其中一个参数。

三个宏定义

  • $JDKPath$ 是在 Android Studio 中设置JDK目录位置
  • $ModuleFileDir$ 是module的根路径
  • $FileClass$ 是对应文件的类名

javah 生成头文件(静态注册)

有三种方法生成头文件

  • 使用 External Tools 集成命令
  • 在 Terminal 中使用 javah 命令
  • 在 Terminal 中使用 javac 生成 class 文件,然后再用 javah 生成头文件

使用 External Tools 集成命令

然后在 jni 对应的 java 文件右击 --> External Tools --> javah

在 Terminal 中使用 javah 命令

上面的 External Tools 实际上就是在 Terminal 执行 javah 命令帮你生成相应的头文件

"C:\\Program Files\\Java\\jdk1.8.0_201\\bin\\javah.exe" -classpath . -jni -d C:\\workspace\\JniDemo\\app\\src\\main\\jni com.example.jnidemo.JniInterface

这个是生成的 com_example_jnidemo_JniInterface.h 文件

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

#ifndef _Included_com_example_jnidemo_JniInterface
#define _Included_com_example_jnidemo_JniInterface
#ifdef __cplusplus
extern "C" 
#endif
/*
 * Class:     com_example_jnidemo_JniInterface
 * Method:    getMessage
 * Signature: (Ljava/lang/String;ZI)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_JniInterface_getMessage
  (JNIEnv *, jobject, jstring, jboolean, jint);

#ifdef __cplusplus

#endif
#endif
  • “ifndef”、“define” 这个是用来避免重复引用头文件的。
  • extern “C” 的主要作用就是为了能够正确实现 C++ 代码调用其他 C 语言代码。加上extern “C” 后,会指示编译器这部分代码按 C 语言的进行编译,而不是 C++的。
  • 函数以 Java 打头,然后后面跟类名加函数名
  • JNIEnv类型实际上代表了Java环境
  • jobject 如果 native 方法不是static的话,这个 obj 就代表这个 native 方法的类实例;如果 native 方法是 static 的话,这个 obj 就代表这个 native 方法的类的 class 对象

在 Terminal 中使用 javac 生成 class 文件,再取出头文件

  • Terminal 的初始化位置是项目路径
  • cd 到目标 java 文件路径下,然后执行 javac 命令生成 class
  • cd 到 src\\main\\java 路径下,在执行 javah 生成头文件
  • 最后删除 class 这个中间临时文件
C:\\workspace\\JniDemo>cd app\\src\\main\\java\\com\\example\\jnidemo
C:\\workspace\\JniDemo\\app\\src\\main\\java\\com\\example\\jnidemo>javac JniInterface.java
C:\\workspace\\JniDemo\\app\\src\\main\\java\\com\\example\\jnidemo>cd ..\\..\\..\\
C:\\workspace\\JniDemo\\app\\src\\main\\java>javah -jni com.example.jnidemo.JniInterface

生成的目录结构如下

生成的 JniInterface.class 文件如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.example.jnidemo;

public class JniInterface 
    public JniInterface() 
    

    public native String getMessage(String var1, boolean var2, int var3);

    static 
        System.loadLibrary("LibJni");
    

如果你觉得一直 cd 比较麻烦,可以直接使用拖拽对应目录到 Terminal 的方式,这样 Terminal 会新建一个 Session,并将该目录作为初始目录

还有一种方法生成 class,就是利用 android studio 的 build 自动生成 class 文件

通过在 Windows 资源管理器搜索对应 java 文件名,可以找到生成的 class 文件路径。我这里的是在 app\\build\\intermediates\\javac\\debug\\compileDebugJavaWithJavac\\classes\\com\\example\\jnidemo。之所以通过 Windows 资源管理器去搜索,是因为随着 Android Studio 的更新路径可能不断的变化,目前还没有一个固定的路径。

最后还是跟之前方法一样,通过class生成头文件。

创建 CPP 文件

将头文件复制一下,重命名时,把前面的类名去掉。
然后在cpp文件中删除宏定义,将原 include 改为该头文件即可。

JniInterface.cpp

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

/*
 * Class:     com_example_jnidemo_JniInterface
 * Method:    getMessage
 * Signature: (Ljava/lang/String;ZI)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jnidemo_JniInterface_getMessage
    (JNIEnv *env, jobject jobject, jstring _name, jboolean _isMale, jint _age) 
    char str[50];
    const char *name = env->GetStringUTFChars(_name, 0);
    sprintf(str, "%s is %s age is %d !", name, _isMale ? "male, his" : "female, her", _age);
    return (*env).NewStringUTF(str);

javap 获取函数签名

有时候需要在 NDK 里面获取 Java 的方法,这时候就需要获取其签名了

有两种方法

  • 在 Terminal 中使用 javap 命令
  • 使用 External Tools 集成命令

在 Terminal 中使用 javap 命令

首先要找到实际生成的 class 文件,位置如下:module/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes,右击,选择 “Open In Terminal”

使用 javap -h 命令可以打印帮助选项
使用 javap 就可以获取对应函数的签名了(签名为 descriptor 的值),有两种方法

  • C:\\workspace\\FastLion\\util\\build\\intermediates\\javac\\debug\\compileDebugJavaWithJavac\\classes> javap -s DESUtil
  • javap.exe -classpath C:\\workspace\\FastLion\\util\\build\\intermediates\\javac\\debug\\compileDebugJavaWithJavac\\classes -s com.tool.crypto.DESUtil

使用 External Tools 集成命令

File -> Settings -> Tools -> External Tools,添加一个 javap 命令

三个宏定义

  • $JDKPath$ 是在 Android Studio 中设置JDK目录位置
  • $OutputPath$ 是生成类的路径
  • $FileClass$ 是对应文件的类名

    使用 External Tools 执行的程序,就不是在 Terminal 里,而是在 Run 选项卡中了。

使用 gradle 编译 NDK

新版的 Android Studio 已经不再使用之前 eclipse 的 mk 来指定 C++ 文件了,而改用 gradle 进行配置

首先,在 File --> Settings --> Appearance & Behavior --> System Settings --> Android SDK 中确保 NDK、LLDB、CMake 均已安装

  • CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake
  • LLDB 是新一代高性能调试器,其是一组可重用组件的集合,这些组件大多是 LLVM 工程中的类库,如 Clang 表达式解析器或 LLVM 反汇编程序等。LLDB 是 Xcode 中默认的调试器,并且支持调试 C/C++ 程序

在对应 module 的 build.gradle 进行如下配置

  • 在 defaultConfig 中添加
//使用 Cmake 工具
externalNativeBuild 
    cmake 
        cppFlags ""
        //生成多个版本的so,出于包大小和向下兼容性的考虑,去掉其他包
        abiFilters 'arm64-v8a', 'armeabi-v7a'//, armeabi, 'x86_64', 'x86', "mips", "mips64"
    

  • 在 android 下配置 CMakeLists.txt 路径
//配置 CMakeLists.txt 路径
externalNativeBuild 
    cmake 
        path "CMakeLists.txt"
    

在对应 module 下添加 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.
# CMakeLists.txt
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.
# 设置so文件名称.
LibJni
# Sets the library as a shared library.
# 设置这个so文件为共享.
SHARED
# Provides a relative path to your source file(s).
# 设置关联的源文件路径.
src/main/jni/JniInterface.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.
# 制定目标库,一般设置成跟so名字一样
LibJni
# Links the target library to the log library
# included in the NDK.
$log-lib )

主要修改如下三个加红框的地方

  • add_library 将指定的源文件生成链接文件,然后添加到工程中去
  • STATIC、SHARED、MODULE的作用是指定生成的库文件的类型
    • STATIC 是目标文件的归档文件,在链接其它目标的时候使用(通俗意思就是:静态库和程序化为一体,不会分开)
    • SHARED SHARED 会被动态链接(动态链接库),在运行时会被加载
    • MODULE 是一种不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数。默认状态下,库文件将会在于源文件目录树的构建目录树的位置被创建,该命令也会在这里被调用。
  • target_link_libraries 将目标文件与库文件进行链接
开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系

以上是关于Android Studio 3.X NDK 开发基础的主要内容,如果未能解决你的问题,请参考以下文章

android studio ndk配置和ndk开发

android studio ndk开发总结

Android Studio编译开源项目(含NDK开发)常见报错

Android Studio NDK初探

Android NDK用Android studio开发步骤:

Android 逆向Android 进程注入工具开发 ( Visual Studio 开发 Android NDK 应用 | 使用 Makefile 构建 Android 平台 NDK 应用 )(代码