NDK入门(Android Studio)

本文介绍了Java Native Interface (JNI) 的基本概念及其在Java与C/C++之间建立桥梁的作用,并详细讲解了Android NDK的使用流程,包括环境搭建、首个项目创建及日志打印等内容。

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确实打印成功了。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值