【Android性能优化】使用NDK进行Java和C++混编

转载请注明原文地址

笔者把Android重难点和读书笔记都整理在github上:https://github.com/miomin/AndroidDifficulty

如果你觉得对你有帮助的话,希望可以star/follow一下哟,我会持续保持更新。

一、Java和C/C++混编的步骤

(1)在Java代码中声明本地方法
(2)实现Java本地接口(JNI)粘合层
(3)创建Android makefile文件(Android Studio不需要,Gradle代替)
(4)使用C/C++实现native方法
(5)编译native库
(6)加载native库

1、声明本地方法

  • JniUtils.java
private static native long fibonacciNative(int n);

  写好之后clean然后rebuild,可以看到生成了classes文件夹。

2、实现JNI粘合层

  • 打开Terminal,输入命令,进入到debug文件夹。
cd app/build/intermediates/classes/debug
  • 继续输入命令,生成头文件。
javah -jni com.scu.miomin.learnndk.JniUtils
  • 这个时候查看debug文件夹,可以看到多了一个.h文件,剪切一下,在scr/main下创建jni文件夹,把这个.h文件粘贴进去。

  • 下面上代码

  • com_scu_miomin_learnndk_JniUtils.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_scu_miomin_learnndk_JniUtils */

#ifndef _Included_com_scu_miomin_learnndk_JniUtils
#define _Included_com_scu_miomin_learnndk_JniUtils
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jlong JNICALL Java_com_scu_miomin_learnndk_JniUtils_fibonacciNative
        (JNIEnv *, jclass, jint);

#ifdef __cplusplus
}
#endif
#endif
  • jni.c
//
// Created by miomin on 2015/8/29.
//

#include "com_scu_miomin_learnndk_JniUtils.h"
#include "fibonacci.h"

JNIEXPORT jlong JNICALL Java_com_scu_miomin_learnndk_JniUtils_fibonacciNative
        (JNIEnv *env, jobject obj, jint n) {
    return fibonacci(n);
}
  • fibonacci.h
//
// Created by 莫绪旻 on 16/5/14.
//

#ifndef LEARNNDK_FIBONACCI_H
#define LEARNNDK_FIBONACCI_H

#include <stdio.h>

extern long fibonacci(unsigned int n);


#endif //LEARNNDK_FIBONACCI_H
  • fibonacci.c
//
// Created by miomin on 2015/8/29.
//
#include "fibonacci.h"

long fibonacci(unsigned int n) {
    if (n > 1) return fibonacci(n - 2) + fibonacci(n - 1);
    return n;
}

3、配置gradle

  • 在gradle.properties文件末尾添加android.useDeprecatedNdk=true

  • 在app下的build.gradle中defaultConfig括号内添加如下代码

ndk {
    moduleName "JniUtils"  //生成的so名字
    abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种abi体系结构下的so库,目前可有可无。
}

4、加载native库

  • 在JniUtils.java中添加如下static代码块
static {
        try {
            System.loadLibrary("JniUtils");//之前在build.gradle里面设置的so名字,必须一致}
            success = true;
        } catch (Throwable e) {

        }
}

5、为Native提供备用的Java方案

  • 有些设备不能支持NDK所支持的所有ABI的任意一种,这时候就调用不了native方法,为了方式应用奔溃,我们提供如下解决方案。
/** * Created by miomin on 16/5/13. */
public class JniUtils {

    private static final boolean useNative;

    static {
        boolean success;
        try {
            System.loadLibrary("JniUtils");//之前在build.gradle里面设置的so名字,必须一致}
            success = true;
        } catch (Throwable e) {
            success = false;
        }
        useNative = success;
    }

    public static long fibonacci(int n) {

        if (useNative)
            return fibonacciNative(n);

        return fibonacciJava(n);
    }

    private static long fibonacciJava(int n) {
        if (n > 1)
            return fibonacciJava(n - 2) + fibonacciJava(n - 1);
        return n;
    }

    private static native long fibonacciNative(int n);
}

二、更多关于JNI的信息

1、JNI字符串

  • 在JNI中使用字符串参数传递,经常会导致性能问题,Java使用Unicode编码字符串,而C/C++使用char*(ASC或UTF-8),Java的字符串必须转换成C/C++字符串才能使用。看下面的例子:

  • JniUtils.java

    public static void withString(String s) {

        if (useNative)
            withStringNative(s);
        else
            withStringNative(s);
    }

    private static void withStringJava(String s) {

    }

    private static native void withStringNative(String s);
  • jni.c
JNIEXPORT void Java_com_scu_miomin_learnndk_JniUtils_withStringNative
        (JNIEnv *env, jobject obj, jstring s) {
    const char *str = (*env)->GetStringUTFChars(env, s, NULL);
    if (str != NULL) {
        // 使用str

        // 释放字符串,否则会内存泄漏
        (*env)->ReleaseStringChars(env, s, str);
    }
}

2、在JNI中访问Java对象或方法

  • JniUtils.java

    static {
        boolean success;
        try {
            System.loadLibrary("JniUtils");//之前在build.gradle里面设置的so名字,必须一致}
            success = true;
        } catch (Throwable e) {
            success = false;
        }
        useNative = success;

        if (success)
            getIds();
    }

    // 避免每次访问域时都去获取一次id,应该在类加载时获取,只执行一次
    private static native void getIds();

    public static int i = 0;

    public static void sayHello() {

        if (useNative)
            sayHelloNative();
        else
            sayHelloJava();
    }

    private static native void sayHelloNative();

    private static void sayHelloJava() {

    }

    public static void callFromJNI() {
        Log.i("miomin", "callFromJni");
    }
  • JniUtils.c

static jfieldID iId;
static jfieldID callFromJNIId;

JNIEXPORT void JNICALL Java_com_scu_miomin_learnndk_JniUtils_sayHelloNative
        (JNIEnv *env, jclass clazz) {
    // 增加i
    jint i = (*env)->GetStaticIntField(env, clazz, iId);
    (*env)->SetStaticIntField(env, clazz, callFromJNIId, i + 1);

    // 调用callFromJNI
    (*env)->CallStaticVoidMethod(env, clazz, callFromJNIId);
}

JNIEXPORT void JNICALL Java_com_scu_miomin_learnndk_JniUtils_getIds
        (JNIEnv *env, jclass clazz) {
    // 获取i的域和callFromJNI方法的id
    iId = (*env)->GetStaticFieldID(env, clazz, "i", "I");
    callFromJNIId = (*env)->GetStaticMethodID(env, clazz, "callFromJNI", " ()V");
}

你可能感兴趣的:(android,jni,NDK,Java-C++混编)