Android中JNI&NDK入门(一) 之 初识NDK和JNI

1 NDK

NDK全称是Native Develop Kit,翻译作原生开发工具包。它允许你为Android使用C/C++代码来实现应用程序的功能。换言之Android的SDK之外,有一个工具叫NDK,用于进行C/C++的开发。一般情况,是用NDK工具把C/C++编译为.co文件,然后在Java中调用。NDK 可能不适合大多数 Android 编程初学者,这些初学者只需使用 Java 代码和框架 API 来开发应用。然而,如果你需要完成一或多个以下事项,那么 NDK 就能派上用场:

       提高代码的安全性,因为Java层代码比较容易被反编译,而so库反编译比较困难。

       重复使用目前已有的 C/C++ 库。

       通过C/C++实现的动态库可以很方便地在其他平台间移植使用

       进一步提升设备性能,以实现低延迟时间,或运行计算密集型应用,如游戏或物理模拟。

2 JNI

JNI全称是Java Native Interface,即Java本地接口。JNI是Java调用Native 语言的一种特性,它是为了方便Java和C/C++等本地代码相互调用所封装的一层接口。

3 配置NDK环境

第一步,下载NDK

在Android Studio的”Default Preferences”中勾选NDK项进行下载NDK,待下载成功后,便可在”Project Structure”中查看到NDK的目录,操作如下面图:

第二步,配置环境变量

打开“终端”,并输入命令:echo $PATH 查看当前环境变量

 输入:sudo vi ~/.bash_profile,按回车输入密码后用vi打开用户目录下的bash_profile文件。一定要用sudo,否则没权限保存文件 

按i键,开始编辑,并将你电脑里NDK路径填写到后面

编辑完之后,按ESC键,输入:wq,就可以保存退出了,如果不想保存就输入:q就可以了

4 Hello world

准备工作完成后,现在我们就来开始创建一个JNI的Demo,该Demo很简单,就是使Java能正常调用到C++代码而已。创建工程后,正常情况下会看到local.properties文件中有指定了ndk的目录,如果没有则要手动加上:

ndk.dir=/Users/liyizhi/Library/Android/sdk/ndk-bundle
sdk.dir=/Users/liyizhi/Library/Android/sdk

第一步,创建JNIUtils类,在类中将执行加载so库文件和定义一个native方法getInfo,并在MainActivity中调用getInfo方法:

JNIUtils.java

public class JNIUtils {
    static {
        System.loadLibrary("jni-demo");
    }
    public static native String getInfo();
}

MainActivity.java

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String info = JNIUtils.getInfo();
        TextView tv = findViewById(R.id.tv1);
        tv.setText(info);
    }
}

第二步,执行命令,生成.h头文件。如下图,执行了两条命令,首先通过命令:cd app/src/main/java跳转到工程中java文件夹中,然后使用命令:javac -h . com/zyx/jnidemo/JNIUtils.java 生成了对应的.h文件。这里笔者使用的是jdk1.8,如果是jdk较前的版本中,应该要使用命令:javah -jni com.zyx.jnidemo.JNIUtils

上面.h头文件代码需要做一下说明

JINEXPORTJNICALL:                                     是JNI中定义的宏,可以在jni.h这个头文件中查找到

Jstring:                                                                 是代表的是getInfo方法的String返回类型

Java_com_zyx_jnidemo_JNIUtils_getInfo:       是函数名,格式遵循如下规则:Java_包名_类名_方法名

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

jclass:                                                                      表示Java对象中的this

第三步,创建JNI Folder,并将com_zyx_jnidemo_JNIUtils.h头文件移至该文件夹中

第四步,在刚创建的jni文件夹中创建2个文件:JNIUtils.cpp和Android.mk,它们的实现如下所示:

JNIUtils.cpp

#include "com_zyx_jnidemo_JNIUtils.h"
#include <stdio.h>
#include <android/log.h>

JNIEXPORT jstring JNICALL Java_com_zyx_jnidemo_JNIUtils_getInfo(JNIEnv * env, jclass thiz) {
    __android_log_print(ANDROID_LOG_DEBUG, "zyx", "Hello world from JNI !");
    return env->NewStringUTF("Hello world from JNI !");
}

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := jni-demo
LOCAL_SRC_FILES := JNIUtils.cpp

LOCAL_LDLIBS := -llog

include $(BUILD_SHARED_LIBRARY)

上面代码中,LOCAL_MODULE表示模块的名称,LOCAL_SRC_FILES表示需要参与编译的源文件,LOCAL_LDLIBS表示在C++代码中支持log日志输出。

第五步,修改app目录下的build.gradle,如下代码,其中说明请看注释:

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.zyx.jnidemo"
        minSdkVersion 22
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk {
            moduleName "jni-demo"                // so库的名称
            abiFilters "armeabi-v7a", "x86"      // 支持的cpu构架平台类型,all表示编译所有cpu平台
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        externalNativeBuild {
            ndkBuild {
                path 'src/main/jni/Android.mk'    // 指向Android.mk文件
            }
        }
    }
}

第六步,执行编译运行,这时就会在app/build/intermediates/ndkBuild/debug/obj/local目录下生成对应两类so文件,这里两类是对应着gradle中配置的cpu构架平台类型。然后将程序运行到手机上便会看到Java成功调用了C++代码返回了结果。

5 引用外部so库

在实际开发过程中,往往C++工程是跟Android工程分离,或者Android工程中直接引用外部提供现成的so库文件。现在我们就来模拟一下这种情况的发生。

首先在jni文件夹中再添加一下Application.mk文件,代码如下:

Application.mk

APP_ABI := armeabi-v7a,x86

接着切换到jni目录的父目录,然后通过命令:ndk-build来手动编译产生so库,这时候NDK会创建一个和jni目录平级的目录libs,libs下面存放着就是so库。

然后修改Gradle文件:

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.zyx.jnidemo"
        minSdkVersion 22
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//        ndk {
//            moduleName "jni-demo"               // so库的名称
//            abiFilters "armeabi-v7a", "x86"     // 支持的cpu构架平台类型,all表示编译所有cpu平台
//        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
//        externalNativeBuild {
//            ndkBuild {
//                path 'src/main/jni/Android.mk'  // 指向Android.mk文件
//            }
//        }
        sourceSets.main {
            jni.srcDirs = []                      // 禁用自动NDK生成调用
            jniLibs.srcDirs = ['src/main/libs']   // so库存放目录
        }
    }
}

这里要说明一下,因为JNI的代码是必须放在上面创建的jni文件夹里,如果不想采用jni这个名称,可以在Gradle通过jin.srcDirs指定JNI代码的路径。因为我们上例中JNI的代码是默认放在了创建的jni文件夹里,就必须得加上jni.srcDirs=[],否则会编译不通过。

最后就是再次执行编译运行,可以再次看到程序运行到手机上也能展示同样的结果

另外说明一下,如果在实际开发过程中存在旧版本兼容的问题,可以尝试在gradle.properties文件中加上下面的一行代码:

Android.useDeprecatedNdk=true 

点击下载示例

发布了106 篇原创文章 · 获赞 37 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/lyz_zyx/article/details/88669229