当前位置: 首页 > news >正文

NDK开发入门终极教程

0 前言

NDK技术的渊源始于3年前,使用so文件的时候了解到NDK技术,并且C语言一直是强项,就鼓捣起NDK开发。在AndroidStduio还没推广的年代,基于eclipse搭建NDK开发环境需要安全依赖开发工具,并且调试起来具备难度。随后AndroidStudio也先后支持nkd-buildcmake使用NDK开发。

参见之前的博客:

eclipse下使用NDK开发so库

AndroidStudio配置NDK开发环境

1 准备工作

1.1 下载 NDK

当前 NDK 稳定版已经 发布到 r15c 。附上各个平台的下载地址:

  1. android-ndk-r15c-windows-x86

  2. android-ndk-r15c-windows-x86_64

  3. android-ndk-r15c-darwin-x86_64

  4. android-ndk-r15c-linux-x86_64

1.2 添加NDK依赖

解压下载好的文件在本地,在 AndroidStudio 工程配置(注意不是 AndroidStudio 工具配置)中指定 NDK 路径。

或者在local.properties文件中指定NDK路径。

1.3 添加cmake支持

在 AndroidStudio 工具配置中,选择 Android SDK -> SDK Tools 中,勾选CMake并安装。

2 新建支持NDk工程

现在的AndroidStduio更支持一种极简方式集成NDK开发支持,即在下图中勾选include C++ support。然后选择C++标准。如C++ 11。建选默认的ToolChain Default

之后正常 run 即可将 C 语言部分生成出 so 文件并打包到 apk 文件中。

3 给工程添加NDK支持

上述方式适合在新的工程中添加 NDK 支持。如何要在现有的项目中添加 NDK 支持,现提供 cmakendk-build 两种方式。

由于在同一个工程中,同时支持 cmakendk-build 两种方式编译 so 文件,因此将 C 源码单独放在 cpp-src 目录。且将 cmakendk-build 区分不同的module进行编译。

3.1 cmake

这是目前最受欢迎的集成方式,AndroidStduio 在创建新工程时默认使用该方式添加 NDK 支持。但在现有的工程中添加 NDK支持,需要手动配置。

创建 cmake module 添加个三个文件。

  1. CMakeLists.txt cmake编译配置文件
cmake_minimum_required(VERSION 3.4.1)

add_library(
        hello-jni # so 库的名称 libhello-jni.so
        SHARED # 设置为分享库
        # 指定C源文件的路径,指向公共cpp-src目录
        ../../../../cpp-src/hello-jni.c
)

find_library(
        log-lib # 设置路径变量名称
        log # 指定CMake需要加载的NDK库
)

# 链接hello-jni库依赖的库,注意下面变量名的配置
target_link_libraries(hello-jni
        ${log-lib}
)
复制代码
  1. AndroidManifest.xml 每个module必须的配置文件,指定packageName。
<?xml version="1.0" encoding="UTF-8" ?>
<manifest package="com.flueky.cmake">

</manifest>
复制代码
  1. Build.gradle 每个module必须的配置文件,用于构建项目。
apply plugin: 'com.android.library'

android {
    compileSdkVersion 28

    defaultConfig{
        externalNativeBuild {
            cmake {
                // 指定配置参数,更多参数设置见 https://developer.android.google.cn/ndk/guides/cmake
                arguments "-DCMAKE_BUILD_TYPE=DEBUG"
                // 添加CPP标准
//                cppFlags "-std=c++11"
            }
        }
    }

    externalNativeBuild {
        cmake {
            // 指定CMake编译配置文件路径
            path "src/main/cpp/CMakeLists.txt"
        }
    }
}
复制代码

关于 CMake 编译参数的设置,更多内容请阅读官方资料。

眼尖的小伙伴已经发现两处配置了 externalNativeBuild。其中第二处的externalNativeBuild配置是生成Gradle Task 可以不运行工程,直接在 ndk-cmake -> Tasks -> other 找到编译 so 文件有关的四个任务。

双击 exeternalNativeBuildDebug 执行任务,如图:

根据路径即可找到生成的so文件。

3.2 ndk-build

这是最传统的 ndk 编译方式。在配置得当的情况下,可以在不打开 AndroidStudio 情况下完成so文件的编译和输出。

创建 ndk-build module ,添加4个文件。

  1. Android.mk
# 讲真,这个参数我看不懂。从 官方demo 抄来的。用于指定源文件的时候使用
abspath_wa = $(join $(filter %:,$(subst :,: ,$1)),$(abspath $(filter-out %:,$(subst :,: ,$1))))

# 指定当前路径
LOCAL_PATH := $(call my-dir)

# 指定源文件路径
JNI_SRC_PATH := $(call abspath_wa, $(LOCAL_PATH)/../../../../cpp-src)

# 声明 clear 变量
include $(CLEAR_VARS)

# 指定 so 库的名称 libhello-jni.so
LOCAL_MODULE    := hello-jni
# 指定 c 源文件
LOCAL_SRC_FILES := $(JNI_SRC_PATH)/hello-jni.c
# 添加需要依赖的NDK库
LOCAL_LDLIBS := -llog -landroid
# 指定为分享库
include $(BUILD_SHARED_LIBRARY)
复制代码

关于 Android.mk 编译参数的设置,更多内容请阅读官方资料

  1. Application.mk
# 指定编译的的so版本
APP_ABI := all
# 指定 APP 平台版本。比 android:minSdkVersion 值大时,会有警告
APP_PLATFORM := android-28
复制代码

关于 Application.mk 编译参数的设置,更多内容请阅读官方资料

  1. AndroidManifext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<manifest package="com.flueky.ndk">

</manifest>
复制代码
  1. build.gradle
apply plugin: 'com.android.library'

android {
    compileSdkVersion 28
    externalNativeBuild {
        ndkBuild {
            // 指定mk文件路径
            path 'src/main/jni/Android.mk'
        }
    }
    defaultConfig {
    }
}
复制代码

上面的externalNativeBuild作用同 CMake方式的一样,用于编译生成 so 文件。 但是 ndk-build 还支持使用命令ndk-build编译 so 文件。 需要将 NDK 路径添加至环境变量。

需要在jni目录下执行该命令:

最后生成的so文件路径如图;

4 实践

4.1 生成头文件

在主 module 中的 MainActivity中添加 native 方法 。使用 javah 编译出头文件。 使用 -d 参数指定头文件的输出目录。

public class MainActivity extends Activity {

    static {
        // 加载 JNI 库
        System.loadLibrary("hello-jni");
    }

    ......

    // 声明 Native 方法
    private native String hello();
}
复制代码

app/src/main/java 目录下执行命令 javah

4.2 编写 C 源码

hello-jni.c文件引用生成的头文件,并编写测试代码。

#include <string.h>
#include <jni.h>
#include "com_flueky_demo_MainActivity.h"
#include "util/log.h"

/**
 * JNI 示例,演示native方法返回一个字符串,Java 源码见
 *
 * ndk-sample/app/src/main/java/com/flueky/demo/MainActivity.java
 */
JNIEXPORT jstring JNICALL
Java_com_flueky_demo_MainActivity_hello( JNIEnv* env,
                                                  jobject thiz )
{
#if defined(__arm__)
    #if defined(__ARM_ARCH_7A__)
        #if defined(__ARM_NEON__)
            #if defined(__ARM_PCS_VFP)
                #define ABI "armeabi-v7a/NEON (hard-float)"
            #else
                #define ABI "armeabi-v7a/NEON"
            #endif
        #else
            #if defined(__ARM_PCS_VFP)
                #define ABI "armeabi-v7a (hard-float)"
            #else
                #define ABI "armeabi-v7a"
            #endif
        #endif
    #else
        #define ABI "armeabi"
    #endif
#elif defined(__i386__)
    #define ABI "x86"
#elif defined(__x86_64__)
    #define ABI "x86_64"
#elif defined(__mips64)  /* mips64el-* toolchain defines __mips__ too */
    #define ABI "mips64"
#elif defined(__mips__)
    #define ABI "mips"
#elif defined(__aarch64__)
    #define ABI "arm64-v8a"
#else
    #define ABI "unknown"
#endif

    LOGD("日志输出示例");

    return (*env)->NewStringUTF(env, "Hello from JNI !  Compiled with ABI " ABI ".");
}
复制代码

4.3 运行截图

页面截图:

日志截图:

5 源码获取

工程源码已开放在GitHub,下载地址。如果您有多余的CSDN积分,不防从这里下载。可以直接编写 C 源码并进行调试和生成 so 文件。

Google 官方资料需要翻墙才可以阅读。想了解翻墙方法,请点SSR

觉得有用?那打赏一个呗。我要打赏

此处是广告:Flueky的技术小站

相关文章:

  • 深入剖析Tomcat(1)
  • Linq To Sql进阶系列 -目录导航
  • 美国少女模仿电影情节“蒙眼驾车” 或遭指控
  • LOJ #6485 LJJ 学二项式定理
  • python最赚钱的4个方向,你最心动的是哪个?
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 组复制官方翻译九、Group Replication Technical Details
  • Kafka在windows下的配置使用
  • IntelliJ IDEA 18 周岁
  • 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
  • 马上搞懂 GeoJSON
  • 阿里国际站新外贸系统上线 助中小企业“数字化出海”
  • 2019-1-21作业
  • bug集合js1--Unexpected token o in JSON at position 1
  • 为什么阿里巴巴不建议在for循环中使用+进行字符串拼接
  • 03Go 类型总结
  • create-react-app项目添加less配置
  • CSS 专业技巧
  • HTTP传输编码增加了传输量,只为解决这一个问题 | 实用 HTTP
  • Java 网络编程(2):UDP 的使用
  • JAVA 学习IO流
  • leetcode386. Lexicographical Numbers
  • LeetCode刷题——29. Divide Two Integers(Part 1靠自己)
  • Linux Process Manage
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • Spring框架之我见(三)——IOC、AOP
  • vue-loader 源码解析系列之 selector
  • 分布式熔断降级平台aegis
  • 记录一下第一次使用npm
  • 如何使用 JavaScript 解析 URL
  • 三栏布局总结
  • 网络应用优化——时延与带宽
  • 优化 Vue 项目编译文件大小
  • 原生Ajax
  • puppet连载22:define用法
  • 京东物流联手山西图灵打造智能供应链,让阅读更有趣 ...
  • 移动端高清、多屏适配方案
  • ## 临床数据 两两比较 加显著性boxplot加显著性
  • #if #elif #endif
  • $.ajax()参数及用法
  • $L^p$ 调和函数恒为零
  • (04)odoo视图操作
  • (3)选择元素——(14)接触DOM元素(Accessing DOM elements)
  • (4)事件处理——(6)给.ready()回调函数传递一个参数(Passing an argument to the .ready() callback)...
  • (Redis使用系列) Springboot 使用redis实现接口幂等性拦截 十一
  • (初研) Sentence-embedding fine-tune notebook
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (六)软件测试分工
  • (一)为什么要选择C++
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • ***php进行支付宝开发中return_url和notify_url的区别分析
  • .bat批处理(三):变量声明、设置、拼接、截取
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .NET 中让 Task 支持带超时的异步等待
  • .NET 中选择合适的文件打开模式(CreateNew, Create, Open, OpenOrCreate, Truncate, Append)