下面我将详细讲解“详解AndroidStudio JNI +Gradle3.0以上JNI爬坑之旅”的完整攻略。
什么是JNI
JNI(Java Native Interface),即Java本地接口,是Java提供的一种机制,允许Java代码和本地代码(如C/C++代码)进行交互。可以让Java程序调用C/C++函数,也可以让C/C++程序调用Java函数。在Android开发中,JNI可以用于实现一些对性能要求比较高的操作。
JNI的开发流程
JNI的开发流程主要包括以下几个步骤:
- 定义Java接口和实现类
- 编写对应的C/C++接口和实现代码
- 使用Java Native Interface Development Kit (JDK)提供的工具将C/C++代码编译成可以与Java代码交互的动态库
- 在Java代码中加载动态库并调用对应的C/C++接口
接下来,我将详细介绍如何在Android Studio中使用JNI。
Android Studio中使用JNI
Android Studio自带的Native Development Kit(NDK)可以让我们很方便地开发JNI代码。
配置NDK
我们需要在Android Studio配置NDK,以便能够编译C/C++代码。具体步骤如下:
- 打开build.gradle文件,在defaultConfig节点下添加如下配置,指定最低的支持的ndk版本:
ndk {
abiFilters "armeabi-v7a", "x86"
// 支持ndk版本
ndkVersion "21.3.6528147"
}
- 在local.properties文件中添加以下NDK路径配置:
ndk.dir=/path/to/ndk
- 在gradle.properties文件中配置下面这一行,表示编译时使用C++11:
android.useDeprecatedNdk=true
新建JNI项目
在Android Studio中新建一个Android项目后,我们可以直接在该项目中使用JNI,并调用C/C++代码。
-
在src/main目录下新建一个jni目录。这个目录中的文件将会被编译成动态库。
-
在jni目录下创建一个名为Android.mk的文件。这个文件是用来描述我们需要用NDK编译的源文件的。如下:
```
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := native-lib
LOCAL_SRC_FILES := native-lib.cpp
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
```
以上是一个简单的Android.mk文件的内容。它告诉NDK编译器我们需要编译一个动态库(LOCAL_MODULE),源文件是native-lib.cpp(LOCAL_SRC_FILES),并且需要连接log库(LOCAL_LDLIBS)。
- 在jni目录下创建一个名为Application.mk的文件。这个文件用来描述我们需要使用的NDK版本和CPU架构。如下:
APP_ABI := armeabi-v7a x86
APP_PLATFORM := android-14
以上的Application.mk文件告诉NDK编译器编译出来的动态库可以运行在armeabi-v7a和x86架构的CPU上,并且需要使用Android 4.0以上版本的API。
- 在build.gradle文件中,添加以下配置:
```
android {
...
defaultConfig {
...
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
}
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
}
```
使用JNI
在使用JNI时,需要通过JNI接口来实现Java代码与C/C++代码之间的交互。
我们可以通过Java中的native关键字声明一个本地方法。如下:
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
}
以上代码是在MainActivity中声明了一个native方法stringFromJNI(),该方法返回一个String类型的值。在Activity的onCreate方法中调用了这个方法,并将返回值设置到TextView中。
接下来,我们需要在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());
}
以上代码中,在文件头部包含了jni.h和string头文件。接着,外部C函数Java_com_example_myapplication_MainActivity_stringFromJNI实现了该方法,并返回了一个字符串。我们可以在该函数中实现一些对C/C++更友好的操作。
示例1:复制文件
下面我们来看一个更加实际的例子,假设我们有一个文件在Android设备上,我们要把这个文件复制到另一个位置。虽然Android本身也提供了一些操作文件的API,但是这些API在处理大文件时可能会存在性能瓶颈。这时候,我们可以使用C/C++代码来实现文件复制操作。
在Java层,我们可以定义如下方法:
public native int copyFile(String inputFile, String outputFile);
在native-lib.cpp文件中,我们可以实现这个方法:
#include <jni.h>
#include <string>
#include <fstream>
#include <android/log.h>
#define TAG "copyFile"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_MainActivity_copyFile(
JNIEnv *env,
jobject thiz,
jstring j_input_file,
jstring j_output_file) {
const char *inputFile = env->GetStringUTFChars(j_input_file, NULL);
const char *outputFile = env->GetStringUTFChars(j_output_file, NULL);
std::ifstream input(inputFile, std::ios::binary);
std::ofstream output(outputFile, std::ios::binary);
if (!input.is_open()) {
LOGE("Failed to open input file: %s", inputFile);
return -1;
}
if (!output.is_open()) {
LOGE("Failed to open output file: %s", outputFile);
input.close();
return -2;
}
char buf[1024*1024];
while(input) {
input.read(buf, sizeof(buf));
output.write(buf, input.gcount());
}
input.close();
output.close();
env->ReleaseStringUTFChars(j_input_file, inputFile);
env->ReleaseStringUTFChars(j_output_file, outputFile);
return 0;
}
以上代码中,我们在native函数中使用了标准的C++文件操作API,将一个文件复制到另一个文件中,并返回操作结果。
示例2:OpenCV图像处理
使用JNI可以方便地将OpenCV库映射到Java层,从而方便进行图像处理。
在Java层中,我们可以定义如下方法:
public native void imageProcess(Bitmap bmpIn, Bitmap bmpOut, int threshold);
其中,bmpIn是输入的Bitmap对象,bmpOut是输出的Bitmap对象,threshold是图像处理中的一个阈值,表示灰度图像中的阈值。在实际使用时,我们需要遍历Bitmap对象中的每个像素进行图像处理。
在native-lib.cpp文件中,我们可以实现这个方法:
#include <jni.h>
#include <opencv2/opencv.hpp>
#include <android/log.h>
#include <android/bitmap.h>
#define TAG "imageProcess"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_MainActivity_imageProcess(
JNIEnv* env,
jobject thiz,
jobject bmp_input,
jobject bmp_output,
jint threshold) {
// 将输入和输出的Bitmap转换成Mat对象
AndroidBitmapInfo info_in = {0};
void* pixels_in;
if (AndroidBitmap_getInfo(env, bmp_input, &info_in) < 0) {
LOGE("Failed to get input bitmap info");
return;
}
if (AndroidBitmap_lockPixels(env, bmp_input, &pixels_in) < 0) {
LOGE("Failed to lock input bitmap pixels");
return;
}
cv::Mat mat_in(info_in.height, info_in.width,
info_in.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ?
CV_8UC4 : CV_8UC2,
pixels_in);
AndroidBitmapInfo info_out = {0};
void* pixels_out;
if (AndroidBitmap_getInfo(env, bmp_output, &info_out) < 0) {
LOGE("Failed to get output bitmap info");
return;
}
if (AndroidBitmap_lockPixels(env, bmp_output, &pixels_out) < 0) {
LOGE("Failed to lock output bitmap pixels");
return;
}
cv::Mat mat_out(info_out.height, info_out.width,
info_out.format == ANDROID_BITMAP_FORMAT_RGBA_8888 ?
CV_8UC4 : CV_8UC2,
pixels_out);
// 图像处理
cv::cvtColor(mat_in, mat_out, cv::COLOR_RGBA2GRAY);
cv::threshold(mat_out, mat_out, threshold, 255, cv::THRESH_BINARY);
// 解锁Bitmap
AndroidBitmap_unlockPixels(env, bmp_output);
AndroidBitmap_unlockPixels(env, bmp_input);
}
以上代码中,我们使用OpenCV库对输入的Bitmap进行灰度化和二值化处理,并将处理后的结果保存到输出的Bitmap中。
如果您还有任何关于JNI或Android Studio的问题,欢迎在评论区留言。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:详解AndroidStudio JNI +Gradle3.0以上JNI爬坑之旅 - Python技术站