学习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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • Android游戏开发实践(1)之NDK与JNI开发02 承接上篇Android游戏开发实践(1)之NDK与JNI...
    AlphaGL阅读 3,686评论 0 24
  • JNI开发系列阅读 JNI与底层调用1 JNI与底层调用2 C/C++在Android开发中的应用 1. JNI ...
    JackChen1024阅读 673评论 0 3
  • 一、NDK产生的背景 Android平台从诞生起,就已经支持C、C++开发。众所周知,Android的SDK基于J...
    Ten_Minutes阅读 3,399评论 1 27
  • 与人交往,我从来没觉得很困难。但也不是处处话多,招惹人的那种。基本上,我还是好看场合。 有的地方和氛围就特别适合大...
    唐薇阅读 206评论 1 2