Java和C++通过JNI实现互相调用 安卓native实战案例

最近研究了一下JNI,JNI作为Java/Kotlin 同C/C++相互调用的桥梁,在安卓native开发中我感觉还是比较重要。
本文简要介绍一下他们之间互相调用的流程和注意事项:
借由Android Studio来开发比较简单: 可以在模板上选择native C++生成安卓工程
如果哪里说得不对,想看到的真大佬能够指出,感激不尽!
从晚上十点敲到晚上十一点半,也不容易~

作为C++开发,在Java的代码规范上,和具体名词或者原理上存在知识上的不足,无视无视

  1. 第一个例子是传字符串给native方法进行解析,native返回字符串大小:
package com.nativer.myapplication;

import android.os.Bundle;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        NativeHelper nativeHelper = new NativeHelper();
        Log.v(TAG, "" + nativeHelper.getStringSizeNow("fuck you"));
    }

    private final String TAG = "MainActivity";
}

然后创建一个用于和native层交互的类,里面需要在静态块中加载这个项目生成中CMakeLists.txt指定的的动态库(demo 就按默认生成的来)

package com.nativer.myapplication;

import android.util.Log;

public class NativeHelper {
    // Used to load the 'myapplication' library on application startup.
    static {
        System.loadLibrary("myapplication");
    }

    public NativeHelper() {
    }

    /**
     * A native method that is implemented by the 'myapplication' native library,
     * which is packaged with this application.
     */

    public native int getStringSizeNow(String msg);

    private final String TAG = "NativeHelper";
}

生成C++的C语言式全局函数:比如我的是下面这样,我实现了这个函数:

extern "C"
JNIEXPORT jint JNICALL
Java_com_nativer_myapplication_NativeHelper_getStringSizeNow(JNIEnv *env, jobject thiz,
                                                             jstring msg) {
    // TODO: implement getStringSizeNow()
    return env->GetStringLength(msg);
}

JNIEnv对象提供了哪些函数,这是一个比较需要研究和学习的,可以通过头文件查看,或者上网查阅资料,在这里我就用了他提供的函数返回Java字符串对象的长度。

  1. 第一个例子是Java通过JNI传参给C++,C++通过反射找到当前Java实例的方法,并传递参数给Java方法,上面代码稍作调整:
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        NativeHelper nativeHelper = new NativeHelper();
        //Log.v(TAG, "size:" + nativeHelper.getStringSizeNow("fuck you"));
        nativeHelper.getStringSizeWithCallBack("Hello");
    }

    private final String TAG = "MainActivity";
}

然后在NativeHelper中添加两个方法,一个是native方法,一个是用于C++回调的方法

	public native void getStringSizeWithCallBack(String msg);

    public void onGetSize(int size) {
        Log.v(TAG, "size:" + size);
    }

生成C++的C语言式全局函数:

extern "C"
JNIEXPORT void JNICALL
Java_com_nativer_myapplication_NativeHelper_getStringSizeWithCallBack(JNIEnv *env, jobject thiz,
                                                                      jstring msg) {
    // TODO: implement giveMeSize()

    jclass clz = env->GetObjectClass(thiz);
    jmethodID method = env->GetMethodID(clz, "onGetSize", "(I)V");

    if (clz and method) {
        auto size = env->GetStringLength(msg);

        env->CallVoidMethod(thiz, method, static_cast<jint>(size));
    } 
}

需要强调的是获取方法ID的那个表达式:通过jclass字节码对象和函数名,以及入参类型得到函数签名,前两个参数都好懂,但是参数类型是一个字符串,遇到不同的参数类型,究竟该填什么字符串呢?下面介绍一个方法,不必去记忆具体的字符串:
跳转到当前类编译后的字节码文件.class所在目录,在命令行中输入javap -s -p 类名.class,比如我输入javap -s -p NativeHelper.class就会自动打印出诸如

  public void onGetSize(int);
    descriptor: (I)V

  public native void getStringSizeWithCallBack(java.lang.String);
    descriptor: (Ljava/lang/String;)V

  public native int getStringSizeNow(java.lang.String);
    descriptor: (Ljava/lang/String;)I

将需要回调的Java方法的descriptor后面部分作为字符串填入C++函数中即可。

  1. 非Java调用native方法线程中,回调Java方法,这个是一个重头戏
    需要记住下面几点,我在CSDN上几乎没看到有人讨论,所以列出来,可能我理解的也有偏差
  • JavaVM :Java虚拟机对象,Java的虚拟机有的是整个系统只有一个,有的是一个程序一个,不管怎么说,对于当前进程,只有一个虚拟机对象。【可以在多线程中进行值传递】
  • jobject:当前Java类的实例,【可以在多线程中进行值传递】
  • jclass: 字节码对象,【不可以在多线程中进行值传递,我暂时只在Java调用C++接口运行线程使用】
  • JNIEnv: JNI环境对象,【不可以在多线程中进行值传递,要单独获取】

我在native层 封装了一个C++类,该类模拟多线程环境下回调Java方法。
首先我自己封装了一个线程类,想了解可以查看我以前的博客。链接

然后对Java,我在Native层封账了C++类:

#pragma once

#include <jni.h>
#include <queue>
#include <string_view>
#include "utils_thread.h"

class NativeProcessor final {
public:
    static NativeProcessor &GetInstance();

    ~NativeProcessor() = default;

private:
    class TaskObject : public Task {
    public:
        TaskObject(NativeProcessor *const observer)
                : Task(true), m_observer{observer} {}

        ~TaskObject() override { stop(); }

        bool ThreadProcess() override;

    private:
        NativeProcessor *m_observer;
    };

    class MessageQueue final {
    public:
        MessageQueue() = default;

        ~MessageQueue() = default;

        void Pop(std::string &);

        void Push(std::string &);

    private:
        MessageQueue(MessageQueue &&) = delete;

        MessageQueue(const MessageQueue &&) = delete;

        MessageQueue &operator=(MessageQueue &&) = delete;

        MessageQueue &operator=(const MessageQueue &) = delete;

    private:
        std::mutex m_procLock;
        std::condition_variable m_procCond;
        std::queue<std::string> m_queue;
    };

private:
    NativeProcessor() : m_task{new TaskObject(this)} {}

    NativeProcessor(NativeProcessor &&) = delete;

    NativeProcessor(const NativeProcessor &&) = delete;

    NativeProcessor &operator=(NativeProcessor &&) = delete;

    NativeProcessor &operator=(const NativeProcessor &) = delete;

    void CallBackToJava(std::string const &);

public:
    int Init(JNIEnv *, jobject);

    int Recycle() const;

    void Push(std::string &);

private:
    MessageQueue m_msgQueue;    // 消息队列
    std::unique_ptr<Task> m_task;   // 任务对象

    JavaVM *m_jvm;  // Java虚拟机对象
    jobject m_jobj; // Java类对象
    jclass m_jclz;  // 字节码对象
    jmethodID m_onCallBack;
    std::string_view m_onCallBackMethod = "onCallBack";
};

不必关注里面多线程是怎样实现的,以及消息队列怎么实现的,甚至用到了哪几种设计模式。这里只讨论Java和C++之间的互相调用链路是怎么实现的。

#include <string>
#include "native_process.h"
#include "utils_log.h"

// 此函数位于Java线程
NativeProcessor &NativeProcessor::GetInstance() {
    static NativeProcessor instance;
    return instance;
}

// 此函数位于Java的MainActivity线程
int NativeProcessor::Init(JNIEnv *env, jobject object) {
    if (nullptr != env and nullptr != object) {
        // 初始化JavaVM对象,全局唯一
        env->GetJavaVM(std::addressof(m_jvm));
        m_jobj = env->NewGlobalRef(object);
        m_jclz = env->GetObjectClass(m_jobj);
        m_onCallBack = env->GetMethodID(m_jclz, m_onCallBackMethod.data(),
                                        "([ILjava/lang/String;)V");

        // 启动C++线程
        m_task->start();
        return 0;
    }
    return -1;
}

// 此函数位于C++创建的线程
void NativeProcessor::CallBackToJava(std::string const &msg) {
    // Java虚拟机对象不允许线程共享,当前线程获取,通过反射回调Java方法
    JNIEnv *env = nullptr;
    m_jvm->AttachCurrentThread(std::addressof(env), nullptr);

    jstring string = env->NewStringUTF(msg.data());

    jintArray array = env->NewIntArray(10);
    int array_[9]{0, 1, 2, 3, 4, 5, 6, 7, 8};
    env->SetIntArrayRegion(array, 0, 10, array_);

    // 回调方法
    env->CallVoidMethod(m_jobj, m_onCallBack, array, string);

    // 在当前线程释放虚拟机对象
    m_jvm->DetachCurrentThread();
}

// 此函数位于Java线程
int NativeProcessor::Recycle() const {
    m_task->stop();
    return 0;
}

// 此函数位于Java线程
void NativeProcessor::Push(std::string &msg) {
    m_msgQueue.Push(msg);
}

// C++线程处理函数
bool NativeProcessor::TaskObject::ThreadProcess() {
    // 取消息
    std::string msg{};
    m_observer->m_msgQueue.Pop(msg);

    // 处理消息
    msg += " !";

    // 回调
    m_observer->CallBackToJava(msg);

    return true;
}

void NativeProcessor::MessageQueue::Pop(std::string &msg) {
    std::unique_lock<std::mutex> lock(m_procLock);
    m_procCond.wait(lock, [this] { return !m_queue.empty(); });

    msg = std::move(m_queue.front());
    m_queue.pop();
}

void NativeProcessor::MessageQueue::Push(std::string &msg) {
    std::unique_lock<std::mutex> lock(m_procLock);

    if (m_queue.size() > 20) {
        lock.unlock();
        m_procCond.notify_all();
        return;
    }

    m_queue.push(msg);
    m_procCond.notify_one();
}

然后在Java类中新增以下方法:

    public void onCallBack(int[] array, String string) {
        for (int j : array) {
            Log.v(TAG, "" + j);
        }

        Log.v(TAG, string);
    }
    
	public native void initNative();

    public native void reset();

    public native void pushStringToCpp(String msg);

我想C++ 回调Java方法的时候,传入多个参数,分别为整型数组和字符串
对应生成native方法:

#include "native_process.h"

extern "C"
JNIEXPORT void JNICALL
Java_com_nativer_myapplication_NativeHelper_pushStringToCpp(JNIEnv *env, jobject,
                                                            jstring msg) {
    std::string msgData = JavaStringToCppString(env, msg);
    NativeProcessor::GetInstance().Push(msgData);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_nativer_myapplication_NativeHelper_initNative(JNIEnv *env, jobject thiz) {
    NativeProcessor::GetInstance().Init(env, thiz);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_nativer_myapplication_NativeHelper_reset(JNIEnv *, jobject) {
    NativeProcessor::GetInstance().Recycle();
}

其中用到了我C++类,于是添加上对应头文件
然后我在Java最上层业务的类中改一下:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        NativeHelper nativeHelper = new NativeHelper();
        //Log.v(TAG, "Get:" + nativeHelper.getStringSizeNow("fuck you"));
        // nativeHelper.getStringSizeWithCallBack("Hello");

        nativeHelper.initNative();
        Thread th = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                nativeHelper.pushStringToCpp("fuck you");
            }
        });
        th.start();
    }

    private final String TAG = "MainActivity";
}

新创建一个去模拟不断给native层生产消息的过程,native起了一个线程去,消息队列会通知该线程去取数据来处理,之后native C++会给Java回调
尤其要注意我的C++中注释,以及结合第三个例子开头罗列出来的条款,才能实现在多线程条件下,对Java类方法进行正常回调。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

歪锅锅

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值