demo鏈接在文末。
在Android Studio中有3種方法生成so文件。
最初的時候,我曾經(jīng)使用過Visual Studio生成so文件。經(jīng)歷了從入門到放棄的過程。。。。
——————文中的方法二 Android Studio 3.1已經(jīng)不再支持,build的時候會報錯(寫這篇文章的時候版本是Android Studio 2,錯誤信息如下)。————
Error: Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio. Please switch to a supported build system.
Consider using CMake or ndk-build integration. For more information, go to:
https://d.android.com/r/studio-ui/add-native-code.html#ndkCompile
To get started, you can use the sample ndk-build script the Android
plugin generated for you at:
D:\AndroidStudio\others\JNI2\app\build\intermediates\ndk\debug\Android.mk
Alternatively, you can use the experimental plugin:
https://developer.android.com/r/tools/experimental-plugin.html
To continue using the deprecated NDK compile for another 60 days, set
android.deprecatedNdkCompileLease=1526536223345 in gradle.properties
1.通過mk文件和gradle。
//這種是帶mk文件的方法
讓Gradle構(gòu)建支持NDK
http://www.iloveandroid.net/2015/09/18/GradleNdkSupport/
首先,什么是mk文件?gradle是什么?
gradle相關(guān)資料:
gradle的wiki解釋:https://zh.wikipedia.org/wiki/Gradle
注意第一句:Gradle是一個基于Apache Ant和Apache Maven概念的項目自動化建構(gòu)工具。
那么什么是自動化構(gòu)建?
組建自動化(英語:Build automation,又稱構(gòu)建自動化、自動化構(gòu)建)指自動創(chuàng)建軟件組建的一組進程,包括將計算機源代碼編譯成二進制碼、將二進制碼包裝成軟件包以及運行自動化測試。
組建自動化原先是通過創(chuàng)建makefile來完成的,如今則主要使用兩大類工具完成組建[1]:
組建自動化工具(如Make、Rake、Cake、MS build、Ant、Gradle等)。
MakeFile相關(guān)資料:
Makefile簡易教程:http://www.lxweimin.com/p/ff0e0e26c47a
http://read.pudn.com/downloads154/ebook/681020/%E4%B8%AD%E6%96%87Makefile%E6%95%99%E7%A8%8B.pdf
http://chuzhiyan.com/2017/03/11/%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84Makefile%E6%95%99%E7%A8%8B/
可以簡單看下。總而言之,一開始是使用makefile,后面就演變成使用gradle。
什么是mk文件?
首先,官方文檔鏈接如下:
https://developer.android.com/ndk/guides/android_mk.html?hl=zh-cn
但是我沒有看懂。然后找到了這篇:
淺析Android下的Android.mk文件
http://blog.csdn.net/flydream0/article/details/7164501
開頭是這樣寫的:大家都知道在Linux下編輯經(jīng)常要寫一個Makefile文件, 可以把這個Makefile文件理解成一個編譯配置文件,它保存著如何編譯的配置信息,即指導編譯器如何來編譯程序,并決定編譯的結(jié)果是什么。而在Android下的Android.mk文件也是類型的功能,顧名思義,從名字上就可以猜測得到,Android.mk文件是針對Android的Makefile文件.
因此可以看出android下的mk文件就是用于組件自動化的文件。
編寫Android.mk文件需要按照google官方文檔中規(guī)范去編寫。
這里通過mk文件和gradle協(xié)同工作去完成組件自動化的工作。
Gradle Android Plugin 中文手冊
https://chaosleong.gitbooks.io/gradle-for-android/content/basic_project_setup/simple_build_files.html
//-------------------這個非常詳細地介紹了gradle----------------------------------
Gradle 完整指南(Android)
http://www.lxweimin.com/p/9df3c3b6067a
//-------------------------------具體demo待補......-----------------
注意,在此之前,需要配置NDK環(huán)境。這里不作介紹。
所需增加文件如下:
Application.mk
Android.mk
具體步驟截圖:
新建一個工程,不用勾選include C++ support。
① 首先配置gradle。文字版在下面。
gradle中配置如下:
apply plugin: 'com.android.application'
android {
? ? compileSdkVersion 26
? ? buildToolsVersion "26.0.1"
? ??defaultConfig {
? ??? ??applicationId "com.demo.jnidemo"
? ???? ???minSdkVersion 19
? ???? ???targetSdkVersion 26
? ???? ???versionCode 1
? ???? ???versionName "1.0"
? ???? ???testInstrumentationRunner? ?"android.support.test.runner.AndroidJUnitRunner"
? ???}
? ???buildTypes {
? ???? ???release {
? ???? ???? ???minifyEnabled false
? ???? ???? ???proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
? ???? ???}
? ???}
? ???sourceSets.main {
? ???? ???jni.srcDirs = []
? ???? ???jniLibs.srcDirs = ['src/main/jniLibs']//設(shè)置目標的so存放路徑
? ???}
? ???task ndkBuild(type:Exec,description:'Compile JNI source via NDK'){
? ???? ???commandLine "C:\\Users\\lei\\AppData\\Local\\Android\\Sdk\\ndk-bundle\\ndk-build.cmd",
? ????? ????//配置ndk的路徑
? ????? ????'NDK_PROJECT_PATH=build/intermediates/ndk',
? ????? ????//ndk默認的生成so的文件
? ????? ????'NDK_LIBS_OUT=src/main/jniLibs',
? ? ? ? ? ?//配置的我們想要生成的so文件所在的位置
? ????? ????'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
? ????? ????//指定項目以這個mk的方式
? ????? ????'NDK_APPLOCATION_MK=src/main/jni/Application.mk'
? ????? ????//指定項目以這個mk的方式
}
? ????? ????tasks.withType(JavaCompile){
? ????? ????//使用ndkBuild
? ????? ????compileTask ->compileTask.dependsOn ndkBuild
? ????? ????}
}
dependencies {
? ????? ??compile fileTree(dir: 'libs', include: ['*.jar'])
.......................................................................
}
//這里還應(yīng)該加上一個clear部分的代碼,暫時未找到。
②?生成.h頭文件
首先,新建一個類JNI。
public class JNI {
? ? static{
? ? ? ? System.loadLibrary("jni_demo");
? ? }
? ? public native int getInt();//隨便定義一個函數(shù)。
}
——————————android studio 3中生成.h頭文件分割線——————————
android studio 3中這種生成.h頭文件的方式不行了,我沒有找到生成的class文件的位置,最簡單的就是自己寫.h文件或者使用External Tools生成。
使用External Tools一勞永逸。
使用External Tools步驟如下所示:
1.點擊File-Settings
2.點擊Tools,選擇External Tools,然后點擊add,彈出Create Tool。
具體配置如下:
Name:javah
Description:javah
Program:$JDKPath$\bin\javah.exe
Parameters:-classpath . -jni -d $ModuleFileDir$\src\main\jni $FileClass$
Working directory:$ModuleFileDir$\src\main\Java
-classpath classes 指明類所在的位置
-d 產(chǎn)生的.h文件放到指定目錄下;
配置完后選中相應(yīng)的java文件,右鍵,點擊External Tools->javah
然后main/jni目錄下就會生成.h文件了。
參考鏈接:
Android Studio 3.0 JNI的實現(xiàn)
https://blog.csdn.net/ziyoutiankoong/article/details/79696279
——————————android studio 3中生成.h頭文件分割線——————————
android studio 3中下面這張方式生成.h失效了,因為路徑改了。
—————————失效分割線—————————
然后rebuild project。
查看這個目錄下是否生成.class文件,沒有生成請重新來過。
再打開Terminal輸入指令
cd app/build/intermediates/classes/debug
再輸入
javah -jni com.demo.jnidemo.JNI
然后就會生成.h文件。這個文件后面有用。
自動生成的代碼如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include? <jni.h>
/* Header for class com_demo_jnidemo_JNI */
#ifndef _Included_com_demo_jnidemo_JNI
#define _Included_com_demo_jnidemo_JNI
#ifdef __cplusplus
extern"C"{
#endif
/*
* Class:? ? com_demo_jnidemo_JNI
* Method:? ? getInt
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_com_demo_jnidemo_JNI_getInt
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
—————————失效分割線—————————
③新建mk文件。
在project模式下,main目錄中新建文件夾-jni。
然后,在jni目錄下新建2個mk文件。
Application.mk
Android.mk
Application.mk中這句表示生成所以平臺的so文件。
APP_ABI:=all
然后新建Android.mk文件。
Android.mk文件需要根據(jù)具體文件去編寫,暫時不管。
將之前生成的.h頭文件復制到j(luò)ni目錄下。
然后在jni目錄下新建一個jni.c文件。
編寫.c文件
#include <stdio.h>
#include <com_demo_jnidemo_JNI.h>
Java_com_demo_jnidemo_JNI_getInt
(JNIEnv *env, jobject jobj){
? ? return 3;
}
然后就可以編寫Android.mk文件了。
一個最簡單Android.mk文件。
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := jni_demo
LOCAL_SRC_FILES := jni.c
include $(BUILD_SHARED_LIBRARY)
然后就可以rebuild project,生成so文件了。
有時會遇到這種錯誤,rebuild project無法clean,解決方法,關(guān)掉android studio,找到文件手動刪除。找到目錄D:\AndroidStudio\JNIDemo\app\build? ,刪除build文件夾,然后重新打開。打開后,重新rebuild project。
Error:Execution failed for task ':app:clean'.
> Unable to delete directory: D:\AndroidStudio\JNIDemo\app\build\intermediates\classes\debug
成功的話,會生成jniLibs文件夾,此時由于已經(jīng)在gradle中配置了該路徑,可以直接使用了。
在MainActivity中使用jni類,打印log,查看日志。
JNI jni =new JNI();
Log.d(TAG,"jni getInt is : "+ jni.getInt());
log顯示return 3.
到這里為止是只有一個c文件的情況,實際使用中,存在多個.c文件。但是由于在生成so文件的時候src設(shè)定所以需要修改。實際使用幾個.c文件就添加幾個.c文件到Android.mk文件中。
LOCAL_SRC_FILES := xxx.c? yyy.c
C語言是魔鬼,C++我差點掛科的好嗎!
首先,新建一個test_get_int.h文件
添加一句:int test(void);? //這是函數(shù)聲明。
#ifndef JNIDEMO_TEST_GET_INT_H
#define JNIDEMO_TEST_GET_INT_H
int test(void);
#endif //JNIDEMO_TEST_GET_INT_H
然后,修改jni.c文件。
修改了這幾個地方。
具體代碼如下:
#include <stdio.h>
#include <com_demo_jnidemo_JNI.h>
//新增處
#include <.h>
Java_com_demo_jnidemo_JNI_getInt
(JNIEnv *env, jobject jobj){
//? return 3;
//修改處
return test();
}
//test中的自定義函數(shù)
int test(){
return 1;
}
然后rebuild project。
然后run運行,返回1。
2.直接通過gradle。
通過gradle和jni,無需mk文件。
①配置gradle。
首先在gradle.properties里面加上這句。
android.useDeprecatedNdk=true
配置gradle。
ndk {
moduleName = "jni_test"
abiFilters "armeabi","armeabi-v7a","x86"
ldLibs "log"
}
②新建JNI類。
public class JNI {
static{
System.loadLibrary("jni_test");
}
public native int getInt();
}
project模式下,在main目錄下,新建jni文件夾。
然后在jni目錄下,新建jni_test.c文件。注意,這個.c文件的名字就是之前在gradle中配置的那個名字,也是JNI類中l(wèi)oad的那個名字。
.c文件內(nèi)容如下。
#include <stdio.h>
#include <jni.h>
jint Java_com_demo_jni2_JNI_getInt(JNIEnv* env, jobject jobj){
return 3;
}
android中調(diào)用c的關(guān)鍵在于橋接函數(shù)。
這個函數(shù)是有命名規(guī)則的。
拆分下面的字符:
j??
int? (返回值類型)
Java
_(以下劃線連接)
com.demo.jni2(包名)
JNI(類名)
getInt(函數(shù)名)
JNIEnv* env, jobject jobj(這個貌似都是統(tǒng)一的參數(shù))
jint Java_com_demo_jni2_JNI_getInt(JNIEnv* env, jobject jobj)
在main中調(diào)用。
最后,成功運行。
3.通過cmake和gradle //這個是比較新的方法。配置比較簡單。
新建工程,不用勾選include C++ support。
勾選include C++ support。好像是給你看一個例子。
一路next下去,然后finish。
然后,在main目錄下新建cpp文件夾。
新建.c文件或者.cpp文件。
jni_demo.c或者jni_demo.cpp。
jni_demo.c具體代碼如下:
#include <jni.h>
JNIEXPORT jint JNICALL
Java_com_demo_cmaketestdemo_JNI_getInt
(JNIEnv *env, jobject jobj){
? ? return 3;
}
新建.cpp文件也可以。
jni_demo.cpp具體代碼如下。
#include <jni.h>
extern"C"
JNIEXPORT jint JNICALL
Java_com_demo_cmaketestdemo_JNI_getInt
(JNIEnv *env, jobject jobj){
return 3;
}
將CMakeLists.txt文件復制到app目錄下或者新建一個CMakeLists.txt。
D:\AndroidStudio\CMakeTestDemo\app\CMakeLists.txt
關(guān)于CMakeLists.txt的具體解釋,可參考文章結(jié)尾處的參考鏈接。
這里以jni_demo.c為例,生成so,如果你需要以jni_demo.cpp生成so,需要修改這里。將
jni_demo.c改成jni_demo.cpp即可。
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
jni_demo
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/jni_demo.c )
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
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 )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
target_link_libraries( # Specifies the target library.
jni_demo
# Links the target library to the log library
# included in the NDK.
${log-lib} )
最后配置gradle文件。
apply plugin: 'com.android.application'
android {
? ? compileSdkVersion 26
? ? buildToolsVersion "26.0.1"
? ??defaultConfig {
? ??? ??applicationId "com.demo.cmaketestdemo"
? ???? ???minSdkVersion 19
? ???? ???targetSdkVersion 26
? ???? ???versionCode 1
? ???? ???versionName "1.0"
? ???? ???testInstrumentationRunner? ?"android.support.test.runner.AndroidJUnitRunner"
? ???? ????externalNativeBuild {
? ???? ????? ???? ???? cmake {
? ???? ?????? ???? ????? ???? ?????? ???? ????cppFlags ""
? ???? ?????? ???? ?????? ???? ?????}
? ???? ?????}
? ???? ??}
? ???? ??buildTypes {
? ???? ??? ???? ??release {
? ???? ??? ???? ??? ???? ??minifyEnabled false
? ???? ??? ???? ??? ???? ??proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
? ???? ???? ???? ???}
? ???? ???? }
? ???? ????externalNativeBuild {
? ???? ???? ???? ???cmake {
? ???? ???? ???? ???? ???? ??? path "CMakeLists.txt"
? ???? ???? ???? ???}
? ???? ???}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
.......................................................................
}
更新gradle之后,rebuild project,然后這個目錄下就會生成so文件。
依舊是簡單地驗證,在main中添加下列代碼。
JNI jni =new JNI();
Log.d(TAG,"jni getInt is : "+ jni.getInt());
最后結(jié)果:
下面以cmake工程為例,記錄如何在別的project中使用so文件。
在別的project使用so我們還需要jar文件,因此,先修改project的gradle使其生成jar。
apply plugin: 'com.android.library'
? ? android {
? ? ? ? compileSdkVersion 26
? ? ? ? buildToolsVersion "26.0.1"
? ? ? ? defaultConfig {
? ? ? ? ? ?//? ? applicationId "com.demo.cmaketestdemo"
? ? ? ? ? ?minSdkVersion 19
? ? ? ? ? ? targetSdkVersion 26
? ? ? ? ? ?versionCode 1
? ? ? ? ? ?versionName "1.0"
? ? ? ? ? testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
? ? ? ? ?externalNativeBuild {
? ? ? ? ? ? ?cmake {
? ? ? ? ? ? ? ? ?cppFlags ""
? ? ? ? ? ? ? ? ?}
? ? ? ? ? ?}
? ? ? ?}
? ? buildTypes {
? ? ? ? ? release {
? ? ? ? ? ? ? minifyEnabled false
? ? ? ? ? ? ?proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
? ? ? ? ? ? ?}
? ? ? ? ?}
? ? ? ? ? externalNativeBuild {
? ? ? ? ? ? ? cmake {
? ? ? ? ? ? ? ? ? path "CMakeLists.txt"
? ? ? ? ? ? ? }
? ? ? ? ? }
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.+'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
}
task makeJar(type: Copy) {
//刪除存在的
delete 'build/libs/jni.jar'
//設(shè)置拷貝的文件
from('build/intermediates/bundles/default/')
//打進jar包后的文件目錄
into('build/libs/')
//將classes.jar放入build/libs/目錄下
//include ,exclude參數(shù)來設(shè)置過濾
//(我們只關(guān)心classes.jar這個文件)
include('classes.jar')
//重命名
rename ('classes.jar', 'jni.jar')
}
makeJar.dependsOn(build)
修改后sync gradle一下。然后打開右邊的gradle命令欄。
找到other中的makeJar命令。鼠標左鍵雙擊。
這個界面就彈出來了。
命令執(zhí)行完在這個目錄下就生成了jni.jar文件啦。
————————update_20180518————————
Terminal中執(zhí)行失敗,后來我找到了解決辦法,參考我這篇文章。AndroidStudio中Terminal運行g(shù)radle(或gradlew)命令失敗解決記錄??http://www.lxweimin.com/p/9a655815e9b0
——————————————————————————
也可以在命令行中輸入這個命令,但是我這臺不行,會報錯,同樣的步驟我在別的電腦上可以。嘗試了各種設(shè)置依然不行,只能使用上面的方法。
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
> Starting Daemon
D:\AndroidStudio\CMakeTestDemo>
D:\AndroidStudio\CMakeTestDemo>gradlew makeJar
Starting a Gradle Daemon, 2 incompatible Daemons could not be reused, use --status for details
FAILURE: Build failed with an exception.
* What went wrong:
Unable to start the daemon process.
This problem might be caused by incorrect configuration of the daemon.
For example, an unrecognized jvm option is used.
Please refer to the user guide chapter on the daemon at https://docs.gradle.org/3.3/userguide/gradle_daemon.html
Please read the following process output to find out more:
-----------------------
Error occurred during initialization of VM
java/lang/NoClassDefFoundError: java/lang/Object
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
好了,下面開始介紹如何在新的project中使用。
①在src目錄下新建jniLibs文件夾,并將之前生成的so文件拷進去。
配置gradle
sourceSets {
? ? main {
? ? jniLibs.srcDirs = ['src/jniLibs'];
? ? }
}
applyplugin:'com.android.application'
android {
? ? ? ? ? compileSdkVersion 26
? ? ? ? ? ?buildToolsVersion "26.0.1"
? ? ? ? ? defaultConfig {
? ? ? ? ? ? ? ?.......
? ? ? ? ? ?}
? ? ? ? ?buildTypes {
? ? ? ? ? ? ? release {
? ? ? ? ? ? ? ? ? .....
? ? ? ? ? ? ? ? ? ?}
? ? ? ? ? ? }
? ? ? ? ? sourceSets {
? ? ? ? ? ? ? ? ? ?main {
? ? ? ? ? ? ? ? ? ? ? ? ? ?jniLibs.srcDirs = ['src/jniLibs'];
? ? ? ? ? ? ? ? ? }
? ? ? ? ? ?}
}
dependencies {
.......
}
②將jni.jar賦值到libs目錄下(libs是默認存在的)。
然后添加depend。
點擊ok,然后就可以了。
最后在mainActivity中使用。
package com.demo.myapplication;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.demo.cmaketestdemo.JNI;
public class MainActivity extends AppCompatActivity {
? ? ? ? ? ?private static final String TAG = "MainActivity";
? ? ? ? ? ? ?@Override
? ? ? ? ? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? ? ? ? ? ? ? super.onCreate(savedInstanceState);
? ? ? ? ? ? ? ? ? ?setContentView(R.layout.activity_main);
? ? ? ? ? ? ? ? ? ?JNI jni =new JNI();
? ? ? ? ? ? ? ? ? ?Log.d(TAG,"jni getInt is : "+ jni.getInt());
? ? ? ? ? ? ? }
}
//?20180518更新
demo鏈接:
https://github.com/VIVILL/android_so_demo
我上傳了第一個和第三個方法的代碼(這里面是不帶.gradle文件夾的,打開后會聯(lián)網(wǎng)自動配置,因此保持連接外網(wǎng)狀態(tài))。
本機軟件環(huán)境配置:
軟件版本:AndroidStudio3.1
gradle版本:4.4
plugin版本:3.1.2
參考鏈接:
Android Studio NDK CMake 指定so輸出路徑以及生成多個so的案例與總結(jié)
http://blog.csdn.net/b2259909/article/details/58591898
Android Studio 2.2 NDK CMake方式入門
http://www.lxweimin.com/p/18724f29d30e
使用Android Studio和CMake進行NDK開發(fā) - 基礎(chǔ)
http://www.lxweimin.com/p/86aac765eecb