返回首页

Android 的 JNI C++ 包装器:类和回调

本文描述了通过 JNI 在 Android 中为 C++ 类创建 Java 包装器,并将指针存储为 long。使用 ThreadPool 和弱全局引用从原生线程实现了异步回调。提供了完整的代码示例和内存管理建议。

Android 中的 C++ JNI 包装器:从类到原生回调
Advertisement 728x90

Android中C++类的JNI封装:创建与回调实现

通过JNI将面向对象的C++代码集成到Android应用中,需要将其转换为C函数,同时存储对象的指针。C++对象的地址以long类型保存在Java类中,从而实现生命周期管理和多实例支持。

考虑一个基础的C++类:

class NativeClass {
public:
    NativeClass() {}
    ~NativeClass() {}
    void fun() {
        __android_log_write(ANDROID_LOG_ERROR, log_tag, "Hello from native");
    }
};

用于操作该类的JNI函数:

Google AdInline article slot
  • 初始化:jlong Java_com_myprog_example_Native_initNativeClass(JNIEnv* env, jobject thiz) 返回 (jlong) new NativeClass();
  • 方法调用:void Java_com_myprog_example_Native_NativeClassFun(JNIEnv env, jobject thiz, jlong cppClass) 调用 ((NativeClass) cppClass)->fun();
  • 销毁:void Java_com_myprog_example_Native_destroyNativeClass(JNIEnv env, jobject thiz, jlong cppClass) 执行 delete (NativeClass) cppClass;

Java封装类:

public class Native { 
    static { System.loadLibrary("Native"); } 
    public static native long initNativeClass();
    public static native void destroyNativeClass(long cppClass);
    public static native void NativeClassFun(long cppClass);
}

public class NativeClass {
    long cppClass;
    NativeClass() {
        this.cppClass = Native.initNativeClass();
    }
    @Override protected void finalize() {
        free();
    }
    private void free() {
        Native.destroyNativeClass(cppClass);
    }
    public void fun() {
        Native.NativeClassFun(cppClass);
    }
}

实现从原生代码的回调

对于异步场景,如网络扫描,原生代码需要在操作期间调用Java方法。使用C++中的Listener接口和ThreadPool在单一线程中序列化调用。

扩展支持回调的C++类:

Google AdInline article slot
class NativeClass {
public:
    class Listener {
    public:
        virtual void print(const char* str) = 0;
        virtual ~Listener() {}
    };
private:
    bool started = false;
    Listener* listener = nullptr;
    static void worker(NativeClass* nc) {
        while(nc->started) {
            if(nc->listener != nullptr) {
                nc->listener->print("From native");
            }
            sleep(1);
        }
    }
public:
    NativeClass() {}
    ~NativeClass() {
        if(listener != nullptr) delete listener;
    }
    void setListener(Listener* listener) {
        this->listener = listener;
    }
    void scan() {
        started = true;
        std::thread thread = std::thread(worker, this);
        thread.join();
        started = false;
    }
    void stop() {
        started = false;
    }
};

Java接口和封装类:

public interface NativeListener {
    void print(String str);
}

public class NativeClass {
    long cppClass;
    // 构造函数、finalize、free方法同上
    public void scan() { Native.NativeClassScan(cppClass); }
    public void stop() { Native.NativeClassStop(cppClass); }
    public void setListener(NativeListener listener) {
        Native.NativeClassSetListener(cppClass, listener);
    }
}

关键组件——使用JNI在C++中实现Listener:

class Listener : public NativeClass::Listener {
private:
    JavaVM* jvm;
    JNIEnv* env;
    jweak listenerRef;
    jmethodID printMethod;
public:
    Listener(JavaVM* jvm, JNIEnv* env, jobject listener) {
        this->jvm = jvm;
        listenerRef = env->NewWeakGlobalRef(listener);
        ThreadPool::execute({
            jvm->AttachCurrentThread(&env, NULL);
            if(env != NULL) {
                jobject localRef = env->NewLocalRef(listenerRef);
                if(localRef != NULL) {
                    jclass clazz = env->GetObjectClass(localRef);
                    printMethod = env->GetMethodID(clazz, "print", "(Ljava/lang/String;)V");
                    env->DeleteLocalRef(localRef);
                }
            }
        });
    }
    ~Listener() {
        ThreadPool::execute({
            env->DeleteWeakGlobalRef(listenerRef);
            jvm->DetachCurrentThread();
        });
    }
    void print(const char* msg) {
        ThreadPool::execute({
            jobject localRef = env->NewLocalRef(listenerRef);
            if(localRef != NULL) {
                jstring jMsg = env->NewStringUTF(msg);
                env->CallVoidMethod(localRef, printMethod, jMsg);
                env->DeleteLocalRef(jMsg);
                env->DeleteLocalRef(localRef);
            }
        });
    }
};

设置监听器的JNI函数:

Google AdInline article slot
JNIEXPORT void JNICALL Java_com_myprog_example_Native_NativeClassSetListener(
    JNIEnv* env, jobject thiz, jlong cppClass, jobject listener) {
    ((NativeClass*) cppClass)->setListener(new Listener(jvm, env, listener));
}

实现建议

  • 单线程ThreadPool:确保从固定线程进行顺序JNI调用。
  • 弱引用(jweak):防止内存泄漏;仅在调用期间创建强本地引用。
  • 生命周期管理:Listener析构函数通过ThreadPool清理资源。

关键实践列表:

  • 使用本地引用前始终检查是否为NULL。
  • 对长期引用使用NewWeakGlobalRef并及时删除。
  • 仅在ThreadPool内调用AttachCurrentThread/DetachCurrentThread
  • 对于同步回调,添加条件变量。
  • 避免在UI线程上执行阻塞操作,如scan()

核心要点

  • 将C++对象指针以long类型存储在Java中,提供完整的对象语义。
  • 单线程ThreadPool对于从原生线程进行稳定的JNI调用至关重要。
  • 弱全局引用(jweak)+本地引用最小化内存泄漏风险。
  • Java封装类的finalize方法自动清理原生资源。
  • 异步回调适用于网络扫描等实时任务。

— Editorial Team

Advertisement 728x90

继续阅读