欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

一步步教你 ndk /jni 开发,从入门到实践

程序员文章站 2022-06-11 09:09:26
...

JNI 开发帮助文档

一. AS环境配置

1. 下载NDK

2.要配置ndk-build 环境变量(下面给出了,cmd方式, 当然也可以 我的电脑 方式添加)

sudo gedit  /etc/profile

增加如下内容

export ANDROID_NDK_ROOT=/home/rentianxin/android-sdk-linux/ndk-bundle
export PATH=$PATH:$ANDROID_NDK_ROOT

3.修改build.gradle 增加配置 目的: 开发C/CPP时 有代码提示

externalNativeBuild {
        ndkBuild {
            path 'src/main/jni/Android.mk'
        }
    }

二. 在AS中使用ndk-build开发JNI示例

说明: Android Studio2.2之前对于JNI开发的支持不是很好,开发一般使用Eclipse+插件编写本地动态库。后面Google官方全面增强了对JNI的支持,包括内置NDK。

1. 新建项目或者 搞一个lib

2. 创建一个类,声明一个native 方法

package com.zhf.ndk;
public class Jni {
    // Hello
    public static native String getHelloFromJNI();
}

3. 生成头文件 目的是自动生成方法名 复制到CPP 文件里

说明:
实际项目最终可以不包含此头文件,不熟悉C的语法的开发人员,借助于该头文件可以知道JNI的相关语法,
首先引入jni.h,里面包含了很多宏定义及调用本地方法的结构体。重点是方法名的格式。
这里的JNIEXPORT和JNICALL都是jni.h中所定义的宏。
JNIEnv表示一个指向JNI环境的指针,可通过它来访问JNI提供的接口方法。
jclass也是jni.h中定义好的,类型是jobject,实际上是一个不确定类型的指针,这里用来接收Java中的this。
实际编写中一般只要遵循Java_包名_类名_方法名就好了。
头文件只是定义了方法,并没有实现,就像一个接口一样。这里就用C写一个简单的无参的JNI方法。

1. cd 到java文件下

使用Terminal窗口 或者直接在目标文件夹下(E:\application\wiki\lib_ndk\src\main\java),使用cmd命令,这里需要你配置了相关的的环境变量

cd E:\application\wiki\lib_ndk\src\main\java>

2. 生成头文件 目的是自动生成方法名 复制到CPP 文件里
E:\application\wiki\lib_ndk\src\main\java> javah com.zhf.ndk.Jni

此时 会在java 文件夹下面生成一个 com_zhf_ndk_Jni.h 的头文件

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

#ifndef _Included_com_zhf_ndk_Jni
#define _Included_com_zhf_ndk_Jni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_zhf_ndk_Jni
 * Method:    getHelloFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_zhf_ndk_Jni_getHelloFromJNI
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

4.编写mk文件实现JNI方法

说明: 先创建一个jni目录,在src的父目录下创建,也可以在其他目录创建,因为最终只需要编译好的动态库。在jni目录下创建Android.mk Application.mk 和jni.cpp(或者jni.c)文件。

4.1. jni.cpp 中的方法名(Java_com_zhf_ndk_Jni_getHelloFromJNI) 是从 jni.h头文件复制过来的,当然也可以自己写,实际编写中一般只要遵循Java_包名_类名_方法名就好了
#include <jni.h>

extern "C"
jstring Java_com_zhf_ndk_Jni_getHelloFromJNI
        (JNIEnv *env, jclass thiz) {
    return (env)->NewStringUTF("Hello Jni");
}
4.2. jni.c 会发现C代码比C++ 有两点区别 第一不需要 extern “C”, 第二 指针的使用,需要星号,并在方法中添加env参数.
#include <jni.h>
jstring Java_com_zhf_ndk_Jni_getHelloFromJNI
        (JNIEnv *env, jclass thiz) {
    return (*env)->NewStringUTF(env, "Hello Jni");
}
4.3. Android.mk LOCAL_MODULE 该值是指的 生成的SO文件的名称. LOCAL_SRC_FILES 是你创建的 C或者 C++文件名
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := zhf
LOCAL_SRC_FILES := jni.cpp
include $(BUILD_SHARED_LIBRARY)
4.4. Application.mk 改文件可以没有. APP_ABI 是指生成几个版本的(cpu架构) os文件(如:arm64-v8a、armeabi-v7a、x86、x86_64). APP_PLATFORM 目标安卓版本
APP_ABI := all
APP_PLATFORM := android-14

5. 使用ndk-build编程生成.so库

切回到jni目录的父目录下,在Terminal中运行ndk-build指令,就可以在和jni目录同级生成一个libs文件夹,里面存放相对应的平台的.so库。同时生成的还有一个中间临时的obj文件夹,和jni文件夹可以一起删除。 打包生成 OS文件 主意: 这里要配置好 ndk-build 环境变量

E:\application\wiki\lib_ndk\src\main> ndk-build

6. 加载.so库并调用方法

在类初始化的时候要加载该.so库,一般会写在静态代码块里。名称就是前面的LOCAL_MODULE。 细心的读者会发现
不仅方法名与前面新建的相同,而且包名和类名也要相同 这是必须的 否则会报错

package com.zhf.ndk;
public class Jni {
    static {
        System.loadLibrary("zhf");
    }
     // Hello
    public static native String getHelloFromJNI();

}

7. 使用

String strFromJNI = Jni.getHelloFromJNI();

三 实践 (案例: 通过ndk 方式校验应用的报名和签名)

1. 再原先基础上添加三个方法

package com.zhf.ndk;
public class Jni {
    // Hello
    public static native String getHelloFromJNI();
    // 返回私密数据
    public static native String getStrFromJNI(Object context);
    //验证包名
    public static native int verificationPackage(Object context);
    //验证签名
    public static native int verificationSign(Object context);
}

2. 生成头文件

E:\application\wiki\lib_ndk\src\main\java> javah com.zhf.ndk.Jni

3. 复制头文件的方法名 ,复制到C或者C++ 文件里

extern "C"
jstring Java_com_zhf_ndk_Jni_getHelloFromJNI
        (JNIEnv *env, jclass thiz) {
    return (env)->NewStringUTF("Hello Jni");
}
extern "C"
JNIEXPORT jint JNICALL Java_com_zhf_ndk_Jni_verificationPackage
        (JNIEnv *env, jclass thiz, jobject context_object) {
        return 1;
}
extern "C"
JNIEXPORT jint JNICALL Java_com_zhf_ndk_Jni_verificationSign
        (JNIEnv *env, jclass thiz, jobject context_object) {
        return 1;
}
extern "C"
jstring Java_com_zhf_ndk_Jni_getStrFromJNI
        (JNIEnv *env, jclass thiz, jobject context_object) {
        return (env)->NewStringUTF(ERROR);
}

4. 直接上完整代码 (CPP版)

#include <jni.h>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <unistd.h>

extern "C"
jstring Java_com_zhf_ndk_Jni_getHelloFromJNI
        (JNIEnv *env, jclass thiz) {
    return (env)->NewStringUTF("Hello Jni");
}
    /**
 * 发布的app 签名,只有和本签名一致的app 才会返回 AUTH_KEY
 * 这个RELEASE_SIGN的值是上一步用java代码获取的值
 */
 
// 报名
const char *RELEASE_SIGN = "30****a04";
// 签名  获取繁殖
const char *RELEASE_PACKAGE = "com.zhf.ndk";

extern "C"
JNIEXPORT jint JNICALL Java_com_zhf_ndk_Jni_verificationPackage
        (JNIEnv *env, jclass thiz, jobject context_object) {

    jclass context_class = env->GetObjectClass(context_object);

    //context.getPackageManager()
    jmethodID methodId = env->GetMethodID(context_class, "getPackageManager",
                                          "()Landroid/content/pm/PackageManager;");
    jobject package_manager_object = env->CallObjectMethod(context_object, methodId);
    if (package_manager_object == NULL) {
        return 1;
    }

    //context.getPackageName()
    methodId = env->GetMethodID(context_class, "getPackageName", "()Ljava/lang/String;");
    jstring package_name_string = (jstring) env->CallObjectMethod(context_object, methodId);
    if (package_name_string == NULL) {
        return 1;
    }

    const char *c_package = (char *) env->GetStringUTFChars(package_name_string, 0);
    if (strcmp(c_package, RELEASE_PACKAGE) == 0) {
        // 返回true
        return 0;
    } else {
        // 返回false
        return 1;
    }
}

extern "C"
JNIEXPORT jint JNICALL Java_com_zhf_ndk_Jni_verificationSign
        (JNIEnv *env, jclass thiz, jobject context_object) {

    jclass context_class = env->GetObjectClass(context_object);

    //context.getPackageManager()
    jmethodID methodId = env->GetMethodID(context_class, "getPackageManager",
                                          "()Landroid/content/pm/PackageManager;");
    jobject package_manager_object = env->CallObjectMethod(context_object, methodId);
    if (package_manager_object == NULL) {
        return 1;
    }

    //context.getPackageName()
    methodId = env->GetMethodID(context_class, "getPackageName", "()Ljava/lang/String;");
    jstring package_name_string = (jstring) env->CallObjectMethod(context_object, methodId);
    if (package_name_string == NULL) {
        return 1;
    }

    env->DeleteLocalRef(context_class);

    //PackageManager.getPackageInfo(Sting, int)
    jclass pack_manager_class = env->GetObjectClass(package_manager_object);
    methodId = env->GetMethodID(pack_manager_class, "getPackageInfo",
                                "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    env->DeleteLocalRef(pack_manager_class);
    jobject package_info_object = env->CallObjectMethod(package_manager_object, methodId,
                                                        package_name_string, 64);
    if (package_info_object == NULL) {
        return 1;
    }

    env->DeleteLocalRef(package_manager_object);

    //PackageInfo.signatures[0]
    jclass package_info_class = env->GetObjectClass(package_info_object);
    jfieldID fieldId = env->GetFieldID(package_info_class, "signatures",
                                       "[Landroid/content/pm/Signature;");
    env->DeleteLocalRef(package_info_class);
    jobjectArray signature_object_array = (jobjectArray) env->GetObjectField(package_info_object,
                                                                             fieldId);
    if (signature_object_array == NULL) {
        return 1;
    }
    jobject signature_object = env->GetObjectArrayElement(signature_object_array, 0);

    env->DeleteLocalRef(package_info_object);

    //Signature.toCharsString()
    jclass signature_class = env->GetObjectClass(signature_object);
    methodId = env->GetMethodID(signature_class, "toCharsString", "()Ljava/lang/String;");
    env->DeleteLocalRef(signature_class);
    jstring signature_string = (jstring) env->CallObjectMethod(signature_object, methodId);
    const char *c_sign = (char *) env->GetStringUTFChars(signature_string, 0);
    //签名一致  返回合法的 api key,否则返回错误
    if (strcmp(c_sign, RELEASE_SIGN) == 0) {
        // 返回true
        return 0;
    } else {
        // false
        return 1;
    }
}

const char *ERROR = "error";
const char *ABC = "我是一个秘密";

extern "C"
jstring Java_com_zhf_ndk_Jni_getStrFromJNI
        (JNIEnv *env, jclass thiz, jobject context_object) {
    if (Java_com_zhf_ndk_Jni_verificationPackage(env, thiz, context_object) == 0 &&
        Java_com_zhf_ndk_Jni_verificationSign(env, thiz, context_object) == 0) {
        // 返回true
        return (env)->NewStringUTF(ABC);
    } else {
        // 返回false
        return (env)->NewStringUTF(ERROR);
    }
}

5. 打包生成 OS文件

E:\application\wiki\lib_ndk\src\main> ndk-build

6. 使用

参考文章:
https://www.jianshu.com/p/95ee410b1229