1、將ndk添加至環境變量
至添加一次即可,即下面語句使用一次之後以後就不用再使用了
echo 'export PATH=~/Desktop/Android/android-sdk-linux/ndk-bundle/:$PATH' >> ~/.bashrc
- 1
echo ‘export PATH=你的具體ndk路徑/ndk-bundle/:$PATH’ >> ~/.bashrc
讓環境變量生效的方法:
- 重啓
- 若不重啓,要使用之前先用source ~/.bashrc
完成以上操作你就可以隨處使用NDK-build這個命令了
2、創建native類聲明
建議單獨寫一個native的java類,理論上MainActivity上寫也是可以的,但是每次改動,你都要重新生成。
新建一個native包,裏面放native的java類
package com.example.myapplication.natives;
/**
* Created by hui on 17-4-4.
*/
public class Test {
static {
System.loadLibrary("test");
}
public native String get();
public native void set(String str);
}
現在你肯定看到get和set方法紅色吧。因爲還沒生成對應的.h頭文件
1、生成對應的class文件
這步必須做,.class文件,在後面生成.h頭文件要用到
對着那個Test.java,右鍵show in file manager,自動打開文件管理器
javac Test.java
- 1
生成了Test.class文件了
2、 生成com_example_myapplication_natives_Test.h文件
這個頭文件是自動生成的,你可以看出上面聲明包package com.example.myapplication.natives;這句與那個.h文件的命名規則了麼?
在文件管理器中,我們回退到這個java目錄下
看到這個包聲明的打頭目錄,就在這裏打開命令行
javah com.example.myapplication.natives.Test
- 1
格式:javah 包聲明.Test
因爲javah要從包聲明最頂層尋找Test.class,所以要在這個相對路徑使用命令
- Android Studio項目上右鍵New->Folder->JNI Folder
- 將.h頭文件放進去
- gradle.properties添加android.useDeprecatedNdk=true
Android.useDeprecatedNdk=true這個很重要!!!
此時我們寫的Test.java沒有紅色提示了。
左邊還多了兩個紅綠箭頭。恩,Android Studio找到了.h頭文件了。
重點是那個.h的命名方式
3、實現Native方法(函數)
test.cpp和test.c的實現很類似,但是它們對env的操作方式有所不同,因此用C++和C來實現同一個JNI方法,它們的區別主要集中在對env的操作上,其他都是類似的,如下所示。
C++: env->NewStringUTF(“Hello from JNI !”);
C:(*env)->NewStringUTF(env,”Hello from JNI !”);
摘自Android開發藝術探索
這裏我們編寫C語言
#include "com_example_myapplication_natives_Test.h"
#include <stdio.h>
JNIEXPORT jstring JNICALL Java_com_example_myapplication_natives_Test_get
(JNIEnv *env, jobject thiz){
printf("start c get ");
return (*env)->NewStringUTF(env,"hello from jni");
}
JNIEXPORT void JNICALL Java_com_example_myapplication_natives_Test_set
(JNIEnv * env, jobject thiz, jstring string){
printf("start c set");
char * str = (char *)(*env)->GetStringUTFChars(env , string , NULL);
printf("%s\n",str);
(*env)->ReleaseStringUTFChars(env , string ,str);
}
- 要使用printf所以導入stdio.h,要實現.h中的方法,所以導入com_example_myapplication_natives_Test.h
- 直接複製.h文件中的聲明,給聲明添加變量名env , thiz (取諧音thiz,clazz等,因爲重複了關鍵字,所以一般用z代替s), string ,接着給個大括號,寫邏輯吧。
- JNIEnv *: 表示一個指向JNI環境的指針,可以通過它來訪問JNI提供的接口方法
- jobject: 表示java對象中的this
- JNIEXPORT和JNICALL: 它們是JNI中所定義的宏,可以在jni.h這個頭文件中找到
摘自Android開發藝術探索
4、生成對應so
編譯前的準備:
1、編寫Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.c
include $(BUILD_SHARED_LIBRARY)
其中
LOCAL_MODULE表示模塊名稱
LOCAL_SRC_FILES表示需要參與編譯的源文件
除了這兩個,其他照搬即可
2、編寫Application.mk
APP_ABI := armeabi
用作配置要編譯的CPU架構平臺的類型
3、生成so庫文件
1、直接編譯(想想是不可以的,因爲沒有指定平臺,當擴展知識吧)
切換到jni文件夾中,命令行使用命令
小插曲:
hui@hui-PC:~/AndroidStudioProjects/MyApplication/myapplication2/src/main/jni$ gcc -shared -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -fPIC test.c -o libtest.so
In file included from com_example_myapplication_natives_Test.h:2:0,
from test.c:1:
/usr/lib/jvm/java-1.8.0-openjdk-amd64/include/jni.h:45:20: fatal error: jni_md.h: 沒有那個文件或目錄
#include "jni_md.h"
^
compilation terminated.
找不到jni_md.h,文件管理器,一找,jni_md.h在下一級目錄的linux文件夾中,所以正確命令是
gcc -shared -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/ -I /usr/lib/jvm/java-1.8.0-openjdk-amd64/include/linux/ -fPIC test.c -o libtest.so
- 1
- 1
2、NDK-build
在jni的上一級目錄,使用命令
hui@hui-PC:~/AndroidStudioProjects/MyApplication/myapplication2/src/main$ ndk-build
[armeabi] Compile thumb : test <= test.c
[armeabi] SharedLibrary : libtest.so
[armeabi] Install : libtest.so => libs/armeabi/libtest.so
自動生成了,libs目錄
4、使用so
新建一個jniLibs,將libs下的文件夾全部複製到jniLibs
並在build.gradle(記得這是module的,不是project的)下添加如下
android {
...
sourceSets{
main{
//jniLibs.srcDirs = ['src/main/jniLibs']
jni.srcDirs = []
}
}
}
新版的gradle語法變成sourceSets{main{ }}了,不是以前的sourceSets.main{ }了。
其實網上都有,感覺自己寫得複雜了。
重點有三個吧。
- 令native的java類報錯消失:添加android.useDeprecatedNdk=true,用於兼容新版dnk
- 移動so:需要把libs下的全部複製到jniLibs,不能僅僅複製一個so文件,接着libs下面的文件你想刪了也行,
- 讓app識別so路徑:
android {
...
sourceSets{
main{
//jniLibs.srcDirs = ['src/main/jniLibs']
jni.srcDirs = []
}
}
}
不這麼做會提示找不到so,奇怪了,so在jniLibs下面,卻要寫jni.srcDirs
PS:如果出現奇怪的錯誤,那麼clear一下project
最後簡單調用即可,這裏簡單的在onreate裏面調用toast就算了
Toast.makeText(MainActivity.this , "" + new Test().get(), Toast.LENGTH_SHORT).show();