JNI和NDK编程

Java JNI:Java Native Interface(Java本地接口),是为了方便Java调用C、C++D等本地代码所封装的一层接口。

NDK是Android所提供的一个工具集合,通过NDK可以在Android中更加方便的通过JNI来访问本地代码,比如C/C++,NDK还提供了交叉编译器,开发人员只需简单修改mk文件就可以生成特定CPU平台的动态库。使用NDK的好处:

1.提高代码的安全性。由于so库反编译比较困难。

2.可以方便的调用已有的C/C++开源库。

3.便于平台 的移植。通过C/C++实现的动态库可以方便的在其他平台使用。

4.提高程序在某些特定情形下的执行效率,不能明显提高Android程序的性能。

JNI和NDK比较适合Linux环境下开发,本人使用的是mac,运行在android studio。

在开发之前我们需要在对NDK进行配置;

下载最新的NDK,http://developer.android.com/tools/sdk/ndk/index.html

最简单的是直接用android studio下载,如图所示,也不用进行单独的进行路径设置,很简单。


JNI开发流程

1.创建一个工程,在需要的地方声明native方法。

public class MainActivity extends AppCompatActivity {

    static {
        System.loadLibrary(“jni-test");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView mTextView = (TextView) findViewById(R.id.main_title);
        mTextView.setText(get());
    }

    public native String get();

}

声明了get/set(String) 两个native方法,需要在JNI中实现。在静态方法中有一个加载动态库的过程,其中jni-test(可以随意起名)是so库的标示,so库完整的名称为libjni-test.so,这是加载so库的规范。

2.编译Java源文件得到class文件,然后通过javah命令导出JNI的头文件。

通过javah命令导出JNI的头文件有两种方式。

第一种是终端定位到当前工程的java目录。然后指向命令:

javah  com.ndk.MainActivity 

javah是固定的命令,生成一个一个以.h结尾的c/c++的头文件。

com.ndk表示当前的包名。

特别注意:.MainActivity 表示后面跟的直接是   . + 类(有native方法,需要导出JNI的类,只要该类是在java目录下的包内,都是包名+ . +类名)。


命令行运行完,会直接在包名目录下生成一个.h结尾的JNI头文件。

第二种方式是直接利用android studio添加 javah 的扩展工具,操作步骤如图所示:


特别说明在Tool setting的设置

1.Program: 执行命令,直接填写:javah

2.Parameters:参数,事实上Program Parameters 就直接构成了整个命令行指令,等同于:javah  com.ndk.MainActivity 。所以这里填写的参数要达到包名+ . +类名的作用。点击右边insert macro按钮,会有很多选项的参数设置,如图所示:我们选择了FileClass,它在弹窗的下部展示信息:com.ndk.MainActivity,是符合我们的预期的。


3.Working directory:工作路径,这里需要的参数是到达java目录的全路径,等同于利用终端定位到java目录的效果。点击右边insert macro按钮,选择符合我们预期的选项。


设置完成,要开始运行验证会直接在包名目录下生成一个.h结尾的JNI头文件。

选择运行的方式比较多,如图所示,最简单的是点击类后右键选择External Tool——》javah运行,同样会直接在包名目录下生成一个.h结尾的JNI头文件。


简单介绍.h结尾的JNI头文件的参数和内容:

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

#ifndef _Included_com_ndk_MainActivity
#define _Included_com_ndk_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_ndk_MainActivity
 * Method:    get
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_ndk_MainActivity_get
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

首先函数的格式遵循如下规则:Java_包名_类名_方法名。比如MainActiivty的get方法,这里变成了JNIEXPORT jstring JNICALL Java_com_ndk_MainActivity_get (JNIEnv *, jobject);其中com_ndk是包名,MainActivity是类名,get是方法名,jstring代表的是get方法的返回值是String类型的参数。Jave与JNI的数据类型之间有对应的关系,感兴趣的大家可以自己查找一下。在这里知道Java的String对应JNI的jstring。

JNIEnv*:表示一个指向JNI环境的指针,可以通过它来访问JNI提供的接口方法。

jobject:表示java对象中的this。

JNIEXPORT和JNICALL:它们是JNI中定义的宏,可以在jni.h文头文件直接查找。

宏的定义是必须的,它指定extern“c”内部的函数采用C语言的命名风格来编译的,负责当JNI采用C++来实现时,由于c与c++编译过程中对函数的命名不同,导致JNI在衔接时无法根据函数名查到具体的函数,导致JNI调用失败。

3.JNI方法的实现。

JNI方法是指Java中声明的native方法,可以选择C++或者C来显示,他们实现过程类似,少量的区别主要集中在env的操作上,如下;

C++:
jstring Java_com_ndk_MainActivity_get(JNIEnv *env, jobject thiz){
  return env->NewStringUTF("我爱全世界");
}
C:
jstring Java_com_ndk_MainActivity_get(JNIEnv *env, jobject thiz){
  return (*env)->NewStringUTF(env,“我爱全世界”);
}

JNI实现的方式也有两种;

首先JNI的实现要有三个实现文件:test.cpp(前缀名称没有限制)、Android.mk、Application.mk。

.cpp文件在上面有说明不在重复介绍。说一下Android.mk和Application.mk。

//Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := jni-test

LOCAL_SRC_FILES := /Users/renxianju/Downloads/dynamic-load-apk-master/NDK/app/src/main/jni/com_ndk_MainActivity.cpp 

include $(BUILD_SHARED_LIBRARY)

//Application.mk

APP_ABI :=armeabi

在Android中LOCAL_MODULE表示模块的名称,这里的值要和在动态库的名称一直,如在MainActivity中的: System.loadLibrary(“jni-test”)。LOCAL_SRC_FILES表示的需要参与编译的源文件,一般就是我们需要手写的.cpp文件,我们在创建这个类时,可以看到它的属性.

Application.mk中的APP_ABI表示的CPU平台的类型,目前市面上常见的构架平台有armeabi、x86、mips,在移动设备中主要是armeabi,这也是大部分apk只包括armeabi类型的so库的原因。默认情况下NDK会编译各个平台的so库,通过APP_ABI可以指定平台,all表示选择编译所有的平台。

刚才提到的两种实现方式的区别就是;

一是手写Android.mk、Application.mk、.cpp三种文件;二是手写cpp文件,在build.grade中设置Application.mk的信息,通过make Project方法自动生成Android.mk文件。无疑后者更简单一些,充分发挥了android studio的优势。这里就只介绍第二种的使用,第一中的写法更多偏向于Eclipse上使用,外部也有很多资料,所以这里不再叙说。

1.在java包下所以创建一个包存放.h和.cpp文件,建议命名为jni。手写cpp文件时可以直接从自动生成的.h文件中复制方法信息,注意返回值的处理。同时要添加以下参数,不然回报找不到方法的错误:#ifdef __cplusplus    extern "C" {    #endif

完成的.cpp代码:

#include <jni.h>
#ifdef __cplusplus
extern "C" {
#endif
jstring Java_com_ndk_MainActivity_get(JNIEnv *env, jobject thiz){

  return env->NewStringUTF("我爱全世界");
}

#ifdef __cplusplus
}
#endif

2.在工程根目录的gradle.properties最后一行添加声明使用NDK支持:android.useDeprecatedNdk=true

3.在app/build.gradle的默认配置块声明库名称和生成的CPU架构文件,这里模块名称是”jni-test”,那么,对应生成的so库文件名就是“lib+模块名.so”了。abiFilters是设置多平台,abiFilter(”“)是设置单平台的。


4.点击 Make Project 按钮(在Build按钮的第一条),就可以看到生成的不同类型的abi的 libHelloJni.so 库文件,同时自动生成Android.mk文件。

ok,设置完成,点击Android studio运行编译。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值