LZ-Says:半夜睡覺滾了地上了,無奈之下醒來了,想想最近幾天因為一個括號導致JNI遲遲不能開展,心里面無奈又崩潰,索性直接起來整完得了~
前言
當前畢業的時候,感覺自己掌握了全世界,隨著參加工作的時間一天天的增長,突然覺得丫的,啥都不會啊,要學的東西還是太多太多了。不過近來被飛大姐洗腦成功,<font color=#FF0000>萬事不過幾行代碼而已,干它~
So 今天為大家帶來簡單的jni配置,使用,以及運行我們的第一個簡單小demo`
Hi Jni
總是再說jni,jni,那么jni到底是什么東西,我們一起來看看:
JNI是Java Native Interface的縮寫,它提供了若干的API實現了Java和其他語言的通信(主要是C&C++)。從Java1.1開始,JNI標準成為java平臺的一部分,它允許Java代碼和其他語言寫的代碼進行交互。
JNI標準至少要保證本地代碼能工作在任何Java 虛擬機環境。
下面為大家附上官方Android平臺架構圖:
<center>可以看到Android上層的Application和ApplicationFramework都是使用Java編寫,底層包括系統和使用眾多的Libraries都是C/C++編寫的,所以上層Java要調用底層的C/C++函數庫必須通過Java的JNI來實現。
Jni使用場景
當你開始著手準備一個使用JNI的項目時,請確認是否還有替代方案。應用程序使用JNI會帶來一些副作用。下面給出幾個方案,可以避免使用JNI的時候,達到與本地代碼進行交互的效果:
1、JAVA程序和本地程序使用TCP/IP或者IPC進行交互。
2、當用JAVA程序連接本地數據庫時,使用JDBC提供的API。
3、JAVA程序可以使用分布式對象技術,如JAVA IDL API。
這些方案的共同點是,JAVA和C處于不同的線程,或者不同的機器上。這樣,當本地程序崩潰時,不會影響到JAVA程序。
下面這些場合中,同一進程內JNI的使用無法避免:
1、程序當中用到了JAVA API不提供的特殊系統環境才會有的特征。而跨進程操作又不現實。
2、你可能想訪問一些己有的本地庫,但又不想付出跨進程調用時的代價,如效率,內存,數據傳遞方面。
3、JAVA程序當中的一部分代碼對效率要求非常高,如算法計算,圖形渲染等。
<font color=#FF0000>總之,只有當你必須在同一進程中調用本地代碼時,再使用JNI。
Jni缺陷
一旦使用JNI,JAVA程序就喪失了JAVA平臺的兩個優點:
1、程序不再跨平臺。要想跨平臺,必須在不同的系統環境下重新編譯本地語言部分。
2、程序不再是絕對安全的,本地代碼的不當使用可能導致整個程序崩潰。一個通用規則是,你應該讓本地方法集中在少數幾個類當中。這樣就降低了JAVA和C之間的耦合性
Jni作用
JNI可以這樣與本地程序進行交互:
1、你可以使用JNI來實現“本地方法”(native methods),并在JAVA程序中調用它們。
2、JNI支持一個“調用接口”(invocation interface),它允許你把一個JVM嵌入到本地程序中。本地程序可以鏈接一個實現了JVM的本地庫,然后使用“調用接口”執行JAVA語言編寫的軟件模塊。
簡單了解以上內容后,我們開啟正題,在Android開發中,我們該怎么使用jni,或者說是在Android Studio中,我們該怎么使用jni呢?表急,往下瞅瞅~
話說,我們開發android應用程序基礎不就是下載官方相關的SDK,ADT啥啥啥的,同理jni也一樣。
現在為大家簡單介紹NDK~如下。
Hi NDK
Android NDK 是一套允許使用原生代碼語言(例如 C 和 C++)實現部分應用的工具集。在開發某些類型應用時,這有助于重復使用以這些語言編寫的代碼庫。
同理,官方也為我們提供了一個小例子,簡單走馬觀花看一下:
public class MyActivity extends Activity {
/**
* 使用 C/C++ 語言實現的原生方法
*/
public native void computeFoo();
}
native方法,當年看到這個東西感覺好高大上,沒想到而今我也能玩玩,哈哈,GGG~
官方對于NDK是這樣說的:
NDK 不適用于大多數初學的 Android 編程者,對許多類型的 Android 應用沒什么價值。 因為它不可避免地會增加開發過程的復雜性,所以通常不值得使用。 但如果您需要執行以下操作,它可能很有用:
從設備獲取卓越性能以用于計算密集型應用,例如游戲或物理模擬;
重復使用您自己或其他開發者的 C 或 C++ 庫。
丫的,爺兒們好奇瞅瞅不行啊?
行~!
哈哈~
簡單有個印象后,我們開始配置相關內容,為什么要說這個配置呢,主要有以下幾個原因:
雖說配置是傻瓜式無腦操作,但是對于LZ小白這樣的人來說,依然覺得是一件很高大上的事兒,何況,丫的,連配置都不會,還開發個卵子?
凡事兒親歷親為,不經歷,怎能有成長?
配置之前,我們還需要了解我們需要配置or下載哪兒些工具,以及這些東西都是干嘛的,不然稀里糊涂的,糟心。
同理,我們也需要去簡單了解下使用NDK好處:
1、代碼的保護。由于apk的java層代碼很容易被反編譯,而C/C++庫反匯難度較大;
2、可以方便地使用現存的開源庫。大部分現存的開源庫都是用C/C++代碼編寫的;
3、提高程序的執行效率。將要求高性能的應用邏輯使用C開發,從而提高應用程序的執行效率;
4、便于移植。用C/C++寫得庫可以方便在其他的嵌入式平臺上再次使用。
NDK以及所需構建工具簡介
NDK:這個還需要再說嘛?
CMake:外部構建工具;
CMake是一個開源的跨平臺系列工具,旨在構建,測試和打包軟件。
CMake用于使用簡單的平臺和編譯器獨立的配置文件來控制軟件編譯過程,并生成可以在選擇的編譯環境中使用的本地makefile和工作空間。
CMack工具套件由Kitware創建,以響應對開源項目(如ITK和VTK)的強大的跨平臺構建環境的需求。
官方地址:https://cmake.org/ 有興趣可以簡單了解下~
- LLDB:Android Studio 上面調試本地代碼的工具
LLDB是下一代高性能調試器。它被構建為一組可重用的組件,可以高度利用較大的LLVM項目中的現有庫,例如Clang表達式解析器和LLVM反匯編程序。
在Mac OS X中,LLDB是Xcode中的默認調試器,支持在桌面和iOS設備和模擬器上調試C,Objective-C和C ++。
LLDB項目中的所有代碼都可以使用標準的 LLVM許可證(一種開放源代碼“BSD風格”)許可證。
LLDB目前將調試信息轉換成clang類型,以便它可以利用clang編譯器基礎架構。這允許LLDB在表達式中支持最新的C,C++,Objective C和Objective C ++語言特性和運行時間,而無需重新實現任何此功能。在編寫表達式的函數,拆卸指令和提取指令詳細信息等時,還可以利用編譯器來處理所有ABI細節。
主要優點包括:
- C,C ++,Objective C的最新語言支持 ;
- 可以聲明局部變量和類型的多行表達式;
- 支持時使用JIT表達式;
- 當JIT不能使用時,評估表達式中間表示(IR)
以上簡單了解下就好了,至于為啥要這么搞,就是為了方便以后有需要直接翻出來看看~
NDK配置(包含構建工具,調試工具)
<font color=#FF0000>下載安裝NDK,有倆種方式,其實都一樣,只是一個需要手動下載,解壓,配置目錄,一個Android Studio自動完成以上操作。
配置NDK倆種方式
手動下載NDK
NDK下載地址如下:
https://developer.android.google.cn/ndk/downloads/index.html
大家可自行選擇相應版本進行下載。
下載完成后,解壓本地目錄,在Android Studio中配置路徑即可,如下圖所示:
<center>強大的Android Studio走起~
1.點擊Project Structure,選擇 Download Android NDK;
<center>2.耐心等待吧,LZ下載比較快,解壓比較慢~
3.下載解壓完成,自動錄入地址,省事兒哈~
<center>到此,關于NDK下載安裝倆種方式圖解完畢~
我們看一下ndk目錄各個作用,簡單了解下。
- docs: 幫助文檔
build/tools:linux的批處理文件
platforms:編譯c代碼需要使用的頭文件和類庫
prebuilt:預編譯使用的二進制可執行文件
sample:jni的使用例子
source:ndk的源碼
toolchains:工具鏈
ndk-build.cmd:編譯打包c代碼的一個指令,需要配置系統環境變量
配置構建工具以及調試工具
1.如下圖所示,點擊SDK Manager,選擇下載安裝CMake以及LLDB;
<center>簡單看一下版本信息,果斷OK~
<center>沒啥可說的,等待,不過分分鐘搞定~
<center>到此,基本配置已完成,但是如何查驗NDK是否成功安裝了呢?
配置下環境變量瞅瞅唄~
配置環境變量
復制NDK地址,按如下圖示進行操作即可。
cmd直接輸入ndk-build回車
<center>在這里吐槽下LZ之前遇到的坑。
LZ目錄習慣命名為HLQWorkSofe(Android) or HLQWorkSofe(Java),在之前從來沒有出現過問題,但是在NDK時候,卻怎么也不行,最后無奈下只能把()去掉。
問題截圖如下:
<center><font color=#FF0000>搞得LZ都快放棄了,最后一試,好了,給我郁悶的,大家注意啊~
創建個項目玩一玩
創建項目時,記得勾選下面的Include C++ support哦~
導入C++支持庫,不然沒法玩哈~
<center>
一路next下之后,關于最后一步選擇時,在此作特殊說明下。
<center>C++ Standard:使用下拉列表選擇您希望使用哪種 C++ 標準。選擇 Toolchain Default 會使用默認的 CMake 設置;
Exceptions Support:如果您希望啟用對 C++ 異常處理的支持,請選中此復選框。如果啟用此復選框,Android Studio 會將 -fexceptions 標志添加到模塊級 build.gradle 文件的 cppFlags 中,Gradle 會將其傳遞到 CMake;
Runtime Type Information Support:如果您希望支持 RTTI,請選中此復選框。如果啟用此復選框,Android Studio 會將 -frtti 標志添加到模塊級 build.gradle 文件的 cppFlags 中,Gradle 會將其傳遞到 CMake。
點擊Finish之后,我們稍等片刻~
查看默認生成例子
<center>下面我們一起來探究下官方提供的例子,看看從例子中我們能得知什么對我們有用的信息。
從上圖可看到,和以前唯一不同的就是多出了cpp目錄以及External Build Files倆個目錄,那么這倆個都有什么用呢?一起來看看。
cpp 目錄存放所有 native code 的地方,包括源碼,頭文件,預編譯項目等。對于新項目,Android Studio 創建了一個 C++ 模板文件:native-lib.cpp,并且將該文件放到了你的 app 模塊的 src/main/cpp/ 目錄下。這份模板代碼提供了一個簡答的 C++ 函數:stringFromJNI(),該函數返回一個字符串:”Hello from C++”
External Build Files 目錄是存放 CMake 或 ndk-build 構建腳本的地方。有點類似于 build.gradle 文件告訴 Gradle 如何編譯你的 APP 一樣,CMake 和 ndk-build 也需要一個腳本來告知如何編譯你的 native library。對于一個新的項目,Android Studio 創建了一個 CMake 腳本:CMakeLists.txt,并且將其放到了你的 module 的根目錄下
首先還是來看看代碼吧,比較直觀也比較好理解。
1.查看activity代碼
package cn.hlq.hlqjnipro;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
}
首先static靜態塊去加載so庫,本地編寫native方法,調用方法輸出內容。
2.查看native-lib.cpp代碼
#include <jni.h>
#include <string>
extern "C"
JNIEXPORT jstring JNICALL
Java_cn_hlq_hlqjnipro_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
簡單可以看到,首先定義hello變量,之后return。
3.查看CMakeLists.txt
簡單的翻譯了下,如有不正之處歡迎指出`
# 使用Android Studio使用CMake的更多信息,閱讀文檔:https://d.android.com/studio/projects/add-native-code.html
# 設置CMake的最低版本構建本機所需庫
cmake_minimum_required(VERSION 3.4.1)
# 創建和名稱庫,使它是靜態的或共享,并提供了相對路徑的源代碼
# 可以定義多個圖書館,CMake將為你構建這些內容
# Gradle自動與你的APK包共享庫
add_library( # 設置庫名稱
native-lib
# 集庫作為一個共享庫
SHARED
# 提供了一個相對路徑你的源文件
src/main/cpp/native-lib.cpp )
# 搜索指定預先構建的庫和存儲路徑變量。因為CMake包括系統庫搜索路徑中默認情況下,只需要指定想添加公共NDK庫的名稱,在CMake驗證庫之前存在完成構建
find_library( # 設置path變量的名稱
log-lib
# 在CMake定位前指定的NDK庫名稱
log )
# 指定庫CMake應該鏈接到目標庫中,可以鏈接多個庫,比如定義庫,構建腳本,預先構建的第三方庫或者系統庫
target_link_libraries( # 指定目標庫
native-lib
# 目標庫到日志庫的鏈接 包含在NDK
${log-lib} )
一開始還覺的差不多點呢,結果看到這里越來越蒙圈,還是先運行一下,看看結果吧。
按照剛才的簡單分析,這個demo輸出的結果應該為:Hello from C++ 。一起來驗證一下唄~
<center>嗯,確實,輸出了,有些似懂非懂的,繼續研究~
經過查詢,發現一個之前從未關注的內容,那就是從編譯到運行示例 APP 的流程到底是怎樣呢,如下:
- Gradle 調用外部構建腳本,也就是 CMakeLists.txt;
- CMake 會根據構建腳本的指令去編譯一個 C++ 源文件,也就是 native-lib.cpp,并將編譯后的產物扔進共享對象庫中,并將其命名為 libnative-lib.so,然后 Gradle 將其打包到 APK 中;
- 在運行期間,APP 的 MainActivity 會調用 System.loadLibrary() 方法,加載 native library。而這個庫的原生函數,stringFromJNI(),就可以為 APP 所用了;
- MainActivity.onCreate() 方法會調用 stringFromJNI(),然后返回 “Hello from C++”,并更新 TextView 的顯示;
注意:Instant Run 并不兼容使用了 native code 的項目。Android Studio 會自動禁止 Instant Run 功能。
如果你想驗證一下 Gradle 是否將 native library 打包進了 APK,你可以使用 APK Analyzer:
選擇 Build > Analyze APK。
從 app/build/outputs/apk/ 路徑中選擇 APK,并點擊 OK。
如下圖,在 APK Analyzer 窗口中,選擇 lib/<ABI>/,你就可以看見 libnative-lib.so
轉自:http://blog.csdn.net/wl9739/article/details/52607010
仿造demo來一下,首先布局中新增一個TextView,activity中編寫一個native方法,調用native方法并輸出結果,如下:
TextView tv1 = (TextView) findViewById(R.id.sample_text_1);
tv1.setText(hlqFromJNI());
public native String hlqFromJNI();
在cpp中這么玩下:
#include <jni.h>
#include <string>
extern "C" {
JNIEXPORT jstring JNICALL
Java_cn_hlq_hlqjnipro_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT jstring JNICALL
Java_cn_hlq_hlqjnipro_MainActivity_hlqFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "真惡心啊 Fuck";
return env->NewStringUTF(hello.c_str());
}
}
大家有沒有注意到Java_cn_hlq_hlqjnipro_MainActivity_hlqFromJNI這個名稱,方法名必須為Java_包名全路徑_方法名
運行結果如下:
<center>一般情況下,Gradle 會將你的本地庫構建成 .so 文件,然后將其打包到你的 APK 中。如果你想 Gradle 構建并打包某個特定的 ABI 。你可以在你的 module 層級的 build.gradle 文件中使用 ndk.abiFilters 標簽來指定他們:
android {
...
defaultConfig {
...
externalNativeBuild {
cmake {...}
// or ndkBuild {...}
}
ndk {
// Specifies the ABI configurations of your native
// libraries Gradle should build and package with your APK.
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
'arm64-v8a'
}
}
buildTypes {...}
externalNativeBuild {...}
}
提供給別人使用
ReBuild Project之后,將生成好的so庫提供有需要的人即可,不過要記得調用的時候包名要一致,不然會報一個找不到so的問題。
LZ簡單寫了一個測試demo,如下圖:
<center>運行結果如下:
<center>比較惡心的就是包名這塊,等以后再看看有沒有好的解決辦法吧。
拓展APP構建流程
查看官方文檔中,突然發現有關構建流程內容,一起了解下,如下:
構建流程涉及許多將您的項目轉換成 Android 應用軟件包 (APK)的工具和流程。構建流程非常靈活,因此了解它的一些底層工作原理會很有幫助。
典型 Android 應用模塊的構建流程圖如下:
典型 Android 應用模塊的構建流程通常依循下列步驟:
1.編譯器將您的源代碼轉換成 DEX(Dalvik Executable) 文件(其中包括運行在 Android 設備上的字節碼),將所有其他內容轉換成已編譯資源;
2.APK 打包器將 DEX 文件和已編譯資源合并成單個 APK。不過,必須先簽署 APK,才能將應用安裝并部署到 Android 設備上;
3.APK 打包器使用調試或發布密鑰庫簽署您的 APK:
a. 如果您構建的是調試版本的應用(即專用于測試和分析的應用),打包器會使用調試密鑰庫簽署您的應用。Android Studio 自動使用調試密鑰庫配置新項目;
b. 如果您構建的是打算向外發布的發布版本應用,打包器會使用發布密鑰庫簽署您的應用
4.在生成最終 APK 之前,打包器會使用 zipalign 工具對應用進行優化,減少其在設備上運行時的內存占用
構建流程結束時,您將獲得可用來進行部署、測試的調試 APK,或者可用來發布給外部用戶的發布 APK。
參考文獻
- http://baike.baidu.com/link?url=QrA-HKewzfPKP5UejCXH8JhE4yQs5MhwG6EBYI8imO9k8zYIlM0h2DYffNSqdK8dG6LZLfT0dK5ocK2NEsOUJq ;
- http://blog.csdn.net/tongseng/article/details/53005123;
- http://www.cnblogs.com/bastard/archive/2012/05/19/2508913.html;
- http://blog.csdn.net/baidu_26352053/article/details/53931045;
- https://developer.android.google.cn/ndk/guides/concepts.html;
- https://developer.android.google.cn/studio/build/index.html;
- http://blog.csdn.net/wl9739/article/details/52607010
結束語
這篇文章寫的夠揪心,個人感覺亂糟糟的,實際學習的時候也是亂糟糟的,幾乎都是靠著各種搜索,況且還有一些不靠譜的博文誤導或者說是版本有些老。
望觀看者海涵`有問題及時溝通。