Android動態註冊jni

最近整理了之前關於jni的代碼,這裏梳理下,供之後參考。java

JNI簡介 

JNI是Java Native Interface的縮寫,它提供了若干的接口實現了Java和其餘語言的通訊(主要是c、c++)。從Java1.1開始,JNI標準成爲java平臺的一部分,它容許Java代碼和其餘語言寫的代碼進行交互。jni是Android中java和c++之間鏈接的橋樑,jni是jvm提供的一種與native方法對接的方式。android

JNI的反作用

一旦使用JNI,JAVA程序就喪失了JAVA平臺的兩個優勢:c++

一、程序再也不跨平臺。要想跨平臺,必須在不一樣的系統環境下從新編譯本地語言部分。算法

二、程序再也不是絕對安全的,本地代碼的不當使用可能致使整個程序崩潰。一個通用規則是,你應該讓本地方法集中在少數幾個類當中。這樣就下降了JAVA和C之間的耦合性。數據庫

JNI使用場景

當你開始着手準備一個使用JNI的項目時,請確認是否還有替代方案。應用程序使用JNI會帶來一些反作用。下面給出幾個方案,能夠避免使用JNI的時候,達到與本地代碼進行交互的效果:安全

一、JAVA程序和本地程序使用TCP/IP或者IPC進行交互。app

二、當用JAVA程序鏈接本地數據庫時,使用JDBC提供的API。jvm

三、JAVA程序可使用分佈式對象技術,如JAVA IDL API。分佈式

這些方案的共同點是,JAVA和C處於不一樣的線程,或者不一樣的機器上。這樣,當本地程序崩潰時,不會影響到JAVA程序。ide

下面這些場合中,同一進程內JNI的使用沒法避免:

一、程序當中用到了JAVA API不提供的特殊系統環境纔會有的特徵。而跨進程操做又不現實。

二、你可能想訪問一些己有的本地庫,但又不想付出跨進程調用時的代價,如效率,內存,數據傳遞方面。

三、JAVA程序當中的一部分代碼對效率要求很是高,如算法計算,圖形渲染等。

總之,只有當你必須在同一進程中調用本地代碼時,再使用JNI。

JNI註冊方法

註冊方法有兩種:靜態註冊和動態註冊

靜態註冊

1,在Java文件中定義native方法。

2,在cmd命令行模式中切換目錄到定義native方法class文件(或者java文件)存放位置。

3,用javah 和javac命令生成包含native方法的.h頭文件。

4,實現native方法,用ndk-build編譯生成.so庫。

靜態註冊方法步驟比較繁瑣,在項目中我比較偏向動態註冊方法。

動態註冊JNI

首先建立一個Android項目,勾選上include c++ support,MainActivity.java中內容:

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("JNITest");
    }

    public String TAG = "MainActivity";
    public JNITest jniTest;
    TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = (TextView) findViewById(R.id.sample_text);
        mTextView = (TextView)findViewById(R.id.textView);
        jniTest = new JNITest();
        int sum = jniTest.addInt(4,3);

        // Example of a call to a native method 
        mTextView.setText(jniTest.getString()+"  "+sum);
        Log.d(TAG,"set text after....");
    }
}

再建立一個java文件定義所須要的native方法,這裏定義了兩個方法

package com.example.szq.testjni;
public class JNITest {
    public JNITest(){
    }
    public native String getString();
    public native int addInt(int a,int b);
}  

在src/main目錄下建立jni文件夾,並新建JNITest.c和Android.mk兩個文件

#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <assert.h>

#define JNIREG_CLASS "com/example/szq/testjni/JNITest" //定義native方法的java文件

//實現
jstring jni_getstr(JNIEnv* jniEnv,jobject ob)
{
    return (*jniEnv)->NewStringUTF(jniEnv,"動態註冊JNI test");
}

jint jni_add(JNIEnv* jniEnv,jobject ob, jint a,jint b)
{
    return a+b;
}

static JNINativeMethod gMethods[] = {
        {"getString", "()Ljava/lang/String;", (void*)jni_getstr},
        {"addInt", "(II)I", (void*)jni_add},
};

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;
}

/*
* 爲全部類註冊本地方法
*/
static int registerNatives(JNIEnv* env) {
    int re = registerNativeMethods(env, JNIREG_CLASS,gMethods,
            sizeof(gMethods)/sizeof(gMethods[0]));
    return re;
}

/*
* System.loadLibrary("lib")時會調用
* 若是成功返回JNI版本, 失敗返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {//註冊
        return -1;
    }
    //成功
    result = JNI_VERSION_1_6;
    return result;
}

配置Android.mk

LOCAL_PATH := $(call my-dir)  
include $(CLEAR_VARS)  
LOCAL_MODULE := JNITest
LOCAL_SRC_FILES := JNITest.c
LOCAL_LDFLAGS += -llog
include $(BUILD_SHARED_LIBRARY)

在命令行模式下切換到jni目錄,運行ndk-build會生成.so庫(前提是ndk環境先配置好),將.so文件copy到src/main/libs中,構建項目,就能運行出下面的結果:

注:生成的.so文件必定要在與jni同一層的libs文件夾中

NDK自動編譯配置

配置build.gradle,由於在構建項目時,編譯器會自動加載gradle文件,因此在gradle中加入編譯的任務(task)就能編譯jni中的c文件了,配置以下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.szq.testjni"
        minSdkVersion 18
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk{
            moduleName "JNITest"
            ldLibs "log", "z", "m"
            abiFilters "armeabi", "armeabi-v7a", "x86"
            //用於指定應用應該使用哪一個標準庫,此處添加c++庫支持
            stl "stlport_static"        //  支持stl
            cFlags "-fexceptions"        // 支持exception
        }
        tasks.withType(JavaCompile) {
            compileTask -> compileTask.dependsOn 'ndkBuild', 'copyJniLibs'
        }
        sourceSets.main{
            jniLibs.srcDirs = ['libs']
        }
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

task ndkBuild(type: Exec) {
//    def ndkDir = project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder()
    def ndkDir = project.android.ndkDirectory
    commandLine "$ndkDir\\ndk-build.cmd", '-C', 'src/main/jni',
            "NDK_OUT=$buildDir/ndk/obj",
            "NDK_APP_DST_DIR=$buildDir/ndk/libs/\$(TARGET_ARCH_ABI)"
}

task copyJniLibs(type: Copy) {
   from fileTree(dir: file(buildDir.absolutePath + '/ndk/libs'), include: '**/*.so')
    into file('src/main/jniLibs')
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

這樣直接構建項目,構建完成就能運行程序了。

在Android studio 3.0版本中添加了更加方便的CMake來編譯jni,配置文件是CMakeLists.txt,CMake會在之後的項目中常常用到,有興趣的能夠一塊兒研究下

相關文章
相關標籤/搜索