Android Studio使用JNI


0x01 前言

  本文講述使用Android Studio通過靜態注冊、動態注冊使用JNI的方法,以及加載第三方so文件的方法

 

0x02 Android Studio靜態注冊的方式使用JNI

  1. 添加native接口

public class MainActivity extends Activity implements OnClickListener {

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

    private native int Add(double num1,double num2);
    private native int Sub(double num1,double num2);
    private native int Mul(double num1,double num2);
    private native int Div(double num1,double num2);


    @Override
    protected void onCreate(Bundle savedInstanceState) {
    }
}

  在Java類中使用System.loadLibrary("JniTest")加載我們要寫的so庫名稱,Add/Sub/Mul/Div這四個方法在Java類中聲明就可以使用了。

 

  2.Build->Make Project
  驗證工程中並無其他錯誤,並對工程進行編譯,生成.class文件
  在Build/intermediates/classes/debug里面

 

  3.javah 生成.h文件

  cmd 進入工程目錄在工程的app/src/main/java ,執行 javah com.example.caculate.MainActivity 命令,就會生成so庫所需要的.h文件

  在/app/src/main/下建立jni目錄,將.h拷貝進去

 

  4.jni目錄下新建一個.c文件,完成so庫的函數實現(注:這里的Android.mk文件並不需要,可以不寫,用法在下面會提到)

  
  


  5.Build->Make Project  會報錯,這里在android studio中添加ndk路徑編譯生成so文件

        ①在local.properties文件中增加ndk的路徑

 

 

  ②在/app/目錄下的build.gradle文件里增加 so的名稱

ndk {
  moduleName "JniTest"
  abiFilters "armeabi", "armeabi-v7a", "x86"
}

 

  6.Build->Make Project 就可以編譯出so文件了
  生成的so在app/Build/intermediates/ndk/debug/lib下面

 

  7.點擊運行,就可以使用我們實現的so中的代碼了

 

 (注:在so庫中實現在原結果基礎上加2)

 

  使用模擬器利用busybox安裝grep命令之后,我們可以通過/proc/<pid>/maps文件中保存的進程加載模塊,查看我們寫的so文件是否被加載了。

 

  8.我們JNI的實現文件

  ①com_example_caculate_MainActivity.h文件

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

#ifndef _Included_com_example_caculate_MainActivity
#define _Included_com_example_caculate_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_caculate_MainActivity
 * Method:    Add
 * Signature: (DD)I
 */
JNIEXPORT jint JNICALL Java_com_example_caculate_MainActivity_Add
  (JNIEnv *, jobject, jdouble, jdouble);

/*
 * Class:     com_example_caculate_MainActivity
 * Method:    Sub
 * Signature: (DD)I
 */
JNIEXPORT jint JNICALL Java_com_example_caculate_MainActivity_Sub
  (JNIEnv *, jobject, jdouble, jdouble);

/*
 * Class:     com_example_caculate_MainActivity
 * Method:    Mul
 * Signature: (DD)I
 */
JNIEXPORT jint JNICALL Java_com_example_caculate_MainActivity_Mul
  (JNIEnv *, jobject, jdouble, jdouble);

/*
 * Class:     com_example_caculate_MainActivity
 * Method:    Div
 * Signature: (DD)I
 */
JNIEXPORT jint JNICALL Java_com_example_caculate_MainActivity_Div
  (JNIEnv *, jobject, jdouble, jdouble);

#ifdef __cplusplus
}
#endif
#endif

  ②so庫中實現函數的main.c文件

#include <jni.h>
#define jintJNICALL
#ifndef _Included_com_example_caculate_MainActivity
#define _Included_com_example_caculate_MainActivity
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jintJNICALL Java_com_example_caculate_MainActivity_Add
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    return (jint)(num1 + num2+2);
}


JNIEXPORT jintJNICALL Java_com_example_caculate_MainActivity_Sub
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    return (jint)(num1 - num2+2);
}


JNIEXPORT jintJNICALL Java_com_example_caculate_MainActivity_Mul
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    return (jint)(num1 * num2+2);
}

JNIEXPORT jintJNICALL Java_com_example_caculate_MainActivity_Div
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    if (num2 == 0) return 0;
    return (jint)(num1 / num2+2);
}
#ifdef __cplusplus
}
#endif
#endif

 

 0x03 Android Studio動態注冊的方式使用JNI

  1.jni目錄下直接編寫so庫中的.c文件

  JNI_Onlad會作為so庫被加載后的第一個執行函數,最后通過RegisterNatives函數將JNI函數注冊

#include <jni.h>
#include <stdio.h>
//#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


JNIEXPORT jint JNICALL native_Add
(JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
return (jint)(num1 + num2 +1);
}


JNIEXPORT jint JNICALL native_Sub
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    return (jint)(num1 - num2 +1);
}


JNIEXPORT jint JNICALL native_Mul
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    return (jint)(num1 * num2 +1);
}

JNIEXPORT jint JNICALL native_Div
        (JNIEnv *env, jobject obj, jdouble num1, jdouble num2)
{
    if (num2 == 0) return 0;
    return (jint)(num1 / num2 +1);
}

//Java和JNI函數的綁定表
static JNINativeMethod gMethods[] = {
        {"Add", "(DD)I", (void *)native_Add},
        {"Sub", "(DD)I", (void *)native_Sub},
        {"Mul", "(DD)I", (void *)native_Mul},
        {"Div", "(DD)I", (void *)native_Div},
};




//注冊native方法到java中
static int registerNativeMethods(JNIEnv* env, const char* className,
                                JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods,numMethods) < 0){
        return JNI_FALSE;
    }

    return JNI_TRUE;
}


int register_ndk_load(JNIEnv *env)
{

    return registerNativeMethods(env, "com/example/caculate/MainActivity",
                                 gMethods,sizeof(gMethods) / sizeof(gMethods[0]));
                                 //NELEM(gMethods));
}



JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }

    register_ndk_load(env);

    // 返回jni的版本
    return JNI_VERSION_1_4;
}

 (注:這里Android.mk文件也不需要,在下面會提到)

 

  2.在Java類中添加native接口,加載JniTest.so,以及聲明native函數,聲明之后,函數可以直接使用。

public class MainActivity extends Activity implements OnClickListener {

    static{ System.loadLibrary("JniTest"); } private native int Add(double num1,double num2); private native int Sub(double num1,double num2); private native int Mul(double num1,double num2); private native int Div(double num1,double num2); @Override protected void onCreate(Bundle savedInstanceState) { } }

  

   3.make Project 生成so庫,如果之前沒有修改/app/build.gradle文件和在local.properties中指定ndk路徑則會報錯。報錯參考靜態使用JNI中的步驟5,指定ndk路徑,並且指定so名稱和輸出的架構。

  

  生成成功,則在/app/build/intermediates/ndk/debug/lib目錄下生成相應的so文件

  

 

  4.在虛擬機中運行

      (注:這里的結果在原結果上加1)



 0x04 Android Studio加載第三方庫

  1.使用ndk生成so文件

  ①建立jni目錄(隨便在哪個地方)

  ②編寫so庫中的.c函數,這里完全使用0x03 Android Studio動態注冊的方式使用JNI中的c文件,使用動態注冊的方式

  ③編寫Android.mk文件(單獨使用ndk編譯so文件的時候需要用到)

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog
LOCAL_PRELINK_MODULE := false
LOCAL_MODULE := JniTest
LOCAL_SRC_FILES := MyJniCalc.c
LOCAL_SHARED_LIBRARIES := libandroid_runtime
include $(BUILD_SHARED_LIBRARY)

  ④使用cmd進入jni目錄,執行ndk-build,生成so文件

  

  

  2.加載生成的so文件,打包進apk中

  ①我們在/app目錄下建立libs目錄,將我們的so文件拷貝進去

  這里可以使用ndk自己生成的so文件,也可以使用其他第三方so文件

  

  

  ②改寫/app/build.gradle文件,編譯的時候將so生成上圖中的libJniTest.jar文件

apply plugin: 'com.android.application'

android {
    compileSdkVersion 16
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "com.example.caculate"
        minSdkVersion 8
        targetSdkVersion 16
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

    sourceSets.main {
        jni.srcDirs = []
        jniLibs.srcDir 'src/main/libs'
    }

    task nativeLibsToJar(type: Zip, description: "create a jar archive of the native libs") {
        destinationDir file("$projectDir/libs")
        baseName "libJniTest"
        extension "jar"
        from fileTree(dir: "libs", include: "**/*.so")
        into "lib"
    }

    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn(nativeLibsToJar)
    }

}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:support-v4:18.0.0'
    compile files('libs/libJniTest.jar')
}

  其中into "lib" 是生成的jar文件存放的目錄,include: "**/*.so" 為所依賴的so文件

 

 

   ③在Java類中添加so的接口

public class MainActivity extends Activity implements OnClickListener {

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

    private native int Add(double num1,double num2);
    private native int Sub(double num1,double num2);
    private native int Mul(double num1,double num2);
    private native int Div(double num1,double num2);


    @Override
    protected void onCreate(Bundle savedInstanceState) {
    }
}

  

  4.Make Project,點擊運行

  這里不需要指定ndk路徑,so庫的名稱也不需要在/app/build.gradle中指定,在Android.mk中指定

(注:這里結果在原結果上加1)

 

0x05 總結

  這里是Android Studio中的使用方法,剛開始也是各種百度,弄得很復雜,在后面的學習中也懂得了很多,所以今天將使用方法重新整理了一遍,希望大家可以不要浪費太多時間。靜態注冊方法主要要用javah生成.h文件,顯得比較復雜,每次添加函數都要重新生成.h文件,不過如果知道函數名稱的格式也可以不生成.h文件,實踐中,在jni目錄下只有main.c文件也是可以運行成功的。動態注冊方法直接在函數中注冊比較容易動態擴展,但是需要對注冊的數據類型有所了解,可以參考靜態方法生成的.h文件中有對應的數據類型。加載第三方庫感覺是最實用的,不管是so文件加密,還是使用不開源的so庫,都需要加載已經生成的so文件。

    注意/app/build.gradle中的sdk版本要改成自己android studio中sdk有的版本,如果沒有也可根據android studio提示下載。

  代碼下載:https://github.com/LycorisGuard/android

    最后編輯於2016.9.17


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM