NDK入门(Android Studio)

来源:互联网 发布:蔡夫人知乎 编辑:程序博客网 时间:2024/06/11 19:56

JNI介绍

JNI概念 : Java本地接口,Java Native Interface, 它是一个协议, 该协议用来沟通Java代码和外部的本地C/C++代码, 通过该协议 Java代码可以调用外部的本地代码, 外部的C/C++ 代码可以调用Java代码。

C和Java的侧重 :
– C语言 : C语言中最重要的是 函数 function;
– Java语言 : Java中最重要的是 JVM, class类, 以及class中的方法;

C与Java如何交流 :
– JNI规范 : C语言与Java语言交流需要一个适配器, 中间件, 即 JNI, JNI提供了一种规范;
– C语言中调用Java方法 : 可以让我们在C代码中找到Java代码class中的方法, 并且调用该方法;
– Java语言中调用C语言方法 : 同时也可以在Java代码中, 将一个C语言的方法映射到Java的某个方法上;
– JNI桥梁作用 : JNI提供了一个桥梁, 打通了C语言和Java语言之间的障碍;

JNI中的一些概念 :
– native : Java语言中修饰本地方法的修饰符, 被该修饰符修饰的方法没有方法体;
– Native方法 : 在Java语言中被native关键字修饰的方法是Native方法;
– JNI层 : Java声明Native方法的部分;
– JNI函数 : JNIEnv提供的函数, 这些函数在jni.h中进行定义;
– JNI方法 : Native方法对应的JNI层实现的 C/C++方法, 即在jni目录中实现的那些C语言代码;

NDK详解

1.交叉编译库文件

C代码执行 : C代码被编译成库文件之后, 才能执行, 库文件分为动态库 和静态库 两种;
– 动态库 : unix环境下.so 后缀的是动态库, windows环境下.dll 后缀的是动态库; 动态库可以依赖静态库加载一些可执行的C代码;
– 静态库 :.a 后缀是静态库的扩展名;

库文件来源 : C代码 进行 编译 链接操作之后, 才会生成库文件, 不同类型的CPU 操作系统 生成的库文件是不一样;
– CPU分类 : arm结构, 嵌入式设备处理器; x86结构, pc 服务器处理器; 不同的CPU指令集不同;
– 交叉编译 :windows x86编译出来的库文件可以在arm平台运行的代码;
– 交叉编译工具链 : Google提供的 NDK 就是交叉编译工具链, 可以在linux环境下编译出在arn平台下执行的二进制库文件;

NDK作用 : 是Google提供了交叉编译工具链, 能够在linux平台编译出在arm平台下执行的二进制库文件;

2.NDK优点:

1.java易被反编译,C/C++反编译难度大
2.调用第三方库,如OpenCV是用C/C++写
3.方便代码调用,c/c++所写库,可被Android/iOS所调用

3.NDK使用场合

NDK就是可以直接使用底层接口,主要用来开发大型游戏
SDK提供的是Java的接口,主要用来开发常见的应用,如QQ、微信等
如果要区分,NDK适合有大量计算和图形计算的应用,除此之外的都适合使用SDK

NDK下载

下载链接:http://www.android-dev.cn/tools/sdk/ndk/index.html
之后就可以选择适合的版本了,下载完了之后直接是一个zip的压缩包,解压即可。

编译环境配置

点击OK之后,在local.properties文件中看到:

这里写图片描述

NDK环境搭建还有最后一步,在gradle.properties的文件末尾加上android.useDeprecatedNdk=true。

这样NDK的编译环境就搭建好了。

第一个NDK项目

1、创建一个Android项目
2、创建JNI目录,放置所需要的c/c++/头文件等
3、编写nativejava层方法
4、生成JNI头文件
1 ) 编写java Native代码
2)介绍一个命令javah

首先创建一个工程,这个和普通的Android工程一样,就不说明。

然后新建一个NdkString类,这个类是我自定义的。写上一些代码。

public class NdkString {    //java调C中的方法都需要用native声明且方法名必须和c的方法名一样    public static native String getFromC();}

写完之后重新Make Project,NdkString的class文件就会生成。

...\app\build\intermediates\classes\debug\com\example\ndk

在这个目录下,就能看到所有编译好的class文件。

然后就是生成.h文件,在Android Studio打开Terminal命令行工具,打开步骤是View->Tool Windows->Terminal (或直接按Alt+F12)。
然后在命令行中先进入到工程的main目录下

输入命令:javah -d jni -classpath 自己编译后的class文件的绝对路径

javah -d jni -classpath ..\app\build\intermediates\classes\debug com.example.ndk.NdkString(注意debug后的空格)

命令行中是直接进入到了工程的main目录下(在哪个目录下运行就会在哪个目录下自动生成jni文件夹),按回车之后就会在main目录下生成jni文件夹,同时生成.h文件。

这个文件.h文件不需要做任何修改,默认即可。

现在我们来写一个test的C文件Hello.c同.h文件一样放到jni文件夹下,代码如下:

#include "com_example_ndk_NdkString.h"JNIEXPORT jstring JNICALL Java_com_ht_asndk_NdkString_getFromC        (JNIEnv *env, jclass jclass) {    return (*env)->NewStringUTF(env, "这是我测试的jni");}

函数就是上面定义的native方法getFromC(),我们可以从.h文件中copy函数的引用到.c文件中。

/* * Class:     com_example_ndk_NdkString * Method:    getFromC * Signature: ()Ljava/lang/String; */JNIEXPORT jstring JNICALL Java_com_example_ndk_NdkString_getFromC  (JNIEnv *, jclass);#ifdef __cplusplus}#endif#endif

这样我们就写完了.c文件,这时我们要在NdkString.java里加上一个static的代码块。

public class NdkString {    static {        System.loadLibrary("Hello");    }    public static native void updateFile(String path);    public static native String getFromC();}

用来调用刚编写的Hello.c。

最后在构建文件中的默认配置中加上:

//ndk编译生成.so文件        ndk {            moduleName "Hello"         //生成的so名字            abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。        }
android {    compileSdkVersion 25    buildToolsVersion "25.0.2"    defaultConfig {        applicationId "com.example.ndk"        minSdkVersion 19        targetSdkVersion 25        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"        ndk {            moduleName "Hello"         //生成的so名字            ldLibs "log"//实现__android_log_print            abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。        }    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }}

接着再用Make Project编译就可以发现在与classes相同的目录下新建了一个ndk的目录,在里面可以找到你设定好的各种体系下的.so文件和自动生成的Android.mk。

但是这里要注意一个地方,如果你要生成.so文件的时候,报出了下面的错误的话:

那么我们还需要在jni目录下再创建一个空的.c文件,名字可以随便,这可能是一个开发的bug吧,默认需要两个.c文件。

到这里,通过jni调C就完成了,现在我们来测试一下,写个Button通过点击显示一下调用的C:

public class MainActivity extends AppCompatActivity {    private Button bt;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        bt = (Button) findViewById(R.id.bt);    }    public void onClick(View view) {        bt.setText(NdkString.getFromC());    }}

这样我们的第一个NDK项目就完成了。

log打印

相信很多人在刚开始学习Android JNI编程的时候,需要输出Log,在百度Google搜索的时候都是说需要在Android.mk中加入LOCAL_LDLIBS+= -L$(SYSROOT)/usr/lib -llog ,其实这是在eclipse开发上的方式,Android Studio并不是这么使用。

Android Studio的Android.mk是自动生成的,就算修改也是没用了,实际Android Studio的Android.mk是根据gradle文件生成的,那么就需要修改gradle文件。

如果不修改gradle,直接使用__android_log_print就会报错。

Error: undefined reference to '__android_log_print'

现在只需要在jni Module中得build.gradle 添加一些代码即可实现输出Log

build.gradle文件完成代码:

android {    compileSdkVersion 25    buildToolsVersion "25.0.2"    defaultConfig {        applicationId "com.example.ndk"        minSdkVersion 19        targetSdkVersion 25        versionCode 1        versionName "1.0"        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"        ndk {            moduleName "Hello"         //生成的so名字            ldLibs "log"//实现__android_log_print            abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。        }    }    buildTypes {        release {            minifyEnabled false            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }    }}

ldLibs “log” 是关键代码

c文件中的修改,如何用c添加log

#include "com_example_ndk_NdkString.h"#include <android/log.h>#define TAG "HTG"#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)JNIEXPORT jstring JNICALL Java_com_ht_asndk_NdkString_getFromC        (JNIEnv *env, jclass jclass) {    LOGV("hello log from C");    return (*env)->NewStringUTF(env, "这是我测试的jni");}

这句代表应用log的头文件,这样就可以使用print的函数了。

#include <android/log.h>

这两句定义宏,将函数和标签申请好,ANDROID_LOG_VERBOSE代表log的等级是verbose。LOGV()中就只用输出log信息了。

#define TAG "HTG"#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
LOGV("hello log from C");

编译运行:

log确实打印成功了。

结束语:本文仅用来学习记录,参考查阅。

3 0