Android Studio建立JNI專案(一)
最近博主在研究JNI,在Android Studio中開發JNI中遇到一些坑,記錄下來,希望給需要的人提供一些解決方法.
JNI(Java Native Interface) Java本地介面.其實就是一種協議,只要實現這種協議,就可以實現Java,C程式碼的互相呼叫
提供了Java與其他的語言的進行互動的能力,增強Java的功能(適用場景):
1.使用C語言的優秀開源框架 ffmpeg(視訊) opengl(影象)等.
2.Java操作硬體的效率問題.
3.安全性,java程式碼反編譯太過簡單,可以直接看到Java程式碼(在C語言加密等,使用者檢驗)等等.
但是:java那麼Android Studio如何建立一個JNI專案呢.
先看下最終的目錄結構:
接下來:
1.建立一個含本地的方法的Java類,不推薦在MainActivity中直接建立,一是javah生成標頭檔案的時候會提示MainActivity的父類無法找到(因為MainActivity的父類的class檔案並不再我們的專案中)二是增加程式碼的耦合性.
package com.example.administrator.yinhangapp; /** * Created by Administrator Youngkaka on 2016/8/18. * 我的心願是:世界和平 */ public class NdkUtils { public native int getResYinHangC(int pass,int word); }
2.建立好類檔案後,重新build這個工程(Build-->Rebuild Project),這裡我們可以看到Rebuild後,在app/build/intermediates/classes/debug/生成NdkUtils的.class檔案,我們進入Terminal模式使用javah生成.h的標頭檔案(這裡要注意的是Jdk1.8
生成.h檔案的時候,需要指定classpath的位置)
目錄結構
使用javah生成標頭檔案,命令執行完畢後,會在debug的目錄下,生成 包名_類名_本地方法名 的標頭檔案,這時我們在/src/main目錄下,新建一個jni目錄下,將生成的.h檔案拷貝該資料夾下.
生成的標頭檔案內容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_administrator_yinhangapp_NdkUtils */
#ifndef _Included_com_example_administrator_yinhangapp_NdkUtils
#define _Included_com_example_administrator_yinhangapp_NdkUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_administrator_yinhangapp_NdkUtils
* Method: getResYinHangC
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_example_administrator_yinhangapp_NdkUtils_getResYinHangC
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
我們今天先不過多討論Jni協議裡面的內容,現在只要清楚,我們生成的標頭檔案中,有一個的本地C方法(引數,返回值同時也包括在內)
3.在jni目錄下,新建一個my.c 實現這個本地c方法.
// Created by Administrator on 2016/8/18.
//
#include "com_example_administrator_yinhangapp_NdkUtils.h"
int login(int num,int pass){
if(num==1234 && pass==1234){
return 1;
}else{
return 0;
}
}
JNIEXPORT jint JNICALL Java_com_example_administrator_yinhangapp_NdkUtils_getResYinHangC
(JNIEnv * env, jobject obj, jint num, jint pass){
jint res=login(num,pass); //呼叫本地的C方法.在C語言驗證使用者名稱和密碼
return res;
}
4.接下來就是坑的開始,因為Android Studio使用Gradle編譯,因此我們要配置Gradle,同時還要手動配置ndk_build的工作路徑,我們先在/src/main/新建一個資料夾jniLibs
我的build.gradle內容如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 24
buildToolsVersion "24.0.1"
defaultConfig {
applicationId "com.example.administrator.yinhangapp"
minSdkVersion 15
targetSdkVersion 24
versionCode 1
versionName "1.0"
ndk {
moduleName "JniLibName" //生成的so名字
abiFilters "armeabi", "x86", "armeabi-v7a" //輸出指定三種abi體系結構下的so庫。目前可有可無。
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets { main {
jni.srcDirs = []
jniLibs.srcDirs=['src/main/jniLibs']
} }
tasks.withType(JavaCompile) { //這裡指定了ndkBuild的工作命令,以及拷貝so的命令
compileTask -> compileTask.dependsOn 'ndkBuild', 'copyJniLibs'
}
}
task ndkBuild(type: Exec) {//設定新的so的生成目錄
def ndkBuildingDir = project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder().absolutePath
commandLine ndkBuildingDir + "/ndk-build.cmd", '-C', 'src/main/jni',
"NDK_OUT=$buildDir/intermediates/ndk/obj",
"NDK_APP_DST_DIR=$buildDir/intermediates/ndk/libs/\$(TARGET_ARCH_ABI)"
}
task copyJniLibs(type: Copy) {//將新生成的so拷貝到jniLibs目錄
from fileTree(dir: file(buildDir.absolutePath + '/intermediates/ndk/libs'), include: '**/*.so')
into file('src/main/jniLibs')
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1'
}
NDK-Build編譯C檔案生成.so檔案需要Android.mk檔案以及Application.mk檔案,我在build.gradle檔案中已經指定我的.mk在/src/main/jni檔案下,因此我們要在jni目錄下,新建
5.Android.mk檔案,Application.mk檔案內容如下:
Andorid.mk內容:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := JniLibName //build.gradle指定的modulename
LOCAL_SRC_FILES := my.c //我的C檔名稱
include $(BUILD_SHARED_LIBRARY)
Application.mk內容:
APP_ABI := all
APP_PLATFORM:= android-19
APP_OPTIM := release
重新Rebuild後會在jniLibs資料夾中生成so檔案,如何沒有的話,從app/build/intermediates/ndk/libs拷貝一個.
6.最後在我們的MainActivity中加入靜態程式碼塊
static {
System.loadLibrary("JniLibName");
}
7 .執行
----------------------------------------------------------------------------------------------------------------------------華麗的分割線
可能會遇到的問題:
答:你的ndk版本不適合
答:刪除你的檢查的你的c原始碼,以及Android.mk檔案,C檔名稱是否錯誤,檢查無誤後,重新編譯
答:你的so檔案並沒有生成,或者沒有存在在/src/main/jniLibs/目錄下,拷貝並且檢查你的build.gradle相關是否正確.
答:手動使用ndk-build時候,需要進入到jni目錄下,並且設定Android.mk檔案,然後使用ndk-build命令,或者使用配置gradle自動編譯
答:jdk1.8的問題,手動設定classpath以及不能再MainActivity的中編寫native方法.
我目前就遇到這些問題,歡迎互相交流.
反向編譯後,只能看到native的宣告呼叫,但是看不到本地方法具體實現.