1. 程式人生 > >我的Android NDK之旅(一),不使用ndk-build命令來建立jni

我的Android NDK之旅(一),不使用ndk-build命令來建立jni

最近閒來無事,想摸索下一下ndk,可是ndk不是塊好啃的骨頭,但作為一名程式設計師,什麼都要了解下,對吧╮( ̄▽ ̄)╭。首先我想吐槽一下,網上有些部落格寫的很亂,一上來就貼一段程式碼,也不告訴是要幹什麼,程式碼一寫完就完事,這讓初學者很難理解jni到底是個什麼東西。每次按照他們的方法敲,敲著敲著就不知道要幹什麼了,然後程式也執行不了。不過有些大神確實總結的很好。所以我想通過這幾天的摸索,總結一下。
對於jni,ndk到底是什麼,我就不多說了,百度講的很清楚。對於如何建立jni,其實很簡單:

  1. 在.java檔案中宣告native方法 。
  2. 編譯這個.java檔案得到.class檔案 。
  3. 用javah命令作用於這個.class檔案生成.h檔案。
  4. 建立一個.c或者.cpp檔案,在程式碼中include剛生成的.h檔案,然後再實現裡面的方法 ,注意方法的命名是有特定的要求的,後面會講到。
  5. 最後就是生成.so檔案了。目前博主知道的三種方法:

    • 通過ndk-build(這個需要Android.mk檔案和Application.mk檔案)生成。
    • cmake(需要CMakeLists.txt)方式生成。
    • 直接通過android studio編譯(這篇文章講的)來生成。

不使用ndk-build命令,直接通過android studio編譯的方式來建立jni

首先在android studio上建立一個專案,

剛建立的專案的結構圖

接下來建立一個類 MyJni,這個類用來實現native方法:

這裡建立了一個類,用來實現native方法

接下來編譯專案:

這裡寫圖片描述

編譯完成之後,就會在app/build/../com/..下生成相應的classes檔案了,接下來就是用 javah 這個指令,將剛才編譯好過了的MyJni.class編譯成 .h 檔案,首先開啟終端,點選android studio下方的Terminal,這裡預設路徑是在當前的專案目錄下。

這裡寫圖片描述

在這裡輸入 cd app/build/intermediates/classes/debug,進入debug目錄,然後在這裡輸入javah指令來生成.h檔案。輸入 javah com.chenxin.MyJni ,之後就會在當前目錄(../debug)下生成一個com.chenxin.MyJni.h檔案。為什麼要在debug目錄下輸入這段指令呢?因為我在其他地方試了,不行啊⊙﹏⊙‖∣° 。javah後面就是要接完整的包名+類名,包名是從com.開始的,所以我們要到com.的父級目錄下,進行javah操作。

這裡寫圖片描述

.h檔案裡面的程式碼如下,其他的不用管,只需要知道JNIEXPORT jstring JNICALL Java_com_chenxin_MyJni_getString
(JNIEnv *, jclass);這個就是我們在MyJni.java裡面宣告的native方法。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_chenxin_MyJni */

#ifndef _Included_com_chenxin_MyJni
#define _Included_com_chenxin_MyJni
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_chenxin_MyJni
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_chenxin_MyJni_getString
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

在app/src/main下建立一個jni資料夾(Directory),因為studio預設的c和c++檔案是在這裡面,將剛才的那個.h檔案剪下到這裡面來,然後在jni資料夾裡建立一個c/c++ source file,隨便叫什麼,我這裡就叫test,字尾名是都.c或者.cpp都可以,只不過函式的實現上稍微有點差異。在這個.c或者.cpp檔案中,首先得匯入jni.h,然後匯入com_chenxin_MyJni.h,就是剛才用javah生成的.h檔案。如果是.cpp的話:

#include "jni.h"
#include "com_chenxin_MyJni.h"

JNIEXPORT jstring JNICALL Java_com_chenxin_MyJni_getString
  (JNIEnv *env, jclass jz){

    return env->NewStringUTF("this is jni , this is my new life!");
  }

如果是.c的話:

#include "jni.h"
#include "com_chenxin_MyJni.h"

JNIEXPORT jstring JNICALL Java_com_chenxin_MyJni_getString
        (JNIEnv *env, jclass jz){

    return (*env)->NewStringUTF(env,"this is jni , this is my new life!");
}

對了,還得在gradle.properties裡面新增一行程式碼,不然會報錯,因為這種方法谷歌現在已經不提倡了。

android.useDeprecatedNdk=true

接下來回到MyJni.java檔案,新增程式碼來載入so檔案,此時還沒有生成,等下編譯專案時會自動生成,自動生成的.so的預設名字是app。

public class MyJni {

    static {
        System.loadLibrary("app");
    }

    public native static String getString();

}

不過如果想自定義生成的so的名字,可以在build.gradle中加入:

        ndk{
            moduleName "myLib"
        }

我這裡將生成的so的名字改成了myLib,所以將上面的 “app” 改成 “myLib” ,例如:

build.
android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
       ......

        ndk{
            moduleName "myLib"
        }

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

MyJni.java檔案中:
    static {
        System.loadLibrary("myLib");
    }

也可以設定指定生成什麼型別的so檔案,這裡我就不詳細講解了。想加的話直接在modulueName下面新增:

abiFilters "armeabi", "armeabi-v7a"

然後回到MainActivity,使用Log列印以下,看下是否呼叫成功了:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.e("info","呼叫jni的結果  :"+TestJni.sayHello());
    }
}

編譯執行
結果:
這裡寫圖片描述

而且這時在開啟app/build/intermediates下就可以看到對應的so檔案了

這裡寫圖片描述

也可以使用 Analyze 來看當前專案的結構:

這裡寫圖片描述

這裡寫圖片描述

這裡寫圖片描述

ok,大功告成。