本篇文章的gradle相關的修改,在android studio 2.3以後的版本已不需要。
請參考這篇:http://gigamine.blogspot.tw/2017/07/android-studio-23-with-jnicmake.html
===================================================================
跌跌撞撞了一整天之後總算讓JNI通了。沒啥可以參考的資料,也只能一直不斷的試誤。
作業環境:OSX with MBP. windows不保證通用。
事前準備:
- File->Project Structure->SDK Location 檢查Android NDK Location是不是已經有路徑了。沒有的話,下面有一行「Download NDK」直接點下去,android studio就會幫你裝好NDK並補上路徑。
- java的版本似乎影響不大。有人建議用1.7,不過我用1.6也可以成功。
接下來就是一連串的改寫:
首先改寫[project root]/gradle/wrapper/gradle-wrapper.properties
(改動的部分以紅色標記):
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
請注意,只改了最後一行的紅色部分:distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
因為JNI的支援必須要使用gradle 2.5以後的版本來build。
接著改寫[project root]/build.gradle(改動的部分以紅色標記):
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle-experimental:0.2.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
}
}
}
支援NDK的plugin被列為experimental。所以得把之前在用的1.2.3版改成這個plugin。
再改寫[project root]/[main module]/build.gradle:
因為experimental版的gradle語法跟舊的有不小的差距,因此會超大改。
apply plugin: 'com.android.model.application' //lib的話就是 com.android.model.library
model { // 之前的android區塊外面必須加上model區塊包住。
android {
compileSdkVersion = 19 //區塊內的所有參數都要加上等號。
buildToolsVersion = "22.0.1"
defaultConfig.with { //掛在android 階層下的參數區塊都要加上".with"。但是第二層以下又不用加... 「.with」不加的話,gradle sync時會出現例外「com.android.build.gradle.managed.AndroidConfig_Impl」
applicationId = 'com.hello.hellojni'
minSdkVersion.apiLevel = 15
targetSdkVersion.apiLevel = 19
versionCode = 1
versionName = '1.00.00.00'
multiDexEnabled = true
}
buildTypes.with {
debug { //不用加".with"。 要是加了的話,gradle sync時會出現例外「Error:Attempt to read a write only view of model of typeorg.gradle.model.ModelMap」
debuggable = true
}
release {
proguardFiles += file('proguard-rules.pro') //proguard的設定要這樣改。
}
}
}
android.productFlavors { //擺在"android"tag的外面只是為了展示也可以這樣加gradle指令。
create("flavor1") { //flavor的名稱必須被「create」給包住。
minSdkVersion.apiLevel = 15 //.apilevel為必須指令。否則gradle編譯時會出現例外「com.android.build.gradle.managed.ProductFlavor_Impl」
applicationId = 'com.hello.hellojni.flavor1'
targetSdkVersion.apiLevel = 19
versionCode = 0
versionName = '0.00.00.00'
}
create("flavor2") {
minSdkVersion.apiLevel = 15
applicationId = 'com.hello.hellojni.flavor2'
targetSdkVersion.apiLevel = 19
versionCode = 0
versionName = '0.00.00.00'
}
}
android.ndk { //新參數。modulename將會是native c code編譯出來的.so檔案的主檔名。在此例,編譯成功之後將會輸出「libapp.so」的檔案。
moduleName = "app"
}
android.packagingOptions { //這個參數下面的指令皆不需要"="。規則有點雜亂...
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
android.lintOptions {
ignoreWarnings = true
disable += 'MissingTranslation' //disable的設定要這樣改。
}
config {
keyPassword = '123456'
storeFile = file('chacha.keystore')
storePassword = '123456'
keyAlias = 'chacha.keystore'
}
}
}
dependencies { //model區塊之外的語法都照舊。
compile project(':project1')
compile project(':project2')
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'junit:junit:4.12' //androidTestCompile / TestCompile指令皆不支援。所以測試的部分要再想辦法...
}
到此,gradle sync沒有出現錯誤的話,恭喜你已經成功一半。
接下來就是加c程式碼然後讓java能夠抓到c的function。
在[project root]/src/main 加上一個新的目錄「jni」。
在裡面新建一個檔名「hellojni.cpp」(主檔名可任意取,無任何關聯性。副檔名一定要是.c或是.cpp)
#include <string.h>
#include <jni.h>
/**
* 函式命名規則: "Java_" + [Java的packagename(只允許英文小寫)把點換成底線]
* + "_" + java程式碼裡面call此函式的class name(注意大小寫) + "_" + 函式名稱.
* 第一個參數是執行此函式的整個java的task環境。可以用來取得java class的變數
* ,static class,function pointer...
* 第二個參數,若是該函式在java 裡面的定義為static method, 這裡要改成 「jclass」。
* 第三個參數以後的就是java端依序定義的傳入資料。
*/
extern "C" { //一定要加. 要不然java會找不到函式,一直跳 Native method not found exception.
JNIEXPORT jstring JNICALL Java_com_hello_hellojni_TestActicity_stringFromJNI(JNIEnv *env, jobject instance) {
return env->NewStringUTF("Hello from JNI ! "); //切記,函式宣告有回傳資料的話,一定要回傳。不回傳的話編譯器不會告訴你錯誤,只有在執行的時候會送你SIGABRT 6...
}
}
對應的Java caller:
package com.hello.hellojni;
import android.app.Activity;
import android.widget.TextView;
import android.os.Bundle;
public class TestActicity extends Activity
{
static // 在使用JNI的函式之前必須使用System.loadLibrary把lib讀進來。
{
System.loadLibrary("app"); //對應android.ndk裡面的「modulename」參數。
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
/* Create a TextView and set its content.
* the text is retrieved by calling a native
* function.
*/
TextView tv = new TextView(this);
tv.setText( stringFromJNI() );
setContentView(tv);
}
/* A native method that is implemented by the
* 'hellojni' native library, which is packaged
* with this application.
*/
public native String stringFromJNI(); //宣告使用JNI過來的函式的時候要加上「native」。
}
參考文件:
http://tools.android.com/tech-docs/new-build-system/gradle-experimental
https://github.com/googlesamples/android-ndk
http://qiita.com/eaglesakura/items/c4af7989b03904d66ebe
http://ph0b.com/new-android-studio-ndk-support/
http://www.slideshare.net/ph0b/mastering-the-ndk-with-android-studio-and-the-gradleexperimental-plugin
補充:
後來做系統測試的時候,發現這樣的編譯環境在使用context.obtainStyledAttributes()之後導致
NoClassDefFoundError: [package name].R$styleable 出現。看來這個編譯環境似乎不太穩定,有必要把所使用的app的所有功能都做過較詳細的測試,以策安全。
16/01/06 補充:
目前的gradle-experimental最新版本為0.6.0-alpha1。前一版是0.4。
對應的gradle distributon分別為2.9跟2.8。
需要用新版的話,請對應做修改。
=====================0.4.0==========================
distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip
=====================0.6.0-alpha1==========================
classpath 'com.android.tools.build:gradle-experimental:0.6.0-alpha1'
distributionUrl=https\://services.gradle.org/distributions/gradle-2.9-all.zip
使用jdk7來編譯是不會卡死了,但是之前使用
android.packagingOptions {
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
解決apache 裡面的幾個jar的duplicatefileexception的方法變成無效。
目前尚無解決方案。(猜測是不打算修了要硬逼大家回去用httpurlConnection)
另外一個衝擊是,gradle script的“+="指令失效。
proguardFiles += file('proguard-rules.pro')
必須修正為proguardFiles.add(file('proguard-rules.pro'))
16/03/29 補充:
切記,函式宣告有回傳資料的話,一定要回傳資料。不回傳的話,編譯器不會告訴你錯誤,只有在執行的時候會送你SIGABRT 6...