NDK 開發(fā)之 Android Studio 中 JNI 環(huán)境搭建

之前弄過一點(diǎn) jni 相關(guān)的東西,使用過程中總是折騰很久,之后用到 jni 工程配置時(shí),又忘記之前的操作了。
哎,記憶力不好,這也是作為一位偽碼農(nóng)的硬傷啊!所以為了以后重復(fù)使用,只能寫寫了,以便日后再用!好了,就開始記錄吧!
由于 Jni 相關(guān)知識(shí)操作比較多,每部分寫一塊的內(nèi)容,不至于文章過長(zhǎng)!

概要:

  • NDK 開發(fā)簡(jiǎn)介
  • Jni 簡(jiǎn)介
  • NDK 開發(fā)環(huán)境搭建

1.NDK 簡(jiǎn)介

1.1 什么是 NDK 開發(fā)?

NDK(Native Development Kit)是 Android 所提供的一個(gè)工具集合,通過 NDK 可以在 Android 中
更加方便地通過JNI來調(diào)用本地代碼(C/C++)。NDK 提供了交叉編譯器,開發(fā)時(shí)只需要修改 .mk 文件就
能生成特定 CPU 平臺(tái)的動(dòng)態(tài)庫,并能自動(dòng)將so和java應(yīng)用一起打包成apk。

這里寫圖片描述

簡(jiǎn)單點(diǎn)說,就是 NDK 幫助你編譯 C/C++ 代碼,通過也提供一些 API 供你調(diào)用,使用時(shí)需要你指定 NDK 的路徑。

1.2 為什么使用 NDK 呢?

先上一張腦圖看看,羅列了一些要點(diǎn)。

這里寫圖片描述

NDK開發(fā)相比于 JAVA 開發(fā)有一定難度,但有時(shí)又不得不使用 NDK 開發(fā),主要也就是圖中列出的幾點(diǎn):

(1)控制硬件,便于移植:因?yàn)橐{(diào)用底層的一些功能,如列出的控制 I2C,驅(qū)動(dòng)開發(fā),藍(lán)牙、Wifi,做硬件移植,使得程序跑在不同的硬件上;

(2)安全性:java是半解釋型語言,很容易被反匯編后拿到源代碼文件,我們可以在重要的交互功能使用C語言代替

(3)高效性:C/C++開發(fā)比較高效,像做數(shù)學(xué)運(yùn)算、實(shí)時(shí)渲染游戲、音視頻處理、文件壓縮、人臉識(shí)別等

  • 優(yōu)點(diǎn)

也就是上面面列出來的為什么使用 NDK 開發(fā)的幾點(diǎn)

  • 缺點(diǎn)

(1)C/C++:NDK 開發(fā)底層是 C/C++ 寫的,所以就需要會(huì)這兩種語言,毫無疑問,這兩種語言是公認(rèn)的比較難的語言,學(xué)習(xí)成本高

(2)內(nèi)存泄露:雖然高效,但是內(nèi)存需要程序員進(jìn)行管理,容易發(fā)生內(nèi)存泄露等錯(cuò)誤

(3)調(diào)試?yán)щy:相比于上層 Java 調(diào)試起來還有有一定難度,要求熟練使用call chain

2.Jni 簡(jiǎn)介

2.1 什么是Jni?

JNI:全稱:Java Native Interface,是一層接口,用于 Java 和 C/C++ 溝通的橋梁。通過 Jni 可以實(shí)現(xiàn) Java 調(diào)用 C/C++ 庫中的方法,也可以實(shí)現(xiàn) C/C++ 調(diào)用 Java 中的方法。
Java 通過 JVM 實(shí)現(xiàn)在不同的系統(tǒng)上運(yùn)行,具有跨平臺(tái)的能力;若要調(diào)用一些和操作系統(tǒng)的操作(一般通過 C/C++ 實(shí)現(xiàn)),就需要通過 Jni 來實(shí)現(xiàn)。

Jni 既然是一個(gè)接口,那么也會(huì)有它自己的一定規(guī)則,像 Jni 的數(shù)據(jù)類型,后面再詳細(xì)介紹數(shù)據(jù)類型,方法的操作,這里只需要先有一個(gè)概念,Jni 在程序中的作用以及與 NDK 之間的關(guān)系。

這里寫圖片描述

3.NDK 開發(fā)環(huán)境搭建

3.1 安裝與部署

  • 指定 NDK

之前如果你沒有配置過 NDK 的話,那么需要指定 NDK 的路徑,打開 File--->Project Structure--->SDK Location.

NDK 的路徑需要指定,分為兩種情況:

(1)已下載過NDK:通過找到本地的 NDK 的位置并指定

(2)如果本地沒有 NDK,那么就需要下載 NDK

a.打開 Tool--->Android--->SDK Manager

b.找到System Settings--->Android SDK----SDK Tool,選擇要下載的選項(xiàng),進(jìn)行下載,下載和解壓,并安裝的時(shí)間會(huì)有點(diǎn)長(zhǎng),請(qǐng)耐心等待!

這里寫圖片描述

下載好 NDK 后,就可以指定了,也可以通過 local.properties 文件指定 NDK 位置

這里寫圖片描述

3.2 開發(fā)步驟

NDK 的指定后,就可以進(jìn)行 NDK 開發(fā),下面列出 NDK 開發(fā)的主要步驟和其中的一些要點(diǎn)。

這里寫圖片描述

那下面我們就按步驟,結(jié)合一個(gè)例子來嘗試一下

  • 創(chuàng)建工程 JniTest ,并加入 native 方法

MainActivity中代碼


package com.ralf.www.jnitest;

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

public class MainActivity extends AppCompatActivity {

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

        Button button = (Button)findViewById(R.id.button);
        final TextView textView = (TextView)findViewById(R.id.text_view);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                //從JNI中獲取字符串
                textView.setText(JniUtils.getString());
            }
        });
    }

}

JniUtils類代碼,我把 native 方法單獨(dú)拿出來,放到這個(gè)類里面


package com.ralf.www.jnitest;

/**
 * 作者:Ralf on 2017/11/9 17:31
 * desc:
 */
public class JniUtils {
    static {
        System.loadLibrary("jnitest");
    }

    public static native String getString();
}

可以看到 native 方法書寫方式,類似于接口,但需要有關(guān)鍵字 native。

  • 創(chuàng)建 jni文件夾

在 src---> main 下創(chuàng)建 jni 文件夾,并在連添加三個(gè)文件

這里寫圖片描述

(1)jnitest.cpp
(2)Android.mk
(3)Applicationm.mk

  • 編寫 .c或.cpp文件

jnitest.cpp 代碼,其中extern “C” 聲明,是為了說明可能會(huì)用到 C 的代碼


#include <jni.h>

extern "C" {
/*
 * Class:     com_ralf_www_jnitest_JniUtils
 * Method:    getString
 * Signature: ()Ljava/lang/String;
 */

JNIEXPORT jstring JNICALL Java_com_ralf_www_jnitest_JniUtils_getString
  (JNIEnv *env, jclass jc){

    const char* ch = "String From JNI";
    return env->NewStringUTF(ch);
  }

}

注意:
(1)函數(shù)名:JNIEXPORT + 返回類型 + JNICALL Java_+包名 + 類型 + 函數(shù)名(java中聲明的),以下劃線連接
(2)返回值類型,是 jni 中的數(shù)據(jù)類型,若沒有返回類型,則使用 void
(3)默認(rèn)傳入兩個(gè)參數(shù) JNIEnv* env(jvm運(yùn)行環(huán)境), jobject obj(調(diào)用這個(gè)函數(shù)的Java對(duì)象)

  • 配置編譯環(huán)境
    (1)Android.mk文件
    .cpp 文件為 jnitest.cpp,對(duì)應(yīng)的庫的名字為 jnitest,即生成的 so 為 jnitest.so

LOCAL_PATH := $(call my-dir) 指定cpp文件位置

include $(CLEAR_VARS) #編譯時(shí)清除舊庫

LOCAL_MODULE    := libjnitest #生成so的名字,前面加lib
LOCAL_SRC_FILES := jnitest.cpp #需要編譯的cpp文件

include $(BUILD_SHARED_LIBRARY) #注明生成動(dòng)態(tài)庫

(2)Application.mk文件
這個(gè)文件中一般進(jìn)行ABI管理,告訴ndk-build生成適用于那些CPU指令集的庫文件,=all就是編譯生成所有CPU指令集的庫文件
APP_ABI :=all

(3)build.gradle 配置


Android{

  ...

  externalNativeBuild{
        //指定Android.mk文件
        ndkBuild{
            path 'src/main/jni/Android.mk'
        }
    }

    //生成so到指定路徑下
    sourceSets{
        main{
            jni.srcDirs = []
            jniLibs.srcDirs = ['libs']
        }
    }
}

(4)gradle.properties設(shè)置

該文件中要加上這一句話
android.useDeprecatedNdk=true

  • 加載動(dòng)態(tài)庫

加載比較簡(jiǎn)單了,前面得代碼中已經(jīng)寫過了

(1)需要在static代碼塊中加載

(2)System.loadLibrary

(3) 庫文件去掉.so, 去掉前面的lib


static {
    //加載動(dòng)態(tài)庫 libjnitest.so
    System.loadLibrary("jnitest");
}

4.常見錯(cuò)誤

(1)函數(shù)名編寫中容易出錯(cuò),注意格式
(2)配置文件中Android.mk 中的module name 注意不要寫錯(cuò)
(3)注意gradle文件的配置

代碼地址 JniTest

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容