Android 使用NDK-build生成so文件 C++ JNI NDK

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();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章