Android動態注冊jni


最近整理了以前關於jni的代碼,這里梳理下,供以后參考。

JNI簡介 

JNI是Java Native Interface的縮寫,它提供了若干的接口實現了Java和其他語言的通信(主要是c、c++)。從Java1.1開始,JNI標准成為java平台的一部分,它允許Java代碼和其他語言寫的代碼進行交互。jni是Android中java和c++之間連接的橋梁,jni是jvm提供的一種與native方法對接的方式。

JNI的副作用

一旦使用JNI,JAVA程序就喪失了JAVA平台的兩個優點:

1、程序不再跨平台。要想跨平台,必須在不同的系統環境下重新編譯本地語言部分。

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

JNI使用場景

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

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

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

3、JAVA程序可以使用分布式對象技術,如JAVA IDL API。

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

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

1、程序當中用到了JAVA API不提供的特殊系統環境才會有的特征。而跨進程操作又不現實。

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

3、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會在以后的項目中經常用到,有興趣的可以一起研究下


免責聲明!

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



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