標籤:包含 ring ted 程式崩潰 efi share eset 不同的 研究
最近整理了以前關於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 := JNITestLOCAL_SRC_FILES := JNITest.cLOCAL_LDFLAGS += -lloginclude $(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會在以後的項目中經常用到,有興趣的可以一起研究下
Android動態註冊jni