Android 一天搞定 NDK和JNI编程 3分钟学会写NDK实例

Android NDK 是在SDK前面又加上了原生二字,即Native Development Kit,因此又被Google称为NDK。

1、什么是JNI?
      JNI全称 Java Native Interface , java本地化接口  ,它提供了若干的 API 实现了Java和其他语言的通信( 主要是 C & C++ )。 它允许Java代码和其他语言写的代码进行交互,简单的说,一种在Java虚拟机控制下执行代码的标准机制。

NDK包括了
1、从C / C++生成原生代码库所需要的工具和build files。
2、将一致的原生库嵌入可以在Android设备上部署的应用程序包文件(application packages files ,即.apk文   件)中。
3、支持所有未来Android平台的一系列原生系统头文件和库
为何要用到NDK?
概括来说主要分为以下几种情况:
1. 代码的保护,由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大(安全)。
2. 在NDK中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。
3. 便于移植,用C/C++写的库可以方便在其他的嵌入式平台上再次使用。
2、JNI的优缺点
优点:
  可以使用本地的代码,为java与C语言之间建立了一个桥梁
缺点:
1>、程序不再 跨平台 。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。
2>、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个 当中。这样就降低了JAVA和C之间的 耦合性

3、JNI的使用场景
        当你开始着手准备一个使用JNI的项目时,请确认是否还有替代方案。应用程序使用JNI会带来一些副作用。下面给出几个方案,可以避免使用JNI的时候,达到与本地代码进行交互的效果:
1、JAVA程序和本地程序使用 TCP/IP 或者 IPC 进行交互。
2、当用JAVA程序连接本地数据库时,使用 JDBC 提供的API。
3、JAVA程序可以使用分布式对象技术,如JAVA  IDL  API。
这些方案的共同点是,JAVA和C处于不同的线程,或者不同的机器上。这样,当本地程序崩溃时,不会影响到JAVA程序。
下面这些场合中,同一进程内JNI的使用无法避免:
1、程序当中用到了JAVA API不提供的特殊系统环境才会有的特征。而跨进程操作又不现实。
2、你可能想访问一些己有的本地库,但又不想付出跨进程调用时的代价,如效率,内存,数据传递方面。
3、JAVA程序当中的一部分代码对 效率要求非常高 ,如算法计算,图形渲染等。
总之,只有当你必须在同一进程中调用本地代码时,再使用JNI。

这是 eclipse 的的操作流程
4、JNI的调用过程

1)安装和下载Cygwin,下载 AndroidNDK

   2)ndk项目中JNI接口的设计

   3)使用C/C++实现本地方法

  4)JNI生成动态链接库.so文件

  5)将动态链接库复制到Java工程,在java工程中调用,运行java工程即可


    studio下的NDK操作流程:

      NDK开发思路:1、下载NDK

                                2、创建一个native方法,

                                3、通过javah生成头文件,

                                4、改cpp类的代码,

                                5、编辑c语言 ,

                                6、添加application.mk文件,

                                7、运行

                               按照上述的思路开始一步一步执行


studio配置环境:

Android Studio 2.2 重新加入了jni的支持,并且使用的是CMake,所以现在写jni不需要配置mk文件了

eclipse找那个的 所以就不像以前那样还要安装什么cygwin


SDK、JDK、NDK的区别

SDK 软件开发工具包;英语全称:Software Development Kit
JDK Java语言的软件开发工具包;英语全称:Java Development Kit
NDK 原生软件开发工具包;英语全称:Native Development Kit;被Google称为NDK

由此可见,其实不管什么XDK,都可以叫SDK,可能为了有很好的区分,便有了JDKNDK,所以我们有的时候常说的SDK并不是特指安卓开发工具包

而只是我们都是同行,交流的时候都知道指的是什么,其实你们会发现,我们常常接三方平台的时候,那些工具包也是叫SDK,但可能我们在交流的时候就会加个前缀,比如:微信分享SDK、支付宝SDK、xxSDK。

在项目配置中 , 关联NDK之后就会ok

《一》下载NDK
《二》配置NDK


local.properties 里面可以找到SDK和NDK的路径


如果出现如下错误: NDK integration is deprecated in the current plugin. gradle.properties中添加如下配置

使用AndroidStudio开发前我们也要做点额外工作,我们需要在项目根目录下local.properties中添加编译NDK的路径:ndk.dir=/Users/liangqi/android-ndk

不要急,还没有完,ndk环境搭建还有最后一步,在 gradle.properties的文件末尾加上android.useDeprecatedNdk=true这段代码 ,文字看不懂吧直接上图:

<三> java代码调用C的工具类

然后如 下图所示重新Make Project一下工程:生成Class文件



System.loadLibrary(huazict");  里面不用加.so也可以了


//javah -d jni -classpath E:\NdkDemo\app\build\intermediates\classes\debug ndkdemo.peng.cx.com.myapplication.JNIUtils
public class JNIUtils {

    static {
        System.loadLibrary("huazict");
    }


    public native String getString();

}
<四>如何生成.h文件
在studio打开Terminal命令行工具打开步骤是View->Tool Windows->Terminal (或直接按Alt+F12),如下图所示:


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

.h文件生成的目录是更加你的javah指令。

javah -d jni -classpath E:\NdkTest\app\build\intermediates\classes\debug ndkdemo.peng.cx.com.myapplication.JniDemo(注意debug后的空格)

找到类所在的路径:


输入指令之后就自动生成了.h的文件.


把生成的jni目录拷贝到程序的根目录:

<五> 拷贝JNI在正确的目录下   我们在工程中创建一个文件夹jni,此目录与工程中的java目录同级,并把生成的*.h文件放置到jni文件夹中。

<六>写C语言文件

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

C包含里面的头文件.h文件,就是上面生成的.h文件

#include "ndkdemo_peng_cx_com_myapplication_JNIUtils.h"

JNIEXPORT jstring JNICALL Java_ndkdemo_peng_cx_com_myapplication_JNIUtils_getString
  (JNIEnv *env , jobject obj) {
    return (*env)->NewStringUTF(env, "这是我测试的jni");
}

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

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


.so文件的区别

armeabi、armeabi-v7a和x86都表示CPU的类型。一般的手机或平板都是用arm的cpu。

不同的cpu的特性不一样,armeabi就是针对普通的或旧的

arm v5 cpu,armeabi-v7a是针对有浮点运算或高级扩展功能的arm v7 cpu。

mips、armeabi、armeabi-v7a和x86到底是什么

armeabi默认选项,将创建以基于

ARM* v5TE 的设备为目标的库。 具有这种目标的浮点运算使用软件浮点运算。 使用此 ABI (二进制接口)

创建的二进制代码将可以在所有 ARM*设备上运行。所以armeabi通用性很强。但是速度慢

armeabi-v7a创建支持基于 ARM* v7 的设备的库,并将使用硬件 FPU 指令。armeabi-v7a是针对有浮点运算或高级扩展功能的arm v7 cpu。

x86:支持基于硬件的浮点运算的

总结:

不同的CPU不用的芯片,armeabi是最基本的,有不同的运算指令,如浮点运算,所以需要生成不同的.so文件


如果项目只包含了 armeabi,那么在所有android设备都可以运行;如果项目只包含了 armeabi-v7a,除armeabi架构的设备外都可以运行; 

如果项目只包含了 x86,那么armeabi架构和armeabi-v7a的Android设备是无法运行的;

如果同时包含了 armeabi, armeabi-v7a和x86,

所有设备都可以运行,程序在运行的时候去加载不同平台对应的so,这是较为完美的一种解决方案,同时也会导致包变大。


<七>编译生成SO库

Android Studio的菜单build > Rebuild Project   clean 一下,然后rebuild一下,就会生成.so文件,在build/intermediates.ndk下



不是eclipse还要配置android.mk这么文件吗?android  studio不用,

你还真猜对了,不过不是说不用就代表他没有,只不过这个配置过程不过你来做,你要做的就是配置上图的代码。那android.mk



<八>建立一个jniLib目录,把so库拷贝进来


运行程序,可以看到效果

public class MainActivity extends AppCompatActivity {

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


        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(new JNIUtils().getString());


    }
}
 
 

总结一下流程:

编写静态方法(用java声明)-->编译生成class文件--->编译生成h文件---->编写C文件(用C/C++实现)

---->配置NDK---->配置so库---->在Activity调用(Java调用C/C++)。

自己总结的流程:

1.下载NDK

2.配置NDK

3.写JAVA类调用C

4.把JAVA生成Class

5.生成.h文件

6.写C语言

7.配置so库并编译so库

=================================================================================

知识拓展

C语言和.h文件解析

.h文件详细解析:

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

#ifndef _Included_ndkdemo_peng_cx_com_myapplication_JNIUtils
#define _Included_ndkdemo_peng_cx_com_myapplication_JNIUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     ndkdemo_peng_cx_com_myapplication_JNIUtils
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_ndkdemo_peng_cx_com_myapplication_JNIUtils_getString
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

其实 ,我们不用javah命令 , 也能写出头文件 

命名都是有规律 :函数名称规则:Java_完整类名_方法名 , 包名的.号 , 以`_`表示


其中jstring是返回的java的String类型 , jstring类型是jni里面定义的类型 ,  标准C里面是没有的 。那么 , jstring是什么类型呢 ?
使用VS的转到定义功能 , 我们可以看到 ,  jstring在jni.h的定义  , jstring是jobject的别名 , jobject是一个_jobject结构体的指针 

typedef jobject jstring;
typedef struct _jobject *jobject;
 

JNI数据类型对应java的标准数据类型

Java TypeNative TypeDescription
boolean jboolean unsigned 8 bits
byte jbyte signed 8 bits
char jchar unsigned 16 bits
short jshort signed 16 bits
int jint signed 32 bits
long jlong signed 64 bits
float jfloat 32 bits
double jdouble 64 bits
void void not applicable


作者:逝我
链接:http://www.jianshu.com/p/cba836f6a08c
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


java文件如何编译生成Class文件,先到了.java的目录羡慕,然后用命令  javac 类名     javac JNIUtils.java 




javah -jni com.example.binbin.testjni.myJNI

生成头文件;Javah ndktest.peng.cx.com.myapplication.JNIUtils






方式二:

创建NDK项目(方式二)

第一步 , 创建支持C++的项目




在创建项目的时候 , 勾选了C++ support , 项目创建完成之后 , 会自动帮我们生成一个





可能遇到的问题
  1. 为什么我生成的C函数参数是(JNIEnv , jobject)而不是(JNIEnv , jclass); 
    答:这和java代码中对Native函数的声明有关,声明为static,这里的参数就是jclass,即代表该函数所在的类(如HelloJNI.getFromString(),这是jclass接收的是HelloJNI.class)。如果没有声明static,这里的参数就是jobject,表示调用者的实体对象,(如HelloJNI t=new HelloJNI();t.getFromCString(),这里jobject就代表就是t这个对象)。
  2. 报java.lang.UnsatisfiedLinkError错误; 
    答:此类错误一般后面有详细的解释,此处列出常见的错误: 
    • could’t find “xxx.so” 此类错误表示找不到对应的so文件,请查看自己的gradle配置是否有ndk标签,函数实现文件要用标准的后缀.cpp或.c,不然gradle编译ndk的时候不会加入编译的。
    • Native method not found 此类问题表明你在JAVA中声明的函数没有对应的Native实现,如果已经实现了,请确保执行了System.loadLibary(“xxx.so”);一般来说用javah生成的函数定义是不会出现签名不一致的情况的。
    • Can’t load 64-bit .so on 32-bit platform 也就是说不能在32位环境中载入64位的so。一般来说把so移到armeabi-v8a 文件夹下即可。该文件夹存放的是arm64位so文件。

      gradlew clean


参考博客:


好用,写.h文件,相同的效果(自己的博客链接)


NDK调用完美,高级版本,最新采用cpp
studio2.2版本




JNI调用

Jni系列教程

demo地址:不知道为什么上传不了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值