一、为什么要用NDK和JNI

在Android开发中,大部分业务逻辑都可以用Java或Kotlin搞定,但有些场景不得不请出"原生代码"这个外援:

  1. 性能敏感场景:比如音视频编解码、图像处理等算法,C++的执行效率比Java高出一个数量级
  2. 复用现有库:很多成熟的开源库(如OpenCV、FFmpeg)都是用C/C++写的
  3. 硬件底层操作:需要直接操作传感器或特定硬件时

不过跨语言调用就像两个说不同语言的人交流,需要个翻译——这就是JNI(Java Native Interface)的作用。

二、搭建NDK开发环境

1. 工具准备(以Android Studio为例)

// build.gradle配置示例
android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                cppFlags "-std=c++17"  // 使用C++17标准
                arguments "-DANDROID_STL=c++_shared"  // 使用动态STL库
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"  // CMake配置文件路径
            version "3.22.1"
        }
    }
}

2. 创建第一个JNI调用

Java层声明native方法:

public class NativeLib {
    // 加载动态库
    static {
        System.loadLibrary("native-lib");
    }
    
    // 声明native方法
    public native String stringFromJNI();
}

C++层实现(注意方法名规范):

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

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

三、JNI数据类型与高级用法

1. 类型映射表

Java类型 JNI类型 C/C++类型
boolean jboolean unsigned char
int jint int
String jstring const char*
int[] jintArray jint*

2. 复杂数据传递示例

Java层传递对象到Native:

public class UserData {
    public int id;
    public String name;
    
    public native void processInNative(UserData user);
}

C++层处理:

extern "C" JNIEXPORT void JNICALL
Java_com_example_UserData_processInNative(
    JNIEnv* env, 
    jobject thiz,
    jobject userObj) {
    
    // 获取Java类引用
    jclass userClass = env->GetObjectClass(userObj);
    
    // 获取字段ID
    jfieldID idField = env->GetFieldID(userClass, "id", "I");
    jfieldID nameField = env->GetFieldID(userClass, "name", "Ljava/lang/String;");
    
    // 读取字段值
    jint id = env->GetIntField(userObj, idField);
    jstring name = (jstring)env->GetObjectField(userObj, nameField);
    
    // 转换成C字符串
    const char* nameStr = env->GetStringUTFChars(name, nullptr);
    
    // 业务处理(示例:打印日志)
    __android_log_print(ANDROID_LOG_INFO, "JNI", 
        "UserID: %d, Name: %s", id, nameStr);
    
    // 释放资源
    env->ReleaseStringUTFChars(name, nameStr);
}

四、原生代码调试技巧

1. LLDB调试配置

在Android Studio的Run -> Edit Configurations中添加:

{
    "type": "lldb",
    "request": "attach",
    "name": "Attach to Native",
    "processId": "${android_pid}"
}

2. 实用调试命令

# 查看线程状态
thread list

# 添加断点
breakpoint set --file native-lib.cpp --line 42

# 查看变量值
frame variable

# 查看内存内容
memory read --size 4 --format x --count 16 0x16fd3a20

3. 日志输出技巧

#include <android/log.h>

#define LOG_TAG "JNI_DEBUG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

void complexCalculation() {
    LOGD("Starting calculation at %lld", getCurrentTime());
    // ...计算逻辑
    if (error) {
        LOGE("Calculation failed with code %d", errorCode);
    }
}

五、实战:性能敏感场景优化

图像处理示例

Java层:

public class ImageProcessor {
    public native void convertToGray(Bitmap bitmap);
    
    static {
        System.loadLibrary("image-processing");
    }
}

C++层实现:

extern "C" JNIEXPORT void JNICALL
Java_com_example_ImageProcessor_convertToGray(
    JNIEnv* env, 
    jobject thiz,
    jobject bitmap) {
    
    AndroidBitmapInfo info;
    void* pixels;
    
    // 获取Bitmap信息
    AndroidBitmap_getInfo(env, bitmap, &info);
    
    // 锁定像素缓冲区
    AndroidBitmap_lockPixels(env, bitmap, &pixels);
    
    // 灰度化处理
    uint8_t* p = (uint8_t*)pixels;
    for(int y = 0; y < info.height; y++) {
        for(int x = 0; x < info.width; x++) {
            uint8_t* pixel = p + y * info.stride + x * 4;
            uint8_t gray = (uint8_t)(0.299 * pixel[0] + 0.587 * pixel[1] + 0.114 * pixel[2]);
            pixel[0] = pixel[1] = pixel[2] = gray;
        }
    }
    
    // 解锁像素缓冲区
    AndroidBitmap_unlockPixels(env, bitmap);
}

六、常见问题解决方案

1. JNI引用管理

// 局部引用会自动释放,但大量创建时需要手动管理
jobject createGlobalRef(JNIEnv* env, jobject obj) {
    jobject globalRef = env->NewGlobalRef(obj);
    // ...使用globalRef
    env->DeleteGlobalRef(globalRef); // 必须手动释放
    return globalRef;
}

// 弱引用示例
jweak createWeakRef(JNIEnv* env, jobject obj) {
    jweak weakRef = env->NewWeakGlobalRef(obj);
    // 使用前需要检查是否被GC
    if (env->IsSameObject(weakRef, NULL)) {
        // 对象已被回收
    }
    return weakRef;
}

2. 异常处理模式

extern "C" JNIEXPORT void JNICALL
Java_com_example_NativeLib_riskyOperation(
    JNIEnv* env, 
    jobject thiz) {
    
    // 清空之前的异常
    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
    }
    
    // 执行可能抛出异常的操作
    jclass cls = env->FindClass("java/lang/NullPointerException");
    if (cls == nullptr) {
        // 处理查找失败
        return;
    }
    
    // 检查是否发生异常
    if (env->ExceptionCheck()) {
        // 转换异常类型
        jthrowable ex = env->ExceptionOccurred();
        env->ExceptionClear();
        env->Throw(ex);
        return;
    }
}

七、技术选型建议

适用场景

  • 计算密集型任务(如3D渲染)
  • 实时性要求高的处理(如音频降噪)
  • 需要复用现有C/C++库的场景

优缺点分析

优点

  • 极致性能优化空间
  • 直接访问底层API
  • 代码安全性更高(反编译难度大)

缺点

  • 开发调试复杂度高
  • 跨平台适配成本大
  • 内存管理风险增加

注意事项

  1. 处理好JNI引用避免内存泄漏
  2. 注意线程安全(AttachCurrentThread调用)
  3. 做好异常边界处理
  4. 考虑ABI兼容性问题

八、总结

掌握NDK开发就像获得了一把瑞士军刀,虽然日常开发可能用不到,但在处理性能瓶颈或复杂计算时能发挥关键作用。建议从简单的JNI调用开始,逐步深入到内存管理和多线程处理,最终实现Java与原生代码的无缝协作。记住:能力越大责任越大,原生代码的错误往往会导致更严重的崩溃,所以务必做好充分的测试和异常处理。