手把手教你搭建 NDK 環(huán)境搭建

本文基于 Android Studio 3.4.2 薛窥、gradle:3.2.1

1、什么是 JNI悔耘、NDK暇矫?

JNI 是 Java Native Interface (Java 本地接口)的縮寫,是 Java 與其他語言通信的橋梁颂鸿。在 Android 中的應(yīng)用主要為:音視頻開發(fā)促绵、熱修復(fù)、插件化嘴纺、逆向開發(fā)和系統(tǒng)源碼調(diào)用败晴,為了方便使用 JNI 技術(shù),Android 提供了 NDK 工具集合栽渴,它和 JNI 開發(fā)本質(zhì)上沒有區(qū)別尖坤,NDK 是在 Android 中實(shí)現(xiàn) JNI 的手段,

NDK 有兩個(gè)主要作用:
  • 幫助開發(fā)者快速開發(fā) C/C++ 的動(dòng)態(tài)庫

  • NDK 使用了交叉編譯器闲擦,可以在一個(gè)平臺(tái)上開發(fā)出另一個(gè)平臺(tái)的二進(jìn)制代碼

2慢味、Android 中 NDK 的使用

1)首先下載 NDK 的安裝包

在 SDK Tools 里下載 NDK、LLDB墅冷、CMake

下載 NDK纯路、LLDB、CMake
  • NDK : 即我們需要下載的工具俺榆,會(huì)生成到 SDK 根目錄下的 ndk-bundle 目錄下

  • CMake : 一個(gè)跨平臺(tái)的編譯構(gòu)建工具感昼,可以用簡單的語句來描述所有平臺(tái)的安裝過程

  • LLDB : 一個(gè)高效的 C/C++ 的調(diào)試工具

2)編寫界面

這里的界面很簡單,一個(gè) TextView 和一個(gè) Button 罐脊,點(diǎn)擊 Button 后調(diào)用 JNI 的方法修改 TextView 的值定嗓。

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".JNIDemo.JNIActivity">

    <TextView

        android:id="@+id/jni_tv"

        android:layout_marginTop="20dp"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintEnd_toEndOf="parent"

        android:text="I'm a TextView"

        android:textAllCaps="false"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" />

    <Button

        android:id="@+id/jni_btn"

        app:layout_constraintTop_toBottomOf="@id/jni_tv"

        android:layout_marginTop="20dp"

        app:layout_constraintStart_toStartOf="parent"

        android:text="Call Method From JNI"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>
3)編寫 Activity 代碼
/*

* Copyright (c) 2019\. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.

* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.

* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.

*                                              Vestibulum commodo. Ut rhoncus gravida arcu.

*/

package com.learnandroid.learn_android.JNIDemo;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

import android.widget.Toast;

import com.learnandroid.learn_android.R;

public class JNIActivity extends AppCompatActivity {

    private TextView jni_tv;

    private Button jni_btn;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_jni);

        jni_tv = findViewById(R.id.jni_tv);

        jni_btn = findViewById(R.id.jni_btn);

        jni_btn.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                jni_tv.setText(JNIUtils.sayHelloFromJNI());

            }

        });

    }

}
4)編寫 JNIUtils
package com.learnandroid.learn_android.JNIDemo;

public class JNIUtils {

    static {

        System.loadLibrary("MyJNIHello");

    }

    public static native String sayHelloFromJNI();

}

這里的靜態(tài)代碼塊中首先加載了需要使用的動(dòng)態(tài)庫蜕琴,然后創(chuàng)建 native 方法。

5)生成頭文件

在 Android Studio 的 Terminal 中宵溅,cd 到當(dāng)前項(xiàng)目的根目錄凌简,然后執(zhí)行 javah 命令

(注意將含有本地方法的類寫完整的包名)

# binguner @ binguner in ~/AndroidStudioProjects/learn_android/app/src/main/java [10:12:17]

$ javah -d ../cpp com.learnandroid.learn_android.JNIDemo.JNIUtils

-d ../cpp 指定了頭文件的生成位置:當(dāng)前目錄上一級(jí)下的 cpp 文件夾中。

然后打開 Project 目錄下的 com_learnandroid_learn_android_JNIDemo_JNIUtils.h 可以看到它為我們生成的方法原型

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class com_learnandroid_learn_android_JNIDemo_JNIUtils */

#ifndef _Included_com_learnandroid_learn_android_JNIDemo_JNIUtils

#define _Included_com_learnandroid_learn_android_JNIDemo_JNIUtils

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class:     com_learnandroid_learn_android_JNIDemo_JNIUtils

* Method:    sayHelloFromJNI

* Signature: ()Ljava/lang/String;

*/

JNIEXPORT jstring JNICALL Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI

  (JNIEnv *, jclass);

#ifdef __cplusplus

}

#endif

#endif
6)實(shí)現(xiàn) C++ 代碼

在 cpp 目錄下新建一個(gè) C++ 文件恃逻,命名為 MyJNIDemo

新建 C++ 文件

這時(shí)候如果直接在 C++ 文件里編寫代碼雏搂,是沒有代碼提示的,這時(shí)候需要用 CMake 工具寇损。

首先編輯 app 的 build.gradle 文件凸郑,添加如下內(nèi)容

android -> defaultConfig 下添加

// 使用Cmake工具

externalNativeBuild {

    cmake {

        cppFlags ""

        //生成多個(gè)版本的so文件,指定需要編譯的cpu架構(gòu)

        abiFilters "armeabi-v7a"

    }

}

android -> 下添加

// 配置CMakeLists.txt路徑

externalNativeBuild {

    cmake {

        //編譯后so文件的名字

        **path "src/main/cpp/CMakeLists.txt"**

    }

}

再編輯 CMakeLists.txt 文件(cmake腳本配置文件矛市,cmake會(huì)根據(jù)該腳本文件中的指令去編譯相關(guān)的C/C++源文件芙沥,并將編譯后產(chǎn)物生成共享庫或靜態(tài)塊,然后Gradle將其打包到APK中)

# 設(shè)置 CMake 的最低版本

cmake_minimum_required(VERSION 3.4.1)

# 第一個(gè)參數(shù):創(chuàng)建并命名一個(gè) lib浊吏,會(huì)自動(dòng)生成這個(gè) lib 的 so 庫而昨,

# 第二個(gè)參數(shù):將它設(shè)置為 STATIC (靜態(tài)庫,以 .a 結(jié)尾)或者 SHARED(動(dòng)態(tài)庫以 .so 結(jié)尾)找田,

# 最后一個(gè)參:數(shù)提供一個(gè)相對的源碼路徑

# 可以用 add_library 設(shè)置多個(gè) lib歌憨,CMake 會(huì)自動(dòng)構(gòu)建并把 lib 包打包到 apk 中。

add_library( # Sets the name of the library.

        MyJNIHello

        # Sets the library as a shared library.

        SHARED

        # Provides a relative path to your source file(s).

        MyJNIHello.cpp

        )

# 搜索一個(gè)預(yù)置的 lib墩衙,并把它的路徑保存為一個(gè)變量

# CMake 默認(rèn)在搜索路徑中包含了系統(tǒng)的 lib务嫡,我們只需要給想要添加的 NDK lib 設(shè)置一個(gè)名稱即可。

# CMake 會(huì)在完成構(gòu)建之間檢查它是否存在

find_library( # Sets the name of the path variable.

        log-lib

        # Specifies the name of the NDK library that

        # you want CMake to locate.

        log)

# 將指定的庫關(guān)聯(lián)起來

target_link_libraries( # Specifies the target library.

        MyJNIHello

        # Links the target library to the log library

        # included in the NDK.

        ${log-lib}

        )

這時(shí)候點(diǎn)擊 Sync Now底桂,編寫 C++ 文件有代碼提示了

C++ 的代碼如下

//

// Created by Binguner on 2019-08-13.

//

#include <jni.h>

//#include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h"

extern "C"

JNIEXPORT jstring JNICALL

Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI

        (JNIEnv *env, jclass jclass1) {

    return env->NewStringUTF("JNIHELLO");

}

這里實(shí)現(xiàn)了 Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI 方法植袍,并返回了一個(gè) 「JNIHELLO」的字符串,
我為什么注釋掉 #include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h” 呢籽懦?

因?yàn)?JNI 生成的頭文件目的是幫助我們得到 native 方法的原型于个,自己寫原型容易的話命名可能出錯(cuò),你也可以看到這個(gè)方法的名稱非常復(fù)雜暮顺。但是如果我們能自己寫對這個(gè)名字厅篓,不用 include 這個(gè)頭文件也可以,CMake 會(huì)我們自動(dòng)生成這個(gè)方法捶码。

比如我又在 JNIUtils 中添加了一個(gè) test1() 的方法

新建 native 方法

點(diǎn)擊代碼提示之后羽氮,它會(huì)自動(dòng)在 C++ 代碼中生成相應(yīng)的方法,我們只要實(shí)現(xiàn)其中的方法即可

自動(dòng)創(chuàng)建方法

7)運(yùn)行 App

未點(diǎn)擊按鈕

點(diǎn)擊按鈕后
點(diǎn)擊按鈕

8)JNI 中打印日志

在 C++ 文件中添加如下內(nèi)容:

//

// Created by Binguner on 2019-08-13.

//

#include <android/log.h>

#include <jni.h>

//#include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h"

#define LOG_TAG "System.out.c"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

extern "C"

JNIEXPORT jstring JNICALL

Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI

        (JNIEnv *env, jclass jclass1) {

    LOGD("TAGD,a=%d,b=%d",1,2);

    return env->NewStringUTF("JNIHELLO");

}
運(yùn)行效果
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惫恼,一起剝皮案震驚了整個(gè)濱河市档押,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖令宿,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叼耙,死亡現(xiàn)場離奇詭異,居然都是意外死亡粒没,警方通過查閱死者的電腦和手機(jī)筛婉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來癞松,“玉大人爽撒,你說我怎么就攤上這事∠烊兀” “怎么了硕勿?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長枫甲。 經(jīng)常有香客問我首尼,道長,這世上最難降的妖魔是什么言秸? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮迎捺,結(jié)果婚禮上举畸,老公的妹妹穿的比我還像新娘。我一直安慰自己凳枝,他們只是感情好抄沮,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著岖瑰,像睡著了一般叛买。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹋订,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天率挣,我揣著相機(jī)與錄音,去河邊找鬼露戒。 笑死椒功,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的智什。 我是一名探鬼主播动漾,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼荠锭!你這毒婦竟也來了旱眯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎删豺,沒想到半個(gè)月后共虑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡吼鳞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年看蚜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赔桌。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡供炎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疾党,到底是詐尸還是另有隱情音诫,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布雪位,位于F島的核電站竭钝,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏雹洗。R本人自食惡果不足惜香罐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望时肿。 院中可真熱鬧庇茫,春花似錦、人聲如沸螃成。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寸宏。三九已至宁炫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氮凝,已是汗流浹背羔巢。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留覆醇,地道東北人朵纷。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像永脓,于是被迫代替她去往敵國和親袍辞。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容