NDK開發 - JNI開發流程

JNI是Java和C/C++創建鏈接的橋樑,開發者能夠將關鍵的加密算法經過用C/C++實現以提升被反編譯的難度,保護APK的數據安全。又或者經過C/C++獲取一些手機數據,以此來繞過相似Xposed這類做弊工具,提升數據的準確性。
源碼地址:https://github.com/gnaix92/as-ndkjava

傳送門: NDK開發 - JNI開發流程android

在上一篇搭建的環境下,就能夠進行NDK的開發。NDK開發主要分爲一下幾個步驟:c++

  • java層入口代碼編寫git

  • java代碼編譯github

  • C/C++頭文件生成算法

  • C/C++代碼編寫shell

  • 運行編譯SO安全

java層代碼編寫

  新建一個NativeMethod的Class,java層代碼主要負責兩件事:架構

Load native library(SO文件)

  Load操做很簡單,其中包名爲build.gradle中定義的ndk名,代碼以下:app

android.ndk {
        moduleName = "native"  //設置庫(so)文件名稱
//        CFlags.add("-DCUSTOM_DEFINE")
        ldLibs.addAll(["log", "android", "EGL", "GLESv1_CM"])
//        ldFlags.add("-L/custom/lib/path")
        stl = "stlport_static"
    }
static{
        System.loadLibrary("native");
    }

定義接口名

  JNI的接口名以native字符修飾,而且不須要實現。

package com.example.gnaix.ndk;

/**
 * 名稱: NativeMethod
 * 描述:
 *
 * @author xiangqing.xue
 * @date 16/3/10
 */
public class NativeMethod {


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

    /**
     * 基礎類型
     * @param i
     * @return
     */
    public static native int getInt(int i);

    /**
     * string
     * @param str
     * @return
     */
    public static native String getString(String str);

    /**
     * array
     * @param data
     * @return
     */
    public static native byte[] getByteArray(Byte[] data);

    /**
     * 調用java對象
     * @param name
     * @param age
     */
    public static native void invokeJobject(String name, int age);

    /**
     * 調用java靜態方法
     */
    public static native void invokeStaticFieldAndMethod();

    /**
     * 獲取結構體
     * @return
     */
    public static native Person[] getPersons();
}

  到這裏java層的活就幹完了。

java代碼編譯

  在生成C/C++的頭文件前須要把上面寫的class編譯成.class文件。Android Studio中編譯很簡單點一下右上角的<img width=40px height=35px src="http://gnaix92.github.io/blog... style="display:inline-block"/>按鈕就能夠了。生成的class文件在app/build/intermediates/calsses/all/debug/xx.xx.xx/xx.class

C/C++頭文件生成

  JNI開發對C/C++的頭文件有命名的格式要求,因此咱們可使用jdk提供的javah命令生成規定的C/C++頭文件。爲了方即可以先切換到app/src/main目錄下。

cd app/src/main

  再執行javah命令。(根據實際替換須要生成的class文件)

javah -d jni -classpath ../../build/intermediates/classes/all/debug com.example.gnaix.ndk.NativeMethod

參數說明:

  • classpath:類搜索路徑

  • d:將生成的頭文件放到當前的 jni 目錄下

  • o: 指定生成的頭文件名稱,默認以類全路徑名生成(包名+類名.h)

      執行完會發如今main目錄下生成了一個jni目錄,裏面有個com_example_gnaix_ndk_NativeMethod.h的頭文件。裏面有生成好的頭文件。

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

#ifndef _Included_com_example_gnaix_ndk_NativeMethod
#define _Included_com_example_gnaix_ndk_NativeMethod
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_gnaix_ndk_NativeMethod
 * Method:    getInt
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_com_example_gnaix_ndk_NativeMethod_getInt
  (JNIEnv *, jclass, jint);

/*
 * Class:     com_example_gnaix_ndk_NativeMethod
 * Method:    getString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_gnaix_ndk_NativeMethod_getString
  (JNIEnv *, jclass, jstring);

/*
 * Class:     com_example_gnaix_ndk_NativeMethod
 * Method:    getByteArray
 * Signature: ([Ljava/lang/Byte;)[B
 */
JNIEXPORT jbyteArray JNICALL Java_com_example_gnaix_ndk_NativeMethod_getByteArray
  (JNIEnv *, jclass, jobjectArray);

/*
 * Class:     com_example_gnaix_ndk_NativeMethod
 * Method:    invokeJobject
 * Signature: (Ljava/lang/String;I)V
 */
JNIEXPORT void JNICALL Java_com_example_gnaix_ndk_NativeMethod_invokeJobject
  (JNIEnv *, jclass, jstring, jint);

/*
 * Class:     com_example_gnaix_ndk_NativeMethod
 * Method:    invokeStaticFieldAndMethod
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_example_gnaix_ndk_NativeMethod_invokeStaticFieldAndMethod
  (JNIEnv *, jclass);

/*
 * Class:     com_example_gnaix_ndk_NativeMethod
 * Method:    getPersons
 * Signature: ()[Lcom/example/gnaix/ndk/Person;
 */
JNIEXPORT jobjectArray JNICALL Java_com_example_gnaix_ndk_NativeMethod_getPersons
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

C/C++代碼編寫

  生成頭文件後就能夠寫具體的C++的實現代碼,這裏只寫了一小段實現代碼,具體的調用開發細節下一篇再說。 實現了getInt方法,獲取了android的serialno,並用log打印出來。

//
// Created by 薛祥清 on 16/3/10.
//

#include "com_example_gnaix_ndk_NativeMethod.h"
#include <android/log.h>
#include <sys/system_properties.h>

using namespace std;

#define ENABLE_DEBUG 1
#if ENABLE_DEBUG
#define TAG "NDK_NATIVE"
#define LOGD(fmt, args...)  __android_log_print(ANDROID_LOG_DEBUG,TAG, fmt, ##args)
#define DEBUG_PRINT(format, args...) \
    LOGD(format, ##args)
#else
#define  DEBUG_PRINT(format, args...)
#endif

JNIEXPORT jint JNICALL Java_com_example_gnaix_ndk_NativeMethod_getInt
        (JNIEnv *env, jclass object, jint num)
{
    int len;
    char buf[1024];
    __system_property_get("ro.serialno", buf);
    LOGD("name : %s", buf);
    return num;
}

運行編譯SO

  通過上面的步驟,已經能夠編譯了,在NDK開發中除了本身apk須要,更多時候是做爲第三方包提供給客戶使用。Android Studio也爲咱們打包好了,能夠在module目錄下找到。

PS:

  • 這裏須要注意debug模式編譯會產生gdb.steup gdbserver這兩個是gdb調試工具,在正式發佈必需要用release模式編譯。

  • 舊版本android-ndk-r9d只能編譯armeabi armeabi-v7a mips x86四種cpu架構的,如今大多數手機已是64位架構了,舊版本已經不能支持全部手機了。因此在android-ndk-r10e中增長了arm64-v8a mips64 x86_64三種架構。

  • 目前最新的NDK版本爲android-ndk-r11b

相關文章
相關標籤/搜索