學習JNI編程第1篇--寫一個FactorialDemo APP

作者:汶水一方
2017.08.09

本文軟硬件環境
MacBook Pro, OS X El Capitan, 10.11.6
Android Studio v2.3

2017.11.1更新:
本文的ndk方法已經被更好用的cmake方法取代。cmake方法會另外撰文介紹。

0. 計劃

0-1.png

00. 準備工作

如果你還沒有安裝NDK:

  1. 下載,然后解壓。無需安裝。
https://developer.android.com/ndk/downloads/index.html#stable-downloads

解壓得到android-ndk-r15c目錄,記住路徑。主要需要它下面的ndk目錄及文件。

  1. 設置PATH
    編輯~/.bash_profile文件,加入這樣一行(要用到上面的解壓路徑):
PATH=$PATH:/Downloads/android-ndk-r15c/ndk

然后,執行source ~/.bash_profile,使之生效。

  1. 執行:
ln -s /Downloads/android-ndk-r15c/ndk ndk

這樣就設置好了。

1. 新建一個Android Studio項目

命名為FactorialDemo,接下來的選項全部默認即可。


1-1.png
1-2.png
1-3.png
1-4.png

把視圖切換到Project,下圖中標記出來的是要修改的幾個文件,當然我們還要創建幾個文件:


1-5.png

2. 新建Factorial.java類

2-1.png

內容如下:

package ai.nixie.aiden.factorialdemo;
public class Factorial {
    public static long fac(long n){
        return n <=0? 1 : n * fac(n-1);
    }
    public native static long facNTV(long n);
}

3. 從java類文件生成頭文件(ai_nixie_aiden_factorialdemo_Factorial.h)

點擊窗口下方的Terminal,打開命令行窗口,切換到app/src/main/目錄,然后執行命令生成頭文件。

cd app/src/main/
javah -jni -classpath java/ -d jni/ ai.nixie.aiden.factorialdemo.Factorial

注意:ai.nixie.aiden.factorialdemo.Factorial
前面ai.nixie.aiden.factorialdemo是package包名,全部小寫。最后的Factorial是上面創建的Factorial的類名,注意區分大小寫哦!否則會報錯。

執行結果如下圖。

3-1.png

如果沒有看到任何提示,說明運行成功啦。
這時再看左側的項目樹,發現多一個jni文件夾,展開里面就是我們剛生成的頭文件啦。

3-2.png

4. 生成c文件(ai_nixie_aiden_factorialdemo_Factorial.c)

現在選中ai_nixie_aiden_factorialdemo_Factorial.h文件,按Command + C復制,接著按Command + V粘貼,彈出如下對話框。

4-1.png

把文件名最后的h改為c。點OK
現在的jni文件夾就有2個文件了。

4-2.png

接下來修改C文件(ai_nixie_aiden_factorialdemo_Factorial.c)的內容為:

#include <ai_nixie_aiden_factorialdemo_Factorial.h>

static jlong fac(long n) {
    return n<=0 ? 1 : n * fac(n - 1);
}

JNIEXPORT jlong JNICALL Java_ai_nixie_aiden_factorialdemo_Factorial_facNTV
  (JNIEnv *env, jclass clazz, jlong n){

    return fac(n);

  };

注意!!!fac函數必須放在前面!順序不能反!否則會提示找不到!

5. 編寫Android.mk文件

jni文件夾下新建一個文件,名字為Android.mk,內容如下:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES :=ai_nixie_aiden_factorialdemo_Factorial.c
LOCAL_MODULE :=Factorial
include $(BUILD_SHARED_LIBRARY)

6. 編寫Application.mk文件

jni文件夾下新建一個文件,命名為Application.mk,內容如下:

APP_PLATFORM := android-14
APP_ABI :=arm64-v8a armeabi-v7a

如果不加的話,會提示Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-14.

點擊右上角出現的Sync Now

6-1.png

7. 編譯so庫文件

此時,在Terminal窗口中執行ndk-build,就可以得到編譯的so文件。

$ ndk-build
Android NDK: WARNING: APP_PLATFORM android-14 is higher than android:minSdkVersion 1 in ./AndroidManifest.xml. NDK binaries will *not* be comptible with devices older than android-14. See https://android.googlesource.com/platform/ndk/+/master/docs/user/common_problems.md for more information.    
[arm64-v8a] Compile        : Factorial <= ai_nixie_aiden_factorialdemo_Factorial.c
[arm64-v8a] SharedLibrary  : libFactorial.so
[arm64-v8a] Install        : libFactorial.so => libs/arm64-v8a/libFactorial.so
[x86_64] Compile        : Factorial <= ai_nixie_aiden_factorialdemo_Factorial.c
[x86_64] SharedLibrary  : libFactorial.so
[x86_64] Install        : libFactorial.so => libs/x86_64/libFactorial.so
[mips64] Compile        : Factorial <= ai_nixie_aiden_factorialdemo_Factorial.c
[mips64] SharedLibrary  : libFactorial.so
[mips64] Install        : libFactorial.so => libs/mips64/libFactorial.so
[armeabi-v7a] Compile thumb  : Factorial <= ai_nixie_aiden_factorialdemo_Factorial.c
[armeabi-v7a] SharedLibrary  : libFactorial.so
[armeabi-v7a] Install        : libFactorial.so => libs/armeabi-v7a/libFactorial.so
[armeabi] Compile thumb  : Factorial <= ai_nixie_aiden_factorialdemo_Factorial.c
[armeabi] SharedLibrary  : libFactorial.so
[armeabi] Install        : libFactorial.so => libs/armeabi/libFactorial.so
[x86] Compile        : Factorial <= ai_nixie_aiden_factorialdemo_Factorial.c
[x86] SharedLibrary  : libFactorial.so
[x86] Install        : libFactorial.so => libs/x86/libFactorial.so
[mips] Compile        : Factorial <= ai_nixie_aiden_factorialdemo_Factorial.c
[mips] SharedLibrary  : libFactorial.so
[mips] Install        : libFactorial.so => libs/mips/libFactorial.so

8. 鏈接C++代碼和Gradle

如果現在就編譯整個項目,會得到下面的錯誤。

8-1.png
8-2.png

jni文件夾下的任意文件上右擊,選擇Link C++ Project with Gradle

8-3.png

選擇ndk-build,并找到并選擇它的Android.mk文件,然后OK。

8-4.png

執行完這一步后,在build.gradle文件中android下面多了幾行:

externalNativeBuild {
    ndkBuild {
        path 'src/main/jni/Android.mk'
    }
}

其實直接在這個文件中加入這幾行應該就可以了。

9. 導入庫文件

在Factorial.java文件中,加入導入庫文件的代碼,

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

完成后如下:

package ai.nixie.aiden.factorialdemo;
public class Factorial {
    public static long fac(long n){
        return n <=0? 1 : n * fac(n-1);
    }
    public native static long facNTV(long n);
    static {
        System.loadLibrary("Factorial");
    }
}

好了,現在先測試一下,應該可以正常編譯了。不過我們現在還沒有在我們的APP中用上so庫的功能。

9-1.png
9-2.png

10. 完善APP,驗證我們的so庫

10.1 修改布局文件

修改res/layout/activity_main.xml文件,改為LinearLayout布局,加入3個控件,一個輸入框用于輸入數字,一個文本框用于顯示結果,一個按鈕。

完成后如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >

    <EditText
        android:id="@+id/input"
        android:text="5"
        android:textSize="32dp"
        android:textAlignment="center"
        android:selectAllOnFocus="true"
        android:inputType="text"
        android:maxLines="1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <requestFocus />
    </EditText>

    <TextView
        android:id="@+id/result"
        android:textSize="32dp"
        android:textAlignment="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="result"
        />

    <Button
        android:id="@+id/calculate"
        android:text="Calculate"
        android:textSize="32dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

10.2 修改主java文件MainActivity.java

主要的幾個修改點:

  • 加入implements監聽Click事件。
  • 獲得3個控件,并為按鈕加入Click事件監聽。
  • 在onClick函數中,實現點擊時計算階乘的功能。其中用到了:
    • 判斷字符串是否為空
    • String轉換成Long
  • 創建了Factorial的一個實例,并調用它的方法來實現階乘的功能。
  • 將階乘的計算結果,進行字符串格式化后,顯示在文本框中。

修改完成的MainActivity.java文件為:

package ai.nixie.aiden.factorialdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private EditText inputBox;
    private TextView tvResult;
    private Button calButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        inputBox = (EditText) findViewById(R.id.input);
        tvResult = (TextView) findViewById(R.id.result);
        calButton = (Button) findViewById(R.id.calculate);

        calButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {

        String in = inputBox.getText().toString();

        if (TextUtils.isEmpty(in)) {
            return;
        }
        long input = Long.parseLong(in);

        /*  These two lines are the most important! */
        Factorial myFactorial = new Factorial();
        long result = myFactorial.facNTV(input);
        /*  These two lines are the most important! */

        tvResult.setText(String.format("fac(%d)=%d", input, result));

    }
}

其中最主要的是這2句:

Factorial myFactorial = new Factorial(); //生成一個Factorial類的實例,
long result = myFactorial.facNTV(input); //然后調用它的facNTV或fac方法,來計算階乘。

11. 編譯測試

到此項目完成。實際的運行結果圖:


11-1.png

完成后的項目樹:


11-2.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,560評論 25 708
  • Android游戲開發實踐(1)之NDK與JNI開發02 承接上篇Android游戲開發實踐(1)之NDK與JNI...
    AlphaGL閱讀 3,789評論 0 24
  • JNI開發系列閱讀 JNI與底層調用1 JNI與底層調用2 C/C++在Android開發中的應用 1. JNI ...
    JackChen1024閱讀 737評論 0 3
  • 一、NDK產生的背景 Android平臺從誕生起,就已經支持C、C++開發。眾所周知,Android的SDK基于J...
    Ten_Minutes閱讀 3,555評論 1 27
  • 與人交往,我從來沒覺得很困難。但也不是處處話多,招惹人的那種。基本上,我還是好看場合。 有的地方和氛圍就特別適合大...
    唐薇閱讀 241評論 1 2