Android NDK编程

一、NDK简介

1.1 什么是NDK

        NDK是一系列工具的集合,全称为Android Native Development Kit,用于帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的;

1.2 为什么使用NDK

        1. 方便不同平台之间的代码移植;
        2. 方便进行库的重复使用;
        3. 在某些情况下提性能,特别是像游戏这种计算密集型应用;
        4. 使用第三方库,现在许多第三方库都是由C/C++库编写的,比如Ffmpeg这样库;
        5. 不依赖于Dalvik Java虚拟机的设计;
        6. 代码的保护,由于APK的Java层代码很容易被反编译,而C/C++库反编译难度大;

1.3 JNI和NDK的关系

1.3.1 简述

        JNI负责Java与C/C++进行互相操作,NDK提供工具方便在Android平台使用JNI;

1.3.2 详解

        JNI提供一些列的接口,允许Java类与C/C++等本地编辑语言(在JNI中,这些语言被称为 本地语言)编写的应用 程序、模块 、库进行交互操作。比如,在Java类中使用C语言库中的函数或在C语言中使用 Java类库,都需要借助JNI;

        Android NDK是一个开发工具集,提供一系列工具快速开发C/C++的动态库,并能自动将 .so/.dll 和 Java 应用一起打包到Apk;NDK提供工具可以方便JNI调用C/C++,而且提供了交叉编译器可以修改.mk文件生成特定CPU平台的动态库,并能将so和java应用一起打包到apk中;

二、NDK开发实例-使用命令

2.1 NDK开发包

        接下来以Ubuntu虚拟机为平台,介绍如何安装NDK开发环境; 

2.1.1 NDK开发包下载

        下载地址:NDK 下载  |  Android NDK  |  Android Developers

         下载完后解压

2.1.2 加入环境变量

        1. 打开终端,在当前home目录下执行 sudo gedit .bashrc,在打开的文件的最后位置输入如下内容,并保存退出;

# set NDK env
NDKROOT=/media/zhuziwen/code/Myfile/NDK/android-ndk-r23b-linux
export PATH=$NDKROOT:$PATH

        2. 执行 source .bashrc,使配置的环境变量生效;

2.2 NDK开发步骤

        接下来将讲述如何通过NDK完成C语言动态链接库的生成,再通过JNI来调用该动态库返回运算结果;

        主要工作是:编写Java代码,使用native声明需要本地实现的方法add;通过javah -jni命令生成带有JNI样式的头文件;使用C代码实现该函数并编写相应的编译规则文件Android.mk;最后由ndk-build命令生成动态链接库,并将其打包到应用程序。接下来将描述具体步骤:

2.2.1 编写Java源代码 

        源代码功能:定义整型a作为保存AddNum反馈的结果,并最终显示在TextView上;

        1. 代码第16行的add方法在使用NDK开发的.so动态链接库中完成;

        2. Java源代码中native声明add为本地方法,比如使用C/C++实现;

        3. 一般使用static块加载动态库,该块中使用System.loadLibrary()加载库,myjni为动态库的名字;

        Java代码如下:

        1. com.android.ndkdemo.MainActivity.java

package com.android.ndkdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView tv1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int a = add(5,4);	//需要相加的两个数字
        tv1 = findViewById(R.id.tv);
        tv1.setText("a = " + a);	//在屏幕上显示a的结果
    }

    public native int add(int a, int b);
    static
    {
        //装载lib*.so文件
        System.loadLibrary("NativeAdd");
    }

}

2.2.2 生成Java头文件

        1. 拷贝 jni.h 头文件

        (1)生成头文件之前,需要从Linux系统安装的JDK目录下将 jni.h 文件拷贝到当前NDK根目录;

        (2)先要查找Linux系统当前安装的JDK目录,查找方法参考如下文章;

        查找JDK目录:在linux中查看jdk的版本以及安装路径-CSDN博客

        (3)在你的JDK目录的include目录下有一个jni.h的文件,将其复制到NDK根目录下即可;

        2. 生成 com_android_ndkdemo_MainActivity.h 头文件

        进入NDK根目录下,输入命令javah -jni com.android.ndkdemo.MainActivity,生成JNI样式的头文件,即com_addnum_AddNum.h,该文件中含有JNI函数的声明(这里属于静态注册);

        com_android_ndkdemo_MainActivity.h代码内容如下:

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

#ifndef _Included_com_android_ndkdemo_MainActivity
#define _Included_com_android_ndkdemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_android_ndkdemo_MainActivity
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

注意点:

        (1)生成JNI头文件后,记得把其中的 #include 改成 #include "jni.h",因为我们使用的是当前目录下的 jni.h 头文件,并不是配置好的环境变量目录下的 jni.h 文件;

        jint JNICALL Java_com_android_ndkdemo_MainActivity_add  (JNIEnv *, jobject, jint, jint)即是本地方法中需要实现的方法,需要保持方法名一致,否则将导致错误。native对应的函数名要以Java_开头,后面分别跟着com_android_ndkdemo(包名)、MainActivity(类名)、add(函数名)。

        关于JNI基本原理和函数结构,见博客:https://blog.csdn.net/qq_41739313/article/details/121947136

2.2.3 编写C代码和Android.mk

        (1)在根目录下(即NDK目录)编写C代码AddNum.c,直接通过return a+b实现加法功能;

#include "com_android_ndkdemo_MainActivity.h"

JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b)
{
    return a + b;
}

        (2)编写Android.mk,代码如下:

LOCAL_PATH := $(call my-dir)	# 获取当前路径,并保存在LOCAL_PATH中

NDK_PROJECT_PATH := .
include $(CLEAR_VARS)		# 由编译系统提供,GNU MAKEFILE清除LOCAL_XXX变量,除了LOCAL_PATH以外
LOCAL_MODULE := NativeAdd	# 指定编译出的库名
LOCAL_SRC_FILES := AddNum.c	# 指定编译源文件
include $(BUILD_SHARED_LIBRARY) # 指定编译成动态链接库,BUILD_STATIC_LIBRARY为静态库

说明:

        参考:Android.mk 用法解析2_09的专栏-CSDN博客_call my-dir

2.2.4 编写Application.mk

        (1)在NDK根目录下创建Application.mk文件,内容如下:

APP_BUILD_SCRIPT := Android.mk	# 指明用于构建Android项目的mk文件的位置
APP_PLATFORM := android-17	    # 设置使用的ndk版本,使其与本地下载的ndk版本保持一致
APP_ALLOW_MISSING_DEPS := true	# 与APP_PLATFORM := android-17一起起作用

注意点:

        不加Application.mk会出现如下报错 :

        (1)不加APP_BUILD_SCRIPT := Android.mk,会导致找不到Android.mk,具体报错信息如下:

Android NDK: Your APP_BUILD_SCRIPT points to an unknown file: ./jni/Android.mk    
/media/zhuziwen/code/Myfile/NDK/android-ndk-r23b-linux/android-ndk-r23b/build/core/add-application.mk:88: *** Android NDK: Aborting...    .  Stop.

        (2)不加 APP_PLATFORM 和 APP_ALLOW_MISSING_DEPS ,会出现如下报错:

Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16. 

2.2.5 生成动态链接库

        在终端,进入NDK根目录,输入 ndk-build NDK_PROJECT_PATH=. NDK_APPLICATION_MK=Application.mk 命令,即可生成动态链接库,该命令执行过程中,会根据Android.mk中规定的编译规则将AddNum.c编译成libNativeAdd.so。

        如果编译成功,会在NDK根目录下生成libs和obj目录,在libs目录中有几个子目录,每个子目录对应一个平台,在其中都有一个libNativeAdd.so的动态链接库文件,将其复制到NDK根目录下;

Android NDK编程_第1张图片

2.2.6 运行测试

        打开AVD模拟器或者把apk安装到手机上,就可以看到通过调用本地方法实现加法功能,输出结果正确;​

Android NDK编程_第2张图片

三、 NDK开发实例-使用AS

        本节主要讲解在Linux环境下如何使用ndk-build构建工具来进行NDK开发,以及ndk-build构建工具在Android Stuido中的快捷工具配置。

        关于其中涉及到的JNI的相关知识与原理,见博客:https://blog.csdn.net/qq_41739313/article/details/121947136

3.1 安装NDK工具包

        在SDK Tools中安装NDK开发环境(File > Settings > Appearance & Behavior > System Settings > Android SDK > SDK Tools):

Android NDK编程_第3张图片

3.2 新建NDK项目

        (1)正常新建一个Android空项目;

        (2)进入project显示模式,在src -> main下创建文件夹jni,用于编写原生代码;

                        Android NDK编程_第4张图片

3.3 快捷键配置

        在Android Studio中,可以将 javah -jni 命令和 ndk-build 命令配置成快捷键,博主未配置成功,感兴趣的同学可以参考Android NDK开发(二) 使用ndk-build构建工具进行NDK开发 - 简书

        本文采取直接在Android Studio界面下方的Terminal中执行相关命令的方式,见后文;

3.4 创建Java文件

        在java目录下com.android.ndkdemo包下创建并编辑Java文件;

package com.android.ndkdemo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView tv1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        int a = add(5,4);	//需要相加的两个数字
        tv1 = findViewById(R.id.tv);
        tv1.setText("a = " + a);	//在屏幕上显示a的结果
    }

    public native int add(int a, int b);
    static
    {
        //装载lib*.so文件
        System.loadLibrary("NativeAdd");
    }

}

3.5 生成Java头文件

        (1)cd app/src/main/java

        (2)javah -jni com.android.ndkdemo.MainActivity  该命令结束后,就在java目录下生成了头文件com_android_ndkdemo_MainActivity.h,内容如下,不用再修改jni.h了;

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_android_ndkdemo_MainActivity */

#ifndef _Included_com_android_ndkdemo_MainActivity
#define _Included_com_android_ndkdemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_android_ndkdemo_MainActivity
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

3.6 编写C代码和Android.mk

        (1)在app/src/main/jni目录下新建AddNum.c文件,内容如下,与上一节一致;

#include "com_android_ndkdemo_MainActivity.h"

JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b)
{
    return a + b;
}

        (2)在app/src/main/jni目录下新建Android.mk文件(jni目录上鼠标右键,New -> C/C++ File),内容如下;

#include "com_android_ndkdemo_MainActivity.h"

JNIEXPORT jint JNICALL Java_com_android_ndkdemo_MainActivity_add(JNIEnv *env, jobject thiz, jint a, jint b)
{
    return a + b;
}

3.7 新建Application.mk

        (1)在app/src/main/jni目录下新建Application.mk,内容如下;

APP_BUILD_SCRIPT := Android.mk	# 指明用于构建Android项目的mk文件的位置
APP_PLATFORM := android-17	# 设置使用的ndk版本,使其与本地下载的ndk版本保持一致
APP_ALLOW_MISSING_DEPS := true	# 与APP_PLATFORM := android-17一起起作用

3.8 ndk-build

        (1)将 com_android_ndkdemo_MainActivity.h 头文件拷贝到jni目录;

        (2)cd ../jni

        (3)ndk-build NDK_APPLICATION_MK=Application.mk

        (4)ndk-build执行后会在jni所在的同级目录生成一个libs文件,其中每个子文件夹下都有一个libNativeAdd.so动态库文件,将其拷贝到java/com.android.ndkdemo目录下;

3.9 测试

        这个时候连接手机进行测试,就能看到 界面显示 "a=9"。

附录 

        基础
        | Android NDK开发之JNI基础篇

        实践
        | JNI与NDK编程知识基础详解
        | NDK 编译的三种方式
        | 在 NDK 开发中引入第三方静态库和动态库
        | NDK开发中Native与Java交互
        | Android NDK开发(二) 使用ndk-build构建工具进行NDK开发 - 简书

        进阶与补充
        | NDK开发中的几个重要知识点
        | JNI与NDK编程函数注册与C++调用Java详解
        | JNI与NDK编程常用方法史上最全总结和详解
        | Android NDK开发基础之C语言的内存管理

        文章链接总结
        | JNI与NDK编程(基础到精通)最全总结

你可能感兴趣的:(Android底层开发,android,NDK)