详解AndroidStudio JNI +Gradle3.0以上JNI爬坑之旅

下面我将详细讲解“详解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的开发流程主要包括以下几个步骤:

  1. 定义Java接口和实现类
  2. 编写对应的C/C++接口和实现代码
  3. 使用Java Native Interface Development Kit (JDK)提供的工具将C/C++代码编译成可以与Java代码交互的动态库
  4. 在Java代码中加载动态库并调用对应的C/C++接口

接下来,我将详细介绍如何在Android Studio中使用JNI。

Android Studio中使用JNI

Android Studio自带的Native Development Kit(NDK)可以让我们很方便地开发JNI代码。

配置NDK

我们需要在Android Studio配置NDK,以便能够编译C/C++代码。具体步骤如下:

  1. 打开build.gradle文件,在defaultConfig节点下添加如下配置,指定最低的支持的ndk版本:

ndk {
abiFilters "armeabi-v7a", "x86"
// 支持ndk版本
ndkVersion "21.3.6528147"
}

  1. 在local.properties文件中添加以下NDK路径配置:

ndk.dir=/path/to/ndk

  1. 在gradle.properties文件中配置下面这一行,表示编译时使用C++11:

android.useDeprecatedNdk=true

新建JNI项目

在Android Studio中新建一个Android项目后,我们可以直接在该项目中使用JNI,并调用C/C++代码。

  1. 在src/main目录下新建一个jni目录。这个目录中的文件将会被编译成动态库。

  2. 在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)。

  1. 在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。

  1. 在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技术站

(0)
上一篇 2023年6月26日
下一篇 2023年6月26日

相关文章

  • linux查看目录大小及硬盘大小

    要查看 Linux 系统中目录的大小以及硬盘的总大小,可以使用以下的方法: 查看当前目录的大小 要查看当前目录的大小,可以使用 du 命令。du 命令用于计算文件或目录占用的磁盘空间,它可以递归显示指定目录的大小,并可控制显示单位的大小。 命令格式如下: du -h –max-depth=1 其中,-h 表示以可读性较好的方式显示出文件大小。–max-d…

    other 2023年6月27日
    00
  • Win10预览版19555.1001更新后开机绿屏怎么办?

    当用户在更新Win10预览版19555.1001后遇到了开机出现绿屏的问题时,可以按照以下攻略来解决: 1. 尝试卸载最新安装的软件 有时候,开机绿屏问题是由于最新安装的软件冲突导致的。因此,可以尝试卸载最新安装的软件,看看是否能够解决问题。 例如,用户最近安装了一个名为ABC的应用程序,他可以打开“设置”>“应用”>“应用和功能”界面,在清单中…

    other 2023年6月27日
    00
  • MySQL变量原理及应用实例

    MySQL变量原理及应用实例攻略 MySQL变量是一种用于存储和操作数据的特殊类型。它们可以在MySQL查询中使用,并且可以存储各种数据类型,如整数、字符串和日期。在本攻略中,我们将详细讲解MySQL变量的原理以及如何在实际应用中使用它们。 1. MySQL变量的原理 MySQL变量是在会话级别中定义和使用的。这意味着变量只在当前会话中可见,并且在会话结束后…

    other 2023年7月29日
    00
  • HttpClient连接池及重试机制解析

    HttpClient连接池及重试机制解析 1. HttpClient连接池 1.1 什么是HttpClient连接池 HttpClient连接池是一个可以存储和重用HTTP连接的池子。当需要进行大量HTTP请求时,可以使用连接池管理HTTP连接的生命周期,以便重复使用并减少连接创建和销毁的开销。 1.2 HttpClient连接池的优点 使用连接池的主要好处…

    other 2023年6月26日
    00
  • MySQL表字段数量限制及行大小限制详情

    MySQL表字段数量限制及行大小限制详情 介绍 MySQL作为流行的关系型数据库管理系统,对于表的字段数量和行大小都做出了限制。本文将详细介绍这些限制规则。 表字段数量限制 MySQL限制表最多可包含的字段数量为4096个。当创建新表时,如果超过了这个限制,会弹出错误提示,例如: CREATE TABLE my_table ( column1 INT, co…

    other 2023年6月25日
    00
  • android利用websocket协议与服务器通信

    下面是“Android利用WebSocket协议与服务器通信”的完整攻略: 1. WebSocket协议简介 WebSocket协议是一种在web浏览器和服务器之间进行全双工通信的标准协议,它通过HTTP/1.1协议中的升级头(Upgrade Header)来建立连接,之后客户端和服务器端就可以进行双向的数据传输。相较于HTTP协议,WebSocket协议具…

    other 2023年6月27日
    00
  • 扩圈app如何查看版本号?扩圈查看版本号方法

    要查看扩圈App的版本号,可以按照以下步骤进行操作: 打开扩圈App:在手机上找到并点击扩圈App的图标,以打开应用程序。 导航到设置页面:一旦你打开了扩圈App,你会看到一个主界面。在主界面上,通常会有一个菜单按钮或者一个设置图标,点击它以进入设置页面。 查找关于页面:在设置页面中,你需要寻找一个关于或者版本信息的选项。这通常在设置页面的底部或者顶部,具体…

    other 2023年8月2日
    00
  • redis服务器环境下mysql实现lnmp架构缓存

    以下是在Redis服务器环境下使用MySQL实现LNMP架构缓存的详细攻略: 安装和配置Redis服务器: 在服务器上安装Redis,并确保Redis服务器正常运行。 配置Redis的内存大小和其他相关参数,以适应您的应用需求。 安装和配置MySQL数据库: 在服务器上安装MySQL数据库,并确保MySQL服务器正常运行。 创建您的数据库和相应的表结构,以存…

    other 2023年10月17日
    00
合作推广
合作推广
分享本页
返回顶部