项目接入 C ++
1. 创建文件夹
main 下面创建 cpp 文件夹 src/main/cpp
在该文件夹中创建一个简单的 C++ 文件,例如 native-lib.cpp
:
// src/main/cpp/native-lib.cpp
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
2. 创建 CMakeLists.txt
在 src/main/cpp
文件夹中创建一个 CMakeLists.txt
文件:
# 指定 CMake 的最低版本要求。
cmake_minimum_required(VERSION 3.4.1)
# 添加一个库,设置库的名称为 native-lib,并指定它是一个共享库(即动态链接库),
# 然后提供一个相对路径到源文件 native-lib.cpp。
add_library(
native-lib # 库的名称
SHARED # 库类型:共享库(动态库)
native-lib.cpp # 源文件
)
# 查找 log 库,并将其路径存储在 log-lib 变量中。
find_library(
log-lib # 路径变量的名称
log # 要查找的 NDK 库名称
)
# 指定目标库,并链接到 log 库。
target_link_libraries(
native-lib # 目标库,即我们要生成的 native-lib 库
${log-lib} # 要链接的库,即在上一步中找到的 log 库
)
3. 修改 build.gradle
在你的 app
级别 build.gradle
文件中添加 externalNativeBuild
配置:
android {
...
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
ndkVersion '20.0.5594570'
}
dependencies {
...
}
4. 声明本地方法
在你的 MainActivity
中声明本地方法:
// MainActivity.java
package com.example.myapplication;
import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
public native String stringFromJNI();
}
jni 回调 java 层的方法
在 JNI(Java Native Interface)中,从本地代码回调 Java 方法涉及几个步骤,包括获取 Java 类和方法的引用,并使用 JNI 提供的 API 调用该方法。以下是一个详细的步骤说明以及示例代码,展示如何从 JNI 回调 Java 层的 onError(int errorMsg)
方法。
1. 定义 Java 方法
首先,确保在 Java 类中定义了 onError(int errorMsg)
方法。例如:
public class MyClass {
public void onError(int errorMsg) {
// 错误处理代码
System.out.println("Error: " + errorMsg);
}
}
2. 声明 Native 方法
在 Java 类中声明一个 native 方法,用于从本地代码触发回调。例如:
public class MyClass {
public void onError(int errorMsg) {
// 错误处理代码
System.out.println("Error: " + errorMsg);
}
// 声明 native 方法
public native void nativeMethod();
// 加载本地库
static {
System.loadLibrary("native-lib");
}
}
3. 实现 Native 方法
在本地代码中实现 nativeMethod
,并在该方法中调用 onError
方法。以下是一个完整的示例:
实现文件 ( native-lib.cpp)
编写本地方法的实现文件,例如 MyClass.c
:
#include <jni.h>
#include <stdio.h>
// 实现 nativeMethod
extern "C"
JNIEXPORT void JNICALL Java_top_xlxs_studydemo_ndk_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
// 获取 MyClass 类
jclass cls = env->GetObjectClass(obj);
if (cls == NULL) {
return; // 类未找到
}
// 获取 onError 方法的 ID
jmethodID mid = env->GetMethodID(cls, "onError", "(I)V");
if (mid == NULL) {
return; // 方法未找到
}
// 调用 onError 方法,传递错误码 10086
env->CallVoidMethod(obj, mid, 10086);
}
参数对应关系
在 JNI 中,Java 方法的签名由方法的参数类型和返回类型组成。对于 onError(int errorMsg)
方法,其签名是 (I)V
,其中:
I
表示int
类型参数。V
表示void
返回类型。
在本地代码中,通过 GetMethodID
函数获取方法 ID,需要提供类引用、方法名和方法签名。然后,使用 CallVoidMethod
函数调用该方法,并传递相应的参数。
如果方法为 onError(int errorCode, String errorMsg)
,则对应如下:
#include <jni.h>
#include <stdio.h>
// 实现 nativeMethod
extern "C"
JNIEXPORT void JNICALL Java_top_xlxs_studydemo_ndk_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
// 获取 MyClass 类
jclass cls = env->GetObjectClass(obj);
if (cls == NULL) {
return; // 类未找到
}
// 获取 onError 方法的 ID
jmethodID mid = env->GetMethodID(cls, "onError", "(ILjava/lang/String;)V");
if (mid == NULL) {
return; // 方法未找到
}
// 创建一个 Java 字符串
jstring jErrorMsg = env->NewStringUTF("一些错误信息");
// 调用 onError 方法,传递错误码和错误信息
env->CallVoidMethod(obj, mid, 10086, jErrorMsg);
// 删除局部引用
env->DeleteLocalRef(jErrorMsg);
}
对于 onError(int errorCode, String errorMsg)
方法,其签名是 (ILjava/lang/String;)V
,其中:
I
表示int
类型参数。Ljava/lang/String;
表示String
类型参数。V
表示void
返回类型。
JNI 方法签名例子
在 JNI 中,每个参数类型都有一个特定的表示符号。多个参数类型直接写在一起,形成一个连续的字符串。方法签名由参数类型和返回类型组成,中间没有任何分隔符。以下是一些常见的 JNI 类型表示:
Z
-boolean
B
-byte
C
-char
S
-short
I
-int
J
-long
F
-float
D
-double
L<classname>;
- 对象类型,其中classname
是类的全限定名,使用斜杠/
作为分隔符[type
- 数组类型,其中type
是数组的元素类型V
-void
返回类型
方法签名的格式为:
(参数类型...)返回类型
具体到 onError(int errorCode, String errorMsg)
方法,它有两个参数:一个 int
类型和一个 String
类型,返回类型是 void
。因此,其 JNI 签名为:
(ILjava/lang/String;)V
(
和)
之间的是参数列表:I
表示int
类型。Ljava/lang/String;
表示String
类型,其中Ljava/lang/String;
表示一个 Java 对象类型,这里的java/lang/String
是String
类的全限定名,L
开头,java/lang/String
是类名,用/
分隔包名,最后一个;
表示对象类型的结束。
V
表示返回类型是void
。
举例说明
假设您有一个方法 foo
,它的签名为:
public String foo(int a, boolean b, double[] arr, String msg)
对应的 JNI 签名为:
(IZ[DLjava/lang/String;)Ljava/lang/String;
表示方法参数,其中:
I
表示int
参数。Z
表示boolean
参数。[D
表示double
数组参数。Ljava/lang/String;
表示String
参数。
Ljava/lang/String;
表示返回类型是 String
#include <jni.h>
#include <stdio.h>
#include <string>
// 实现 nativeMethod
extern "C"
JNIEXPORT jstring JNICALL Java_top_xlxs_studydemo_ndk_MyClass_nativeMethod(JNIEnv *env, jobject obj) {
// 获取 MyClass 类
jclass cls = env->GetObjectClass(obj);
if (cls == NULL) {
return env->NewStringUTF("Class not found"); // 类未找到
}
// 获取foo方法的ID
jmethodID fooMethod = env->GetMethodID(cls, "foo", "(IZ[DLjava/lang/String;)Ljava/lang/String;");
if (fooMethod == NULL) {
return env->NewStringUTF("Method ID not found");
}
// 准备参数
jint a = 10086;
jboolean b = JNI_TRUE;
jdoubleArray arr = env->NewDoubleArray(3);
jdouble elements[] = {1.0, 2.0, 3.0};
env->SetDoubleArrayRegion(arr, 0, 3, elements);
jstring msg = env->NewStringUTF("Hello from JNI");
// 调用Java方法
auto result = (jstring) env->CallObjectMethod(obj, fooMethod, a, b, arr, msg);
// 获取结果字符串
const char *resultCStr = env->GetStringUTFChars(result, NULL);
// 打印结果(或其他处理)
std::string resultStr = std::string(resultCStr);
// 释放资源
env->ReleaseStringUTFChars(result, resultCStr);
env->DeleteLocalRef(arr);
env->DeleteLocalRef(msg);
// 返回结果
return env->NewStringUTF(resultStr.c_str());
}
扩展
在 JNI 中,除了 CallVoidMethod
外,还有许多其他方法可以调用 Java 对象的方法。根据方法的返回类型,使用不同的 Call<Type>Method
方法。这些方法包括:
根据返回类型调用方法
CallBooleanMethod
:调用返回boolean
的方法。CallByteMethod
:调用返回byte
的方法。CallCharMethod
:调用返回char
的方法。CallShortMethod
:调用返回short
的方法。CallIntMethod
:调用返回int
的方法。CallLongMethod
:调用返回long
的方法。CallFloatMethod
:调用返回float
的方法。CallDoubleMethod
:调用返回double
的方法。CallObjectMethod
:调用返回Object
的方法。
播放器场景的例子
举例从 Java 调用 JNI 函数,以及从 JNI 调用 Java 方法。下面通过简化的 JinPlayer
类和 JNICallbackHelper
类来展示这一过程
Java 端代码
在 JinPlayer
类中,定义了一些 native 方法和回调方法,这些方法会在 JNI 中被调用:
public class JinPlayer implements SurfaceHolder.Callback {
static {
System.loadLibrary("native-lib");
}
private OnPreparedListener onPreparedListener;
private OnErrorListener onErrorListener;
private OnProgressListener onProgressListener;
private SurfaceHolder surfaceHolder;
public JinPlayer() {
}
private String dataSource;
public void setDataSource(String dataSource) {
this.dataSource = dataSource;
}
public void prepare() {
prepareNative(dataSource);
}
public void start() {
startNative();
}
public void stop() {
stopNative();
}
public void release() {
releaseNative();
}
public void onPrepared() {
if (onPreparedListener != null) {
onPreparedListener.onPrepared();
}
}
public void onError(int errorCode) {
if (onErrorListener != null) {
onErrorListener.onError(errorCode);
}
}
public void onProgress(int progress) {
if (onProgressListener != null) {
onProgressListener.onProgress(progress);
}
}
private native void prepareNative(String dataSource);
private native void startNative();
private native void stopNative();
private native void releaseNative();
private native void setSurfaceNative(Surface surface);
private native int getDurationNative();
private native void seekNative(int playValue);
}
JNI 端回调代码
在 JNI 端,我们实现 JNICallbackHelper
类,用于从 JNI 回调 Java 方法:
#include "JNICallbackHelper.h"
JNICallbackHelper::JNICallbackHelper(JavaVM *vm, JNIEnv *env, jobject job) {
this->vm = vm;
this->env = env;
this->job = env->NewGlobalRef(job);
jclass clazz = env->GetObjectClass(job);
jmd_prepared = env->GetMethodID(clazz, "onPrepared", "()V");
jmd_onError = env->GetMethodID(clazz, "onError", "(I)V");
jmd_onProgress = env->GetMethodID(clazz, "onProgress", "(I)V");
}
JNICallbackHelper::~JNICallbackHelper() {
vm = nullptr;
env->DeleteGlobalRef(job);
job = nullptr;
env = nullptr;
}
void JNICallbackHelper::onPrepared(int thread_mode) {
if (thread_mode == THREAD_MAIN) {
env->CallVoidMethod(job, jmd_prepared);
} else {
JNIEnv *env_child;
vm->AttachCurrentThread(&env_child, nullptr);
env_child->CallVoidMethod(job, jmd_prepared);
vm->DetachCurrentThread();
}
}
void JNICallbackHelper::onError(int thread_mode, int error_code) {
if (thread_mode == THREAD_MAIN) {
env->CallVoidMethod(job, jmd_onError, error_code);
} else {
JNIEnv *env_child;
vm->AttachCurrentThread(&env_child, nullptr);
env_child->CallVoidMethod(job, jmd_onError, error_code);
vm->DetachCurrentThread();
}
}
void JNICallbackHelper::onProgress(int thread_mode, int audio_time) {
if (thread_mode == THREAD_MAIN) {
env->CallVoidMethod(job, jmd_onProgress, audio_time);
} else {
JNIEnv *env_child;
vm->AttachCurrentThread(&env_child, nullptr);
env_child->CallVoidMethod(job, jmd_onProgress, audio_time);
vm->DetachCurrentThread();
}
}
JNI 方法实现
在 JNI 中实现 native 方法,例如 prepareNative
:
#include <jni.h>
#include "JNICallbackHelper.h"
// 假设 JNICallbackHelper 对象已经初始化
JNICallbackHelper *callbackHelper = nullptr;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_prepareNative(JNIEnv *env, jobject thiz, jstring data_source) {
// 初始化 JNICallbackHelper
if (callbackHelper == nullptr) {
JavaVM *vm;
env->GetJavaVM(&vm);
callbackHelper = new JNICallbackHelper(vm, env, thiz);
}
// 这里添加准备视频播放的逻辑
// 当准备完成时调用 callbackHelper->onPrepared(...)
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_startNative(JNIEnv *env, jobject thiz) {
// 开始播放逻辑
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_stopNative(JNIEnv *env, jobject thiz) {
// 停止播放逻辑
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_releaseNative(JNIEnv *env, jobject thiz) {
// 释放资源逻辑
if (callbackHelper != nullptr) {
delete callbackHelper;
callbackHelper = nullptr;
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_setSurfaceNative(JNIEnv *env, jobject thiz, jobject surface) {
// 设置 Surface 逻辑
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_JinPlayer_getDurationNative(JNIEnv *env, jobject thiz) {
// 获取视频时长逻辑
return 0;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_JinPlayer_seekNative(JNIEnv *env, jobject thiz, jint play_value) {
// 视频跳转逻辑
}