手把手教你搭建 NDK 環境搭建

本文基於 Android Studio 3.4.2 、gradle:3.2.1

1、什麼是 JNI、NDK?

JNI 是 Java Native Interface (Java 本地接口)的縮寫,是 Java 與其他語言通信的橋樑。在 Android 中的應用主要爲:音視頻開發、熱修復、插件化、逆向開發和系統源碼調用,爲了方便使用 JNI 技術,Android 提供了 NDK 工具集合,它和 JNI 開發本質上沒有區別,NDK 是在 Android 中實現 JNI 的手段,

NDK 有兩個主要作用:
  • 幫助開發者快速開發 C/C++ 的動態庫
  • NDK 使用了交叉編譯器,可以在一個平臺上開發出另一個平臺的二進制代碼

2、Android 中 NDK 的使用

1)首先下載 NDK 的安裝包

在 SDK Tools 裏下載 NDK、LLDB、CMake

下載 NDK、LLDB、CMake

  • NDK : 即我們需要下載的工具,會生成到 SDK 根目錄下的 ndk-bundle 目錄下
  • CMake : 一個跨平臺的編譯構建工具,可以用簡單的語句來描述所有平臺的安裝過程
  • LLDB : 一個高效的 C/C++ 的調試工具
2)編寫界面

這裏的界面很簡單,一個 TextView 和一個 Button ,點擊 Button 後調用 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();

}

這裏的靜態代碼塊中首先加載了需要使用的動態庫,然後創建 native 方法。

5)生成頭文件

在 Android Studio 的 Terminal 中,cd 到當前項目的根目錄,然後執行 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 指定了頭文件的生成位置:當前目錄上一級下的 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)實現 C++ 代碼

在 cpp 目錄下新建一個 C++ 文件,命名爲 MyJNIDemo

新建 C++ 文件

這時候如果直接在 C++ 文件裏編寫代碼,是沒有代碼提示的,這時候需要用 CMake 工具。

首先編輯 app 的 build.gradle 文件,添加如下內容

android -> defaultConfig 下添加

// 使用Cmake工具

externalNativeBuild {

    cmake {

        cppFlags ""

        //生成多個版本的so文件,指定需要編譯的cpu架構

        abiFilters "armeabi-v7a"

    }

}

android -> 下添加

// 配置CMakeLists.txt路徑

externalNativeBuild {

    cmake {

        //編譯後so文件的名字

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

    }

}

再編輯 CMakeLists.txt 文件(cmake腳本配置文件,cmake會根據該腳本文件中的指令去編譯相關的C/C++源文件,並將編譯後產物生成共享庫或靜態塊,然後Gradle將其打包到APK中)

# 設置 CMake 的最低版本

cmake_minimum_required(VERSION 3.4.1)

# 第一個參數:創建並命名一個 lib,會自動生成這個 lib 的 so 庫,

# 第二個參數:將它設置爲 STATIC (靜態庫,以 .a 結尾)或者 SHARED(動態庫以 .so 結尾),

# 最後一個參:數提供一個相對的源碼路徑

# 可以用 add_library 設置多個 lib,CMake 會自動構建並把 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

        )

# 搜索一個預置的 lib,並把它的路徑保存爲一個變量

# CMake 默認在搜索路徑中包含了系統的 lib,我們只需要給想要添加的 NDK lib 設置一個名稱即可。

# CMake 會在完成構建之間檢查它是否存在

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)

# 將指定的庫關聯起來

target_link_libraries( # Specifies the target library.

        MyJNIHello

        # Links the target library to the log library

        # included in the NDK.

        ${log-lib}

        )

這時候點擊 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");

}

這裏實現了 Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI 方法,並返回了一個 「JNIHELLO」的字符串,
我爲什麼註釋掉 #include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h” 呢?

因爲 JNI 生成的頭文件目的是幫助我們得到 native 方法的原型,自己寫原型容易的話命名可能出錯,你也可以看到這個方法的名稱非常複雜。但是如果我們能自己寫對這個名字,不用 include 這個頭文件也可以,CMake 會我們自動生成這個方法。

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

新建 native 方法

點擊代碼提示之後,它會自動在 C++ 代碼中生成相應的方法,我們只要實現其中的方法即可

自動創建方法

7)運行 App

未點擊按鈕

 點擊按鈕後 點擊按鈕

8)JNI 中打印日誌

在 C++ 文件中添加如下內容:

//

// 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");

}

運行效果

歡迎關注本文作者:

掃碼關注並回復「乾貨」,獲取我整理的千G Android、iOS、JavaWeb、大數據、人工智能等學習資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章