本文基于 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 : 即我們需要下載的工具俺榆,會(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
這時(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() 的方法
點(diǎn)擊代碼提示之后羽氮,它會(huì)自動(dòng)在 C++ 代碼中生成相應(yīng)的方法,我們只要實(shí)現(xiàn)其中的方法即可
7)運(yùn)行 App
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");
}