Android Studio NDK开发--JNI编程

一、如何安装配置NDK

1、对于Android Studio,点击Setting 在Android SDK -> SDK Tools,找到Android NDK选项,勾选并下载;

2、下载成功后,一般在你Android Studio配置的SDK的 sdk 目录下能找到 ndk-bundle目录;

3、设置环境变量,新建系统变量, NDK_HOME 值为  D:\adt-bundle\sdk\ndk-bundle,然后在 编辑用户变量 path,在后面添加 ;%NDK_HOME%

4、Win +R 命令打开 cmd命令行,输入命令  ndk-build 后, 如果环境变量没有配置成功或者项目所在目录存有空格,就会提示 “ *** Android NDK: Aborting.    .  Stop. ”;

二、JNI编程之Java调用C方法

1、在NdkUtil类中,添加native 方法 和静态代码块加载 .so 库,代码如下:

public class NdkUtil {
    static {
        System.loadLibrary("myJniTest");
    }

    // Java调用C方法
    public static native String getCLanguageString();

    // C调用Java方法,提供入口
    public static native void callJavaMethodFromJni();
}
在JniTestActivity中调用C方法,并显示在TextView;
public class JniTestActivity extends AppCompatActivity {
    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_jni_test);

        textView = (TextView) findViewById(R.id.textView);
        textView.setText(NdkUtil.getCLanguageString());

        // C调用Java方法入口
        NdkUtil.callJavaMethodFromJni();
    }
}

2、选择 Build -> Make Project ,Make之后生成 .class文件,生成的.class文件 位于 build/intermediates/classes/debug/ 包名

3、打开cmd,进入 项目/app/src/main/java 目录,进入到java目录下,使用javah命令在main目录下生成 jni目录,其中有生成的 .h 头文件;

命令如下:

cd D:\AndroidProject\CommonToolsApp\app\src\main\java
javah -d ../jni com.xss.mobile.activity.jnitest.NdkUtil
      生成的.h头文件如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_xss_mobile_activity_jnitest_NdkUtil */

#ifndef _Included_com_xss_mobile_activity_jnitest_NdkUtil
#define _Included_com_xss_mobile_activity_jnitest_NdkUtil
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xss_mobile_activity_jnitest_NdkUtil
 * Method:    getCLanguageString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_getCLanguageString
  (JNIEnv *, jclass);

/*
 * Class:     com_xss_mobile_activity_jnitest_NdkUtil
 * Method:    callJavaMethodFromJni
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_callJavaMethodFromJni
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

4、编辑.cpp文件,在jni目录下 新建 com_xss_mobile_activity_jnitest_NdkUtil.cpp 文件,实现上一步在头文件生成的方法,代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <Android/log.h>
#include "com_xss_mobile_activity_jnitest_NdkUtil.h"

#define TAG "tag_myJniTest"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)  // 定义DEBUG类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)   // 定义INFO类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)   // 定义WARN类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)  // 定义ERROR类型

#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_xss_mobile_activity_jnitest_NdkUtil
 * Method:    getCLanguageString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_getCLanguageString
        (JNIEnv *env, jclass clazz) {
    return env->NewStringUTF("Hello, world!");
}

JNIEXPORT void JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_callJavaMethodFromJni
(JNIEnv *env, jclass clazz) {
    LOGI("%s", "-----begin C-----");
}

#ifdef __cplusplus
}
#endif

5、配置NDK目录,在 local.properties 文件中指明 sdk 和 ndk 的路径:

ndk.dir=F\:\\adt-bundle-windows-x86_64-20140917\\sdk\\ndk-bundle
sdk.dir=F\:\\adt-bundle-windows-x86_64-20140917\\sdk
6、设置ndk参数、名字、发布平台等,如下所示:
defaultConfig {
        applicationId "com.xss.myjni"
        minSdkVersion 16
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        ndk {
            moduleName "myjni"
            ldLibs "log", "z", "m"
            abiFilters "armeabi", "armeabi-v7a", "x86"
        }
    }
7、生成.so文件,再次选择 Build -> Make Project 就可以生成 .so 文件。会出现不支持NDK的错误提示,可根据提示修改,在项目 gradle.properties中添加:
  #使用遗弃的NDK;
  android.useDeprecatedNdk=true   

8、针对三种不同平台的 CPU 生成三种不同的 .so 文件,生成的 .so文件位于 build/intermediates/debug/lib;
9、安装运行。

三、JNI编程之C调用Java方法

JNI调用Java方法,提供一个native方法,在生成的.cpp文件中,完成该方法的逻辑调用,通过JNIEnv指针提供的FindClass 和 GetMethodID 找到对应的类和方法。

1、新建供C调用的类和方法,代码如下:

public class JniHandle {
    /**
     * C调用Java的方法,可理解为C需要获取UI线程中的某个参数,以此传参于C
     * @return
     */
    public static String getStringFromStatic() {
        Log.d("JniHandle", "c invoke Java");
        return "String from static method in Java";
    }
}

2、通过javap命令获取 Java类签名,命令如下;

cd D:\AndroidProject\CommonToolsApp\app\build\intermediates\classes\debug
javap -s com.xss.mobile.activity.jnitest.JniHandle
生成方法签名如下:
Compiled from "JniHandle.java"
public class com.xss.mobile.activity.jnitest.JniHandle {
  public com.xss.mobile.activity.jnitest.JniHandle();
    descriptor: ()V

  public static java.lang.String getStringFromStatic();
    descriptor: ()Ljava/lang/String;
}
3、在com_xss_mobile_activity_jnitest_NdkUtil.cpp 文件中补全Java_com_xss_mobile_activity_jnitest_NdkUtil_callJavaMethodFromJni 方法体,代码如下:
JNIEXPORT void JNICALL Java_com_xss_mobile_activity_jnitest_NdkUtil_callJavaMethodFromJni(JNIEnv *env, jclass clazz) {
    LOGI("%s", "-----begin C-----");
    LOGI("%s", "This is at C");
    // 得到JniHandler java类
    jclass jniHandle = env->FindClass("com/xss/mobile/activity/jnitest/JniHandle");
    if (NULL == jniHandle) {
        LOGI("%s", "Cant find JniHandle");
    }
    // 通过GetStaticMethodID获取静态方法
    jmethodID method = env->GetStaticMethodID(jniHandle, "getStringFromStatic", "()Ljava/lang/String;");
    if (NULL == method) {
        env->DeleteLocalRef(jniHandle);
        LOGI("%s", "Cant find method getStringFromStatic() from JniHandle");
        return;
    }
    jstring result = (jstring)env->CallStaticObjectMethod(jniHandle, method);
    const char *resultChar = env->GetStringUTFChars(result, NULL);
    env->DeleteLocalRef(jniHandle);
    env->DeleteLocalRef(result);
    LOGI("%s", resultChar);
    LOGI("%s", "-----end C-----");
}

Tips:

1)DeleteLocalRef方法表示释放局部引用,VM释放局部引用有两种方式:一是本地方法执行完毕后VM自动释放;二是通过DeleteLocalRef手动释放。

2)VM可以自动释放,为什么还要手动释放?

      A:因局部变量会阻止它所引用的对象被GC回收,它所引用的对象无法被GC回收,自己本身也就无法自动释放,因此需要使用DeleteLocalRef手动释放。

4、在JniTestActivity中打开入口供C调用Java方法:

// C调用Java方法入口
        NdkUtil.callJavaMethodFromJni();

四、如何打印JNI C代码日志

1、导入log头文件:  #include <Android/log.h>

2、在Android.mk 文件中添加:LOCAL_LDLIBS :=-llog   (一般会自动添加上)

3、定义LOG 函数,先定义一个全局变量,再定义一些输出的LOG函数,代码如下:

#define TAG "tag_myJniTest"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)  // 定义DEBUG类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)   // 定义INFO类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)   // 定义WARN类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)  // 定义ERROR类型

4、调用: LOGI("%s", "-----end C-----");

五、上述整个工程运行结果如下:


六、JNI编程之C和C++的区别

1、hello.c文件。在C中没有引用,传递的env是两级指针,用(*env)方法调用并且要在方法中传入 env参数。

     hello.cpp文件。C++中env为一级指针,用env->调用方法,无需传入env;C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern "C"进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名;extern "C"{jni代码}

c调用:
return (*env)->NewStringUTF(env, "Hello, world!");
c++调用:
return env->NewStringUTF("Hello, world!");

2、Android.mk文件中,需要更改为对应的文件名:

LOCAL_SRC_FILES := (is C)? hello.c : hello.cpp

相关链接:

Android NDK开发流程:http://jingyan.baidu.com/article/a501d80c1a9aa4ec630f5e8b.html

Android NDK开发——Java调用:https://segmentfault.com/a/1190000005760699

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值