第一章:NDK开发环境搭建

各位同学好,我是你们的老朋友。今天咱们来聊聊NDK开发的第一步——环境搭建。说实话,这一步看似简单,但坑还真不少。我见过太多人卡在环境配置上,代码还没写一行,先被各种报错搞崩溃了。

嗯,咱们直接进入正题。

1.1 Android Studio配置NDK

先说说Android Studio。现在新版的AS其实已经内置了NDK支持,但默认不下载。你需要手动去SDK Manager里勾选。

具体操作:打开Settings → Appearance & Behavior → System Settings → Android SDK → SDK Tools。找到「NDK (Side by side)」和「CMake」,勾上,点Apply。等它下载完就行。

小提示:我个人习惯用最新稳定版的NDK。别追最新,也别用太老的。比如现在稳定版是r26,我就用这个。太新的可能有兼容问题,太老的有些新特性不支持。

下载完后,记得检查一下local.properties文件。里面应该有类似这样的配置:

ndk.dir=/Users/你的用户名/Library/Android/sdk/ndk/26.1.10909125
cmake.dir=/Users/你的用户名/Library/Android/sdk/cmake/3.22.1

如果没自动生成,手动加上也行。我在项目中遇到过好几次,AS更新后这个文件被重置了,导致编译报错。所以每次更新完AS,我都会看一眼这个文件。

1.2 CMake vs ndk-build:到底选哪个?

这个问题我经常被问到。说白了,现在新项目基本都用CMake。为什么?

  • CMake:语法更现代,跨平台支持好,Android Studio原生支持。你写个CMakeLists.txt,AS自动帮你处理编译流程。
  • ndk-build:老古董了。基于Makefile,语法比较原始。但如果你在维护老项目,或者有现成的Android.mk文件,那也没必要硬改。

我个人建议:新项目一律用CMake。除非你接手的是遗留代码,否则别给自己找麻烦。

避坑指南:我曾经在一个项目里同时用了CMake和ndk-build,结果两个构建系统互相干扰,编译出来的so文件版本不一致。后来花了整整一天才排查出来。所以记住:一个项目只用一个构建系统。

来看看CMakeLists.txt的基本结构:

cmake_minimum_required(VERSION 3.22.1)
project("my_native_lib")

add_library(
    my_native_lib
    SHARED
    native-lib.cpp
)

find_library(
    log-lib
    log
)

target_link_libraries(
    my_native_lib
    ${log-lib}
)

嗯,就这么简单。你只需要指定源文件、依赖库,CMake帮你搞定剩下的。

1.3 ABI架构详解

ABI,全称Application Binary Interface。说白了,就是你的so文件要跑在什么CPU上。

目前主流的有三种:

ABI 架构 适用设备 建议
armeabi-v7a 32位ARM 老设备、低端机 保留,但非必须
arm64-v8a 64位ARM 2015年后几乎所有新机 必须保留
x86_64 64位x86 模拟器、少数平板 调试用,发布可去掉

你想想看,现在还有多少32位的新手机?很少了。所以我的建议是:

  • 发布版本:只保留arm64-v8a。体积小,兼容性好。
  • 调试版本:加上x86_64,方便模拟器测试。
  • 兼容老设备:保留armeabi-v7a。但说实话,我去年开始已经放弃这个了。
注意:千万别把所有ABI都打进去!我见过一个APK,因为包含了所有ABI,体积直接飙到200MB。用户下载时骂声一片。后来我们只保留arm64-v8a,体积降到40MB,问题解决。

在build.gradle里配置ABI过滤:

android {
    defaultConfig {
        ndk {
            abiFilters 'arm64-v8a', 'x86_64'
        }
    }
}

这样编译时只会生成这两个架构的so文件。

1.4 实战:第一个NDK项目

光说不练假把式。咱们来创建一个真正的NDK项目。

  1. 打开Android Studio,新建项目,选择「Native C++」模板。
  2. AS会自动生成CMakeLists.txt和native-lib.cpp。
  3. 在MainActivity里调用native方法:
public class MainActivity extends AppCompatActivity {
    static {
        System.loadLibrary("my_native_lib");
    }

    public native String stringFromJNI();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }
}

对应的C++代码:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapp_MainActivity_stringFromJNI(
    JNIEnv* env,
    jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

运行一下,看到「Hello from C++」了吗?恭喜你,第一个NDK项目跑起来了。

我的经验:刚开始学NDK时,别急着写复杂逻辑。先把这个Hello World跑通,感受一下JNI的调用流程。我当年就是太心急,一上来就想搞音视频解码,结果被各种崩溃折磨得怀疑人生。

好了,第一章的内容就到这里。环境搭好了,项目跑通了,接下来咱们就可以深入JNI和C++的混合编程了。记住:基础不牢,地动山摇。把环境搭建这一步走稳,后面会顺利很多。

有什么问题,欢迎在评论区留言。咱们下章见。