本文基於 Android Studio 3.4.2 、gradle:3.2.1
JNI 是 Java Native Interface (Java 本地接口)的縮寫,是 Java 與其餘語言通訊的橋樑。在 Android 中的應用主要爲:音視頻開發、熱修復、插件化、逆向開發和系統源碼調用,爲了方便使用 JNI 技術,Android 提供了 NDK 工具集合,它和 JNI 開發本質上沒有區別,NDK 是在 Android 中實現 JNI 的手段,java
在 SDK Tools 裏下載 NDK、LLDB、CMakeandroid
這裏的界面很簡單,一個 TextView 和一個 Button ,點擊 Button 後調用 JNI 的方法修改 TextView 的值。git
<?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>
/* * 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()); } }); } }
package com.learnandroid.learn_android.JNIDemo; public class JNIUtils { static { System.loadLibrary("MyJNIHello"); } public static native String sayHelloFromJNI(); }
這裏的靜態代碼塊中首先加載了須要使用的動態庫,而後建立 native 方法。shell
在 Android Studio 的 Terminal 中,cd 到當前項目的根目錄,而後執行 javah 命令api
(注意將含有本地方法的類寫完整的包名)架構
# 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 文件夾中。app
而後打開 Project 目錄下的 com_learnandroid_learn_android_JNIDemo_JNIUtils.h 能夠看到它爲咱們生成的方法原型ide
/* 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
在 cpp 目錄下新建一個 C++ 文件,命名爲 MyJNIDemo工具
這時候若是直接在 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() 的方法
點擊代碼提示以後,它會自動在 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、大數據、人工智能等學習資源。