Android NDK基础

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/androidzf/article/details/87778244

一、JNI 的概念

1、概念

JNI全称 Java Native Interface,Java 本地接口。可以通过JNI调用系统提供的API。

2、JNI 与 NDK 的区别

  • JNI:JNI是一套编程接口,用来实现Java代码与本地C/C++代码的交互。
  • NDK:NDK是Google开发的一套开发和编译工具集,可以生成动态链接库,主要用于Android的JNI开发。

3、JNI 的作用

  • 扩展: JNI扩展了JVM能力,驱动开发,例如开发一个wifi驱动,可以将手机设置为无限路由。
  • 高效: 本地代码效率高,游戏渲染,音频视频处理等方面使用JNI调用本地代码,C语言可以灵活操作内存。
  • 复用:在文件压缩算法 7zip开源代码库,机器视觉 OpenCV开放算法库等方面可以复用C平台上的代码,不必在开发一套完整的Java体系,避免重复发明轮子。
  • 特殊: 产品的核心技术一般也采用JNI开发,不易破解。

二、JNI 两种注册方式

(一)、静态注册

开发流程如下:

  1. 在 Java 中先声明 native 方法。

  2. 编译 Java 源文件得到 .class 文件。

  3. 使用 Javah -jni 命令生成对应的头文件。

    • 命令 javah -jni packagename.classname 生成由包名加类名命名的 jni 层头文件。
    • 命令 javah -o custom.h packagename.classname,其中 custom.h 为自定义的文件名。
  4. 实现 JNI 里面的函数(生成.c文件),再在Java中通过System.loadLibrary加载 so 库。

PS:javah 是 JDK 自带的一个命令,-jni 参数表示将 class 中用到 native 声明的函数生成 JNI 规则的函数

Demo:

  1. 创建项目

首先创建一个 Android 项目,包名:com.zf.ndkdemo

  1. 创建引用本地库的工具类

创建一个 Java 类 NDKTools

package com.zf.ndkdemo;

public class NDKTools {
    public static native String stringFromJNI();
}

  1. 在MainActivity中调用 NDKTools 的 stringFromJNI()方法
package com.zf.ndkdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import java.io.File;

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(NDKTools.stringFromJNI());
    }
}
  1. 获取classes文件

(1)点击 AndroidStudio 中的 Make Project 或者 Rebuild Project 进行编译获取class文件。(Build --> Make Project 或者 Build --> Rebuild Project)

(2)编译完成之后,按照如下目录寻找class文件。

app/build/intermediates/classes/debug/com/zf/ndkdemo

image

  1. 获取.h文件
    点击AndroidStudio下面Terminal,然后进入app/build/intermediates/classes/debug目录下,然后执行命令:javah -jni com.zf.ndkdemo.NDKTools 。如果一切顺利则会在app/build/intermediates/classes/debug下面生成com_zf_ndkdemo_NDKTools.h文件。如下图:
    image
    内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_zf_ndkdemo_NDKTools */

#ifndef _Included_com_zf_ndkdemo_NDKTools
#define _Included_com_zf_ndkdemo_NDKTools
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_zf_ndkdemo_NDKTools
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_zf_ndkdemo_NDKTools_stringFromJNI
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif
  1. 增加对应的.c文件
    在工程main目录下创建jni文件夹,然后将刚刚生成的.h剪切过来。然后再在jni目录下生成一个.c文件ndkdemo.c
//
// Created by zf on 2019/1/17.
//

#include "com_zf_ndkdemo_NDKTools.h"

JNIEXPORT jstring JNICALL
Java_com_zf_ndkdemo_NDKTools_stringFromJNI
        (JNIEnv *env, jobject obj) {
    return (*env)->NewStringUTF(env, "Hello world,我的第一行NDK代码");
}
  1. 添加并编写Android.mk文件
    在jni目录下生成一个Android.mk文件,内容如下:
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := ndkdemo-jni

LOCAL_SRC_FILES :=ndkdemo.c

include $(BUILD_SHARED_LIBRARY)
  1. 修改相关配置文件
  • local.properties 文件中添加 NDK 路径
ndk.dir=D\:\\Sdk\\ndk-bundle
sdk.dir=D\:\\Sdk
  • 修改 app module 目录下的 build.gradle 文件,内容如下:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.zf.ndkdemo"
        minSdkVersion 18
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk{
            moduleName "ndkdemo-jni"
            abiFilters "armeabi", "armeabi-v7a", "x86"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        externalNativeBuild {
            ndkBuild {
                path 'src/main/jni/Android.mk'
            }
        }
        sourceSets.main {
            jni.srcDirs = []
            jniLibs.srcDirs = ['src/main/jniLibs']
        }

    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
  • gradle.properties 文件中添加: Android.useDeprecatedNdk=true
  1. 修改引用类 NDKTools
public class NDKTools {
    static {
        System.loadLibrary("ndkdemotest-jni");
    }
    public static native String stringFromJNI();
}

over

(二)、动态注册

开发流程如下:

  1. 编写 Java 的 native 方法;
  2. 编写 JNI 函数的实现(函数名随意);
  3. 利用结构体 JNINativeMethod 保存 Java native 方法和 JNI 函数的对应关系;
  4. 利用 RegisterNatives(JNIEnv*, jclass, const JNINativeMethod*,jint) 注册类的所有本地方法;
  5. 第3、4步的代码在 JNI_OnLoad 方法中执行。
  6. 在编写 Java native 方法的类中通过 System.loadLibrary 加载 JNI 动态库,然后会自动调用 JNI_OnLoad 函数,完成动态注册。

三、JNI 数据类型映射

  • 基本数据类型:
Java类型 本地类型(Native Type) 描述
boolean jboolean C/C++无符号8位整型 (unsigned char)
byte jbyte C/C++带符号8位整型 (char)
char jchar C/C+无符号16位整型 (unsigned short)
short jshort C/C++带符号16位整型 (short)
int jint C/C++带符号32位整型 (int)
long jlong C/C++带符号64位整型 (long)
float jfloat C/C++32位浮点型 (float)
double jdouble C/C++64位浮点型 (double)

基本数据类型的映射即在Java的基本数据类型前面添加 j 就是本地类型的基本数据类型

  • 引用数据类型:
Java类型 本地类型(Native Type) 描述
Object jobject 任何java对象
Class jclass Class类对象
String jstring 字符串对象
Object[] jobjectArray 任何对象的数组
boolean[] jbooleanArray 布尔型数组
byte[] jbyteArray 比特型数组
char[] jcahrArray 字符型数组
short[] jshortArray 短整型数组
int[] jintArray 整型数组
long[] jlongArray 长整形数组
float[] jfloatArray 浮点型数组
double[] jdoubleArray 双浮点型数组
void void

注意:

a、应用数据类型不能在Native层使用,需要根据 JNI 函数转化后才能使用;
b、多维数组都是引用类型。

 //获得一维数组的类引用,即jintArray类型  
jclass intArrayClass = env->FindClass("[I");   
//构造一个指向jintArray类一维数组的对象数组,该对象数组初始大小为length,类型为 jsize
jobjectArray obejctIntArray  =  env->NewObjectArray(length ,intArrayClass , NULL); 
  • 方法和变量的id

当Native要调用Java层方法的时候,需要通过JNI函数获取对应的id,根据id调用 JNI 函数获取该方法。变量同理。

id 结构如下:

struct _jfieldID;                       /* opaque structure */
typedef struct _jfieldID* jfieldID;     /* field IDs */

struct _jmethodID;                      /* opaque structure */
typedef struct _jmethodID* jmethodID;   /* method IDs */

时间问题,未完待续…

参考链接:
Android NDK开发:JNI基础篇
Android JNI

猜你喜欢

转载自blog.csdn.net/androidzf/article/details/87778244