JNI和NDK編程入門

本文AndroidStudio版本 3.2.1

JNI開發(fā)流程

1. 編寫java部分

Java中聲明native方法
/home/nan/Work/jniProject/com/nan/JniTest.java

package com.nan;
import java.lang.System;
public class JniTest {
        // 1. 加載動態(tài)庫
    static {
        System.loadLibrary("jni-test");
    }

    public static void main(String[] args) {
        JniTest jniTest = new JniTest();
        System.out.println(jniTest.get());
        jniTest.set("hello world");
    }

    public native String get();
    public native void set(String str);
}

2. 編寫jni部分

1. 編譯java源文件得到class文件,通過javah命令到處jni的頭文件

命令如下

nan@breeze:~/Work/jniProject$ javac com/nan/JniTest.java 
nan@breeze:~/Work/jniProject$ javah com.nan.JniTest

當前目錄下會生成一個com_nan_JniTest.h頭文件,用于編寫對應jni cpp用
/home/nan/Work/jniProject/com_nan_JniTest.h

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

#ifndef _Included_com_nan_JniTest
#define _Included_com_nan_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_nan_JniTest
 * Method:    get
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_nan_JniTest_get
  (JNIEnv *, jobject);

/*
 * Class:     com_nan_JniTest
 * Method:    set
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_nan_JniTest_set
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif

2. 實現(xiàn)jni方法

將com_nan_JniTest.h復制到jni目錄下,接著創(chuàng)建test.cpp文件
/home/nan/Work/jniProject/jni/test.cpp

#include "com_nan_JniTest.h"
#include <stdio.h>

JNIEXPORT jstring JNICALL Java_com_nan_JniTest_get
  (JNIEnv *env, jobject thiz) {
    printf("invoke get() in c++\n");
    return env->NewStringUTF("Hello from JNI!");
}

JNIEXPORT void JNICALL Java_com_nan_JniTest_set
  (JNIEnv *env, jobject thiz, jstring string) {

    printf("invoke set() in c++\n");
    char* str = (char*)env->GetStringUTFChars(string,NULL);
    printf("%s\n",str);
    env->ReleaseStringUTFChars(string, str);
}

3. 編譯so庫

編譯命令如下

nan@breeze:~/Work/jniProject/jni$ gcc -shared -I /usr/lib/jvm/java-7-openjdk-amd64/include -fPIC test.cpp -o libjni-test.so

jni目錄下會生成libjni-test.so庫文件.
注:
Ubuntu16.04,gcc為4.8時報如下錯誤.

gcc: error trying to exec 'cc1plus': execvp: No such file or directory

gcc切換為5.4.0時執(zhí)行成功.

4. 執(zhí)行java程序并調用native方法

nan@breeze:~/Work/jniProject$ java -Djava.library.path=jni com.nan.JniTest
invoke get() in c++
Hello from JNI!
invoke set() in c++
hello world

NDK開發(fā)流程

1. 手動ndk-build

1. 新建Android Project,并使用AndroidStudio下載ndk-bundle

新建Project->F4->SDK Location->Android NDK location->點擊Download->完成后如圖


Screenshot from 2018-12-06 20-00-49.png

2. 聲明所需的native方法

通過MainActivity中的按鈕點擊后從native層獲取字符串設置給TextView,并給native層設置字符串

public class MainActivity extends Activity {
    public static final String TAG = "breeze";

    static {
        System.loadLibrary("jni-test");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tvContent = findViewById(R.id.tv_content);
        Button btnCaller = findViewById(R.id.btn_call);
        btnCaller.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tvContent.setText(get());
                set("I am from JniProject app!");
            }
        });
    }

    public native String get();

    public native void set(String str);
}

3. 編寫native方法

在main目錄下創(chuàng)建jni目錄,創(chuàng)建test.cpp
/home/nan/AndroidStudioProjects/JniProject/app/src/main/jni/test.cpp

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

#ifndef _Included_com_nan_jniproject_MainActivity
#define _Included_com_nan_jniproject_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_nan_jniproject_MainActivity
 * Method:    get
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_nan_jniproject_MainActivity_get
  (JNIEnv * env, jobject thiz) {
    printf("invoke get in c++\n");
    return env->NewStringUTF("I am from Jni in libjni-test.so !");
}

/*
 * Class:     com_nan_jniproject_MainActivity
 * Method:    set
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_nan_jniproject_MainActivity_set
  (JNIEnv * env, jobject thiz, jstring string){
    printf("invoke set in c++\n");
    char* str = (char*)env->GetStringUTFChars(string,NULL);
    printf("%s\n",str);
    env->ReleaseStringUTFChars(string,str);
}

#ifdef __cplusplus
}
#endif
#endif

沒有獨立的頭文件,聲明和實現(xiàn)均在test.cpp中. 但我們編寫時可通過javah com.nan.jniproject.MainActivity先生成頭文件,然后將其內容拷貝至test.cpp中,然后改下方法體即可.

4. 創(chuàng)建Android.mk編譯規(guī)則為cpp文件

/home/nan/AndroidStudioProjects/JniProject/app/src/main/jni/Android.mk

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# 模塊名稱
LOCAL_MODULE := jni-test
# 源代碼文件
LOCAL_SRC_FILES := test.cpp

include $(BUILD_SHARED_LIBRARY)

5. 創(chuàng)建Application.mk來指定so庫生成的目標CPU架構類型

armeabi-v7a最常用,故指定該類型
/home/nan/AndroidStudioProjects/JniProject/app/src/main/jni/Application.mk

APP_ABI := armeabi-v7a

6. 編譯so庫并運行Project

  1. ndk-build命令編譯so庫
nan@breeze:~/AndroidStudioProjects/JniProject/app/src/main$ ndk-build 
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.    
Android NDK: WARNING: APP_PLATFORM android-16 is higher than android:minSdkVersion 1 in ./AndroidManifest.xml. NDK binaries will *not* be compatible with devices older than android-16. See https://android.googlesource.com/platform/ndk/+/master/docs/user/common_problems.md for more information.    
[armeabi-v7a] Compile++ thumb: jni-test <= test.cpp
[armeabi-v7a] StaticLibrary  : libstdc++.a
[armeabi-v7a] SharedLibrary  : libjni-test.so
[armeabi-v7a] Install        : libjni-test.so => libs/armeabi-v7a/libjni-test.so

可以看到libjni-test.so已生成在libs目錄下/home/nan/AndroidStudioProjects/JniProject/app/src/main/libs/armeabi-v7a/libjni-test.so

  1. AndroidStudio默認識別的so庫文件夾main/jniLibs/,故我們在main下創(chuàng)建jniLibs目錄,將剛才libs下生成的直接拷到該位置/home/nan/AndroidStudioProjects/JniProject/app/src/main/jniLibs/armeabi-v7a/libjni-test.so
  2. 除此外還需在Module的build.gradle配置參數(shù)如圖
    Screenshot from 2018-12-07 09-46-11.png

    注意build.gradle中添加了sourceSets部分,關注注釋1和2部分
  3. 運行代碼
    這時代碼可以正常運行,點擊Button后會調用native函數(shù)并在TextView上顯示"I am from Jni in libjni-test.so !"

2. AndroidStudio自動ndk-build

手動寫cpp沒有任何提示功能,且編譯移動文件繁瑣,通過AndroidStudio配置可實現(xiàn)一鍵編譯運行,代碼回到初識創(chuàng)建狀態(tài),一步一步來.

1. 聲明所需native方法(同上)

2. 創(chuàng)建jni Folder

Screenshot from 2018-12-07 10-13-15.png

創(chuàng)建后就會在Project視圖下多出cpp文件夾,這個里面就是用來寫c++的

3. 編寫Android.mk

在cpp文件夾下編寫Android.mk,可以將上面的Android.mk直接考入即可.先將源文件注釋掉,因為還沒有c++文件,待編寫后加入即可

4. 編寫c++文件

首先配置自動編譯且可以編寫c++有提示功能

File->Link C++ Project with Gradle->Build System選項選擇ndk-build方式->Project path選擇Android.mk文件->OK 這時Studio就會自動編譯
這時可以cpp下創(chuàng)建c++文件進行編寫,已經有提示功能了哦,也可以直接將上面的test.cpp直接拷入,然后在Android.mk源文件進行指定. 這時已可以運行代碼了

5. 運行程序

運行后產生的so庫文件和第4步配置后的build.gradle的變化一并展示,如圖


Screenshot from 2018-12-07 10-44-10.png

可以發(fā)現(xiàn)AndroidStudio默認會生成如圖4個cpu架構的so庫. 對于build.gradle只多出了注釋1的部分.

6. AndroidStudio指定so庫CPU架構

build.gradle中配置

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.nan.jniproject"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        // 1. 添加該部分  {
        ndk {
            abiFilters 'armeabi-v7a' // , ‘arm64-v8a’, ‘x86’, ‘x86_64’
        }
        // }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

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

注釋1部分就加入了指定cpu類型,指定armeabi-v7a,如果要支持其他加入即可,armeabi-v7a基本上涵蓋了市面上絕大部分手機.
注意ndk{}必須放在defaultConfig下,否則報如下錯.

Could not find method ndk() for arguments

至此開發(fā)流程我們就清楚了!!!

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子掖桦,更是在濱河造成了極大的恐慌菇用,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橄维,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機护蝶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翩迈,“玉大人持灰,你說我怎么就攤上這事「核牵” “怎么了堤魁?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長返十。 經常有香客問我妥泉,道長,這世上最難降的妖魔是什么洞坑? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任涛漂,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘匈仗。我一直安慰自己瓢剿,他們只是感情好,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布悠轩。 她就那樣靜靜地躺著间狂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪火架。 梳的紋絲不亂的頭發(fā)上鉴象,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天,我揣著相機與錄音何鸡,去河邊找鬼纺弊。 笑死,一個胖子當著我的面吹牛骡男,可吹牛的內容都是我干的淆游。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼隔盛,長吁一口氣:“原來是場噩夢啊……” “哼犹菱!你這毒婦竟也來了?” 一聲冷哼從身側響起吮炕,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤腊脱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后龙亲,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體陕凹,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年鳄炉,在試婚紗的時候發(fā)現(xiàn)自己被綠了杜耙。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡迎膜,死狀恐怖泥技,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情磕仅,我是刑警寧澤珊豹,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站榕订,受9級特大地震影響店茶,放射性物質發(fā)生泄漏。R本人自食惡果不足惜劫恒,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一贩幻、第九天 我趴在偏房一處隱蔽的房頂上張望轿腺。 院中可真熱鬧,春花似錦丛楚、人聲如沸族壳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仿荆。三九已至,卻和暖如春坏平,著一層夾襖步出監(jiān)牢的瞬間拢操,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工舶替, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留令境,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓顾瞪,卻偏偏與公主長得像舔庶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子玲昧,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容

  • 郭敬明的小說《悲傷逆流成河》栖茉,我能說我沒看過嗎篮绿?改成電影孵延,我才知道。悲傷要多么大亲配?還是逆流尘应,就不能順流嗎?成河吼虎,也...
    瘋子的瘋y閱讀 549評論 0 0
  • Handing asynchronous results Make controllers asynchronou...
    zerolinke閱讀 858評論 0 0
  • 有點寫不下去喲犬钢,希望有人鼓勵一下
    自由的沃倫威廉閱讀 170評論 0 0