Android Studio NDK 完全指南

一、环境配置

1、NDK简介

Android NDK(Native Development Kit),即Android本地开发工具包,是Google为了允许开发者使用C/C++编写本地代码并在Android设备上运行而提供的一份工具和支持库。NDK提供了一系列系统头文件、函数库、调试器等工具,同时也提供了支持C++代码的STL等库文件。使用NDK可以加速CPU密集型任务和让现有C/C++代码可以在Android系统上运行。

2、安装NDK

首先,要在Android Studio中下载NDK,打开新建工程中的Gradle Scripts->build.gradle(Project:为你的项目名),将“classpath ‘com.android.tools.build:gradle:x.x.x’”中的“x.x.x”修改成你的AS版本所需的gradle版本,然后再在app->build.gradle文件中添加以下内容:

android {
    ...
    defaultConfig {
        ...
        externalNativeBuild {
            cmake {
                ...
                //指向 CMakeLists.txt 文件所在的外部编译目录的绝对路径
                path "src/main/cpp/CMakeLists.txt"
            }
        }
    }
    buildTypes {
        ...
        externalNativeBuild {
            cmake {
                ...
                //指向 CMakeLists.txt 文件所在的外部编译目录的绝对路径
                path "src/main/cpp/CMakeLists.txt"
            }
        }
    }
    externalNativeBuild {
        // 定义ndk-build编译脚本的库
        ndkBuild {
            //指向Android.mk文件所在的绝对路径
            path "src/main/jni/Android.mk"
        }
    }
}

这里以使用CMake为编译系统为例,在app->build.gradle文件中添加以下内容:

android {
    ...
    defaultConfig {
        ...
        // 指定支持abi
        ndk {
            abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
        }
    }
    externalNativeBuild {
        cmake {
            //指向 CMakeLists.txt 文件所在的外部编译目录的绝对路径
            path file('src/main/cpp/CMakeLists.txt')
        }
    }
}

3、配置CMakeLists.txt

CMake是一个跨平台的编译系统,Android NDK支持使用CMake_build原生代码。打开Android项目中的app目录,在cpp文件夹下创建一个新的CMakeLists.txt文件。以下是最简单的示例CMakeLists.txt:

cmake_minimum_required(VERSION 3.4.1)
 
# 创建一个动态库
add_library(
    hello     # 库名称
    SHARED     # 库类型:SHARED表示动态库
    hello.c)  # 库源文件
 
# 导入log库
find_library(
    log-lib     # 定义log库名称
    log)        # 指定库名称在系统中的实际文件名称
 
# 将log库链接到hello库中
target_link_libraries(
    hello       # 目标库名称
    ${log-lib}) # 链接库名称

二、JNI开发

1、JNI简介

Java Native Interface (JNI) 是一个Java平台编程接口,允许Java代码和其他语言如C/C++程序进行交互。Java虚拟机提供一个标准的JNI接口,它可以被C/C++编译器所使用,可以将Java垃圾回收机制与C/C++语言灵活地融合在一起。

2、JNI基础

在Java中使用JNI调用本地代码,需要在Java中定义native函数,并生成头文件。以下是一个Java类中定义native函数的示例:

public class NdkJniUtils {
    static {
        System.loadLibrary("ndkJniLib");
    }
 
    public native String sayHello(String name);
}

使用以下命令生成头文件:

javah -d jni -classpath {Java类的.class文件路径} {Java全限定类名(包括包名)}

3、JNI实践

以调用hello.c函数为例,先编写.h文件:

#include <jni.h>
 
JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_MainActivity_hello(JNIEnv *env, jobject instance){
    return (*env)->NewStringUTF(env, (const char *)"Hello NDK!");
}

在.c文件中实现hello.c函数:

#include <stdio.h>
#include <jni.h>
 
JNIEXPORT jstring JNICALL Java_com_example_ndkdemo_MainActivity_hello(JNIEnv *env, jobject instance){
    return (*env)->NewStringUTF(env, "Hello NDK!");
}

4、编译

使用以下命令编译:

gcc -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux hello.c -o libhello.so

将生成的libhello.so库文件拷贝到app/src/main/jniLibs/armeabi-v7a/目录下,通过System.loadLibrary()方法加载即可。

三、混合编程

1、混合编程简介

将Java和C++混合编程的方式,称之为混合编程。混合编程可以让我们在Java层面上方便的使用C++的特性,也可以让我们利用Java的强大库来进行快速开发。

2、JNI编程注意点

在JNI编程中,有几个重要的规则需要遵循:

  • 尽量不要执行耗时任务
  • 尽量避免崩溃或错误
  • 避免资源泄露
  • 锁定Global Reference和Local Reference

3、示例代码

代码示例中将展示如何从Java层面调用C++库。在Java文件中声明native方法:

public static native int add(int a, int b);

在C++文件中实现add()函数,并将其包装在JNICaller类中:

#include <jni.h>
using namespace std;
class JNICaller
{
public:
    static void init(JNIEnv *env);//初始化

    static int add(int a,int b); //导出给Java层面的native方法
};
 
void JNICaller::init(JNIEnv *env)
{
    jclass jniCaller_clazz=env->FindClass("com/example/ndkdemo/JNICaller");//获取Java层面的类的引用
    jmethodID construct=jniEnv->GetMethodID(jniCaller_clazz,<init>,"()V");//获取Java层面的构造函数的引用
 
    //注册native方法
    JNINativeMethod methods[] = 
    {
        {"add", "(II)I", (void *) JNICaller::add}
    };
    jniEnv->RegisterNatives(jniCaller_clazz, methods, sizeof(methods) / sizeof(methods[0]));
 
    printf("init JNI caller success.\n");
}
 
int JNICaller::add(int a,int b)
{
    return a+b;
}

在Java层面中调用add()方法:

JNICaller.init();//初始化JNICaller
int result = JNICaller.add(a, b);//在Java层面中调用JNICaller的add()方法

四、NDK调试

1、NDK调试简介

NDK调试可以让我们在本地开发环境中调试C++层代码,可以极大的提高开发效率。NDK调试可以帮助我们在开发过程中及时发现bug并进行修复。NDK调试分为两种类型:GDB和LLDB

2、NDK调试步骤

将以下代码添加到app->build.gradle文件中:

externalNativeBuild {
    cmake {
        cmake {
            // -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=libs
            // 指定编译生成文件的目录
            arguments "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=${project.buildDir}/libs/${outputPath}"
            // -DCMAKE_BUILD_TYPE=Debug
            // 指定编译类型为Debug模式
            arguments "-DCMAKE_BUILD_TYPE=Debug"
            // -DANDROID_TOOLCHAIN=clang
            // 指定编译器为Clang编译器
            arguments "-DANDROID_TOOLCHAIN=clang"
            // -DANDROID_STL=c++_static
            // 指定STL库为c++_static
            arguments "-DANDROID_STL=c++_static"
        }
    }
    ndkBuild {
        //同上
    }
}

在Android Studio中打开“Edit Configurations”菜单,在“Debugger”选项卡中设置:

  • Debugger Type为Auto
  • Debug Type为Native

点击“Run”菜单中的“Debug 'app'”进行调试

五、NDK压缩

在开发完成之后,需要将so库打包成apk文件。程序中使用的Shared Library(.so文件)原则上都应当打包进APK里。这样的做法直接导致.apk文件的大小增加,不过现在主流的压缩工具如upx等均对.so文件有压缩能力。毫无疑问,对于 .so 文件而言,应该利用好这样的能力来缩减APK包大小。

1、UPX

UPX是一个免费的、便携式的、可扩展的、高性能的文件压缩器,它是一个通用性的可执行文件压缩器。UPX支持Linux、MacOS X和近年出现的Windows安装包压缩,UPX完全符合ELF, PE, MZ, COFF, NLM和OMF格式标准。在NDK中,我们使用UPX来对.so库进行压缩。

2、NDK压缩步骤

在app/build.gradle文件中添加以下配置:

splits {
    abi {
        enable true
        //This property should be set only to true for publishing part of your app as APK. 
        reset()
        include "armeabi-v7a", "x86"
        universalApk true
    }
}
apply from: "../../tools/JniLibsCompression.gradle"

为了使用UPX,需要拷贝tools目录中的JniLibsCompression.gradle,然后添加以下代码到app/build.gradle文件:

apply from: "../../tools/JniLibsCompression.gradle"
 
project.ext.upx_enabled = true
project.ext.upx_options = "best --lzma"
 
android {
   ...
}

最后执行gradlew clean assembleRelease命令打包APK文件,生成的APK文件中的so库已经被压缩。

原创文章,作者:小蓝,如若转载,请注明出处:https://www.506064.com/n/301976.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
小蓝小蓝
上一篇 2024-12-30 16:10
下一篇 2024-12-30 16:10

相关推荐

  • Java JsonPath 效率优化指南

    本篇文章将深入探讨Java JsonPath的效率问题,并提供一些优化方案。 一、JsonPath 简介 JsonPath是一个可用于从JSON数据中获取信息的库。它提供了一种DS…

    编程 2025-04-29
  • 运维Python和GO应用实践指南

    本文将从多个角度详细阐述运维Python和GO的实际应用,包括监控、管理、自动化、部署、持续集成等方面。 一、监控 运维中的监控是保证系统稳定性的重要手段。Python和GO都有强…

    编程 2025-04-29
  • Python应用程序的全面指南

    Python是一种功能强大而简单易学的编程语言,适用于多种应用场景。本篇文章将从多个方面介绍Python如何应用于开发应用程序。 一、Web应用程序 目前,基于Python的Web…

    编程 2025-04-29
  • Python wordcloud入门指南

    如何在Python中使用wordcloud库生成文字云? 一、安装和导入wordcloud库 在使用wordcloud前,需要保证库已经安装并导入: !pip install wo…

    编程 2025-04-29
  • Python小波分解入门指南

    本文将介绍Python小波分解的概念、基本原理和实现方法,帮助初学者掌握相关技能。 一、小波变换概述 小波分解是一种广泛应用于数字信号处理和图像处理的方法,可以将信号分解成多个具有…

    编程 2025-04-29
  • Python字符转列表指南

    Python是一个极为流行的脚本语言,在数据处理、数据分析、人工智能等领域广泛应用。在很多场景下需要将字符串转换为列表,以便于操作和处理,本篇文章将从多个方面对Python字符转列…

    编程 2025-04-29
  • Python初学者指南:第一个Python程序安装步骤

    在本篇指南中,我们将通过以下方式来详细讲解第一个Python程序安装步骤: Python的安装和环境配置 在命令行中编写和运行第一个Python程序 使用IDE编写和运行第一个Py…

    编程 2025-04-29
  • FusionMaps应用指南

    FusionMaps是一款基于JavaScript和Flash的交互式地图可视化工具。它提供了一种简单易用的方式,将复杂的数据可视化为地图。本文将从基础的配置开始讲解,到如何定制和…

    编程 2025-04-29
  • Python起笔落笔全能开发指南

    Python起笔落笔是指在编写Python代码时的编写习惯。一个好的起笔落笔习惯可以提高代码的可读性、可维护性和可扩展性,本文将从多个方面进行详细阐述。 一、变量命名 变量命名是起…

    编程 2025-04-29
  • Python中文版下载官网的完整指南

    Python是一种广泛使用的编程语言,具有简洁、易读易写等特点。Python中文版下载官网是Python学习和使用过程中的重要资源,本文将从多个方面对Python中文版下载官网进行…

    编程 2025-04-29

发表回复

登录后才能评论