使用AndroidStudio編譯NDK的方法及錯誤解決方案

參考資料:
【android ndk】macos環境下Android Studio中利用gradle編譯jni模塊及配置: http://demo.netfoucs.com/ashqal/article/details/21869151
ANDROID STUDIO, GRADLE AND NDK INTEGRATION: http://ph0b.com/android-studio-gradle-and-ndk-integration/
 
實踐證實:
0.4.2只有在gradle1.10版本下建立只包含AndroidLibrary模塊的工程時才能正常編譯,gradle1.9版本不能夠。
0.4.6使用gradle1.10能夠。
0.5.0不管是gradle1.10仍是gradle1.11版本均可以生成so庫。
0.5.5的不能編譯NDK,不管是gradle1.10仍是gradle1.11版本都不能生成so庫,屙血尿膿。
 
下載AndroidStudio
AndroidStudio的歷史版本下載列表: http://tools.android.com/download/studio/canary
 
下載NDK:
下載連接: http://developer.android.com/tools/sdk/ndk/index.html,注意NDK必定要r9+版本的,不然編譯時會出現以下 錯誤:
Execution failed for task ':hellojni:compileDebugNdk'.
> com.android.ide.common.internal.LoggedErrorException: Failed to run command:
    D:\ndk\ndk-build.cmd NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=F:\androidstudio\test\hellojni\build\ndk\debug\Android.mk APP_PLATFORM=android-19 NDK_OUT=F:\androidstudio\test\hellojni\build\ndk\debug\obj NDK_LIBS_OUT=F:\androidstudio\test\hellojni\build\ndk\debug\lib APP_ABI=armeabi,armeabi-v7a
Error Code:
    2
Output:
    D:/ndk/build/core/setup-app.mk:63: *** Android NDK: Aborting    .  Stop.
下載gradle:
 
 
經過「AndroidStudio歷史版本下載列表」下載的歷史版本一般是綠色的壓縮包,能夠直接解壓縮使用,可是不包含SDK,須要額外下載SDK,因爲以前下載了ADT(版本:adt20131030),因此後面直接使用ADT目錄下的SDK。經過 http://developer.android.com/sdk/installing/studio.html首頁下載的AndroidStudio爲安裝版本,包含了SDK,能夠下載後直接安裝,首次使用建立項目會比較慢,能夠參考「 AndroidStudio建立項目時一直處於building「project name」gradle project info的解決辦法」來解決。
 
建立項目:
運行AndroidStudio後,建立新項目,新項目會有一個默認的Module,這裏項目名稱爲JNIDemo,Module爲app。
 
而後經過嚮導完成項目的建立。
 
AndroidStudio仍是很是慢的,長時間處於這種狀態:
通過漫長的等待後終於完成項目的建立,而後在這個項目下建立一個Module,New Module->Android Library:
 
不勾選「Create activity」而後點擊「Finish」完成建立,此時項目結構如圖:
app和hellojni均爲JNIDemo下的兩個Module,這裏把hellojni做爲生成so庫的NDK開發層,把app做爲調用so庫的APK引用開發層。
 
在hellojni模塊的src/main下建立jni目錄,並在jni目錄下新建文件main.cpp,代碼以下:
#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <assert.h>
#include <sys/types.h>
#include <android/log.h>

#define LOG_TAG "Hellojni"
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)


//註冊native api的類#define JNIREG_CLASS "com/example/test9/app/MainActivity"

extern "C" {
    JNIEXPORT void msg(JNIEnv *env, jobject  clazz, jstring str);
};


//jstring to char* char* jstringTostring(JNIEnv* env, jstring jstr) 
{ 
    char* rtn = NULL; 
    jclass clsstring = env->FindClass("java/lang/String"); 
    jstring strencode = env->NewStringUTF("utf-8"); 
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B"); 
    jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode); 
    jsize alen = env->GetArrayLength(barr); 
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE); 
    if (alen > 0) 
    { 
        rtn = (char*)malloc(alen + 1); 
        memcpy(rtn, ba, alen); 
        rtn[alen] = 0; 
    } 
    env->ReleaseByteArrayElements(barr, ba, 0); 
    return rtn; 
}

JNIEXPORT void msg(JNIEnv *env, jobject  clazz, jstring str)
{
    char *pszstr = NULL; 

    pszstr = jstringTostring(env, str);
    LOGI("%s", pszstr);
    free(pszstr);
}

/**
* Table of methods associated with a single class.
*/static JNINativeMethod gMethods[] = {
    { "msg", "(Ljava/lang/String;)V", (void*)msg},
};

/*
* Register native methods for all classes we know about.
*/static int registerNativeMethods(JNIEnv* env)
{
    int nError = 0;
    jclass clazz = NULL;

    clazz = env->FindClass(JNIREG_CLASS);
    if (clazz == NULL) {
        LOGE("clazz is null");
        return JNI_FALSE;
    }

    nError = env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]) );
    if ( nError < 0 ) {
        LOGE("RegisterNatives error: %d num: %d",nError, sizeof(gMethods) / sizeof(gMethods[0]) );
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

/*
* Set some test stuff up.
*
* Returns the JNI version on success, -1 on failure.
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if(vm->GetEnv((void**) &env,JNI_VERSION_1_6) != JNI_OK){
        return -1;
    }
    assert(env != NULL);

    if (!registerNativeMethods(env)) {
        LOGE("registerNativeMethods failed");
        return -1;
    }

    /* success -- return valid version number */
    result = JNI_VERSION_1_6;

    return result;
}
這裏只導出一個msg函數打印傳遞進來的字符串,僅做測試。再在jni目錄下新建一個empty.cpp文件,內容爲空,這個是爲了解決NDK的bug所做的,以防編譯出錯。
 
打開local.properties,設置正確的SDK路徑和NDK路徑:
sdk.dir=D\:/adt20131030/sdk
ndk.dir=D\:/ndk
打開項目gradle/wrapper目錄下的gradle-wrapper.properties文件,修改:
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-all.zip
爲:
#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip
並打開項目根目錄下的build.gradle文件,修改:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.7.+'
    }
}

allprojects {
    repositories {
        mavenCentral()
    }
}
爲(指定使用gradle1.10則修改成0.9.+,指定使用gradle1.11則修改成0.9.2):
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.9.+'
    }
}

allprojects {
    repositories {
        mavenCentral()
    }
}
解釋:參考 http://tools.android.com/tech-docs/new-build-system知道
0.7.0
Requires Gradle 1.9
Requires Studio 0.4.0
0.9.0
Compatible with Gradle 1.10 and 1.11
Using Gradle 1.11 requires Android Studio 0.5.0
若是配置的是0.7.+則默認使用gradle1.9,若是設置爲0.9.+則默認使用gradle1.10。
 
另外還須要注意的是gradle1.9下沒有buildTypes標籤,須要將debug、release標籤直接放在android標籤內,在gradle1.10下debug、release須要放在buildTypes標籤內,buildTypes在android內。這裏hellojni配置的build.gradle文件內容以下:
assert gradle.gradleVersion >= "1.10"

apply plugin: 'android-library'

android {
    compileSdkVersion 19
    buildToolsVersion "19.0.3"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 16
        versionCode 1
        versionName "1.0"
    }

    buildTypes {
        release {
            runProguard false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
            ndk {
                moduleName "hellojni"
                abiFilters "armeabi", "armeabi-v7a", "x86"
            }
        }

        debug {
            ndk {
                moduleName "hellojni"
                //stl "stlport_shared"
                ldLibs "log", "z", "m"
                //cFlags "-Wall -Wextra -I " + projectDir + "/src/main/jni/include"
                abiFilters "armeabi", "armeabi-v7a", "x86"
            }
        }
    }

    productFlavors {
        x86 {
            versionCode Integer.parseInt("6" + defaultConfig.versionCode)
            ndk {
                abiFilter "x86"
            }
        }
        mips {
            versionCode Integer.parseInt("4" + defaultConfig.versionCode)
            ndk {
                abiFilter "mips"
            }
        }
        armv7 {
            versionCode Integer.parseInt("2" + defaultConfig.versionCode)
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
        arm {
            versionCode Integer.parseInt("1" + defaultConfig.versionCode)
            ndk {
                abiFilters "armeabi", "armeabi-v7a"
            }
        }
        fat
    }
}

dependencies {
    compile 'com.android.support:appcompat-v7:19.+'
    compile fileTree(dir: 'libs', include: ['*.jar'])
}
而後選擇hellojni項目右鍵「Make Module hellojni」,等待一段時間後會在項目下生成build-ndk目錄,目錄下會有一些不一樣版本的so庫文件生成,如圖:
注意這裏的Android.mk文件每次編譯都會從新由工具自動生成,而非手動編輯的,我以爲這一點設計就比較差勁。例如若是想要使用log輸出函數__android_log_print,須要添加「LOCAL_LDLIBS :=  -llog」,則在build.gradle文件中添加以下的配置:
        debug {
            ndk {
                ldLibs "log"
            }
        }
由gradle根據配置再去生成Android.mk文件,最後再調用ndk進行編譯。
 
 
右鍵工程選擇Open Module Settings,選擇Modules-app,打開Dependencies選項卡點擊「+」號,選擇Module dependency,在打開的對話框中選擇hellojni。
 
可是測試發現設置依賴沒有效果,若是直接編譯app,hellojni並無編譯,仍須要手動編譯hellojni。
 
 
調用native函數:
 
app項目中,在MainActivity類中聲明native函數:
public native void msg(String str);
並添加靜態代碼加載hellojni庫:
    static {
        System.loadLibrary("hellojni");
    }
在MainActivity::onCreate中調用native函數打印一句log:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        msg("MainActivity onCreate");
    }
 
還須要將hellojni生成的so庫文件打包進apk,仍須要配置build.gradle文件,添加:
task copyNativeLibs(type: Copy) {
    from fileTree(dir: '../hellojni/build/ndk/arm/debug/lib', include: 'armeabi/*.so') into 'build/lib'
}
tasks.withType(Compile) {
    compileTask -> compileTask.dependsOn copyNativeLibs
}
clean.dependsOn 'cleanCopyNativeLibs'
tasks.withType(com.android.build.gradle.tasks.PackageApplication) { pkgTask ->
    pkgTask.jniFolders = [new File(buildDir, 'lib')]
}
參考:「Android Studio添加so庫」 http://blog.csdn.net/caesardadi/article/details/18264399
其中copyNativeLibs任務是從相對app的項目路徑'../hellojni/build/ndk/arm/debug/lib'下複製全部armeabi子目錄的so文件到本項目build目錄下的lib目錄中,執行效果:
這樣最後打包生成的apk包纔會包含有hellojni的so庫文件。
 
 
測試:
 
編譯運行app,apk安裝完畢運行時輸出log信息:
 
 
後面列出了可能出現的gradle錯誤以及解決方案,以供參考。
 
錯誤:
Execution failed for task ':hellojni:compileDebugNdk'.
> com.android.ide.common.internal.LoggedErrorException: Failed to run command:
    D:\ndk\ndk-build.cmd NDK_PROJECT_PATH=null APP_BUILD_SCRIPT=F:\androidstudio\test\hellojni\build\ndk\debug\Android.mk APP_PLATFORM=android-19 NDK_OUT=F:\androidstudio\test\hellojni\build\ndk\debug\obj NDK_LIBS_OUT=F:\androidstudio\test\hellojni\build\ndk\debug\lib APP_ABI=armeabi,armeabi-v7a
Error Code:
    2
Output:
    make.exe: *** No rule to make target `F:\androidstudio\test\hellojni\build\ndk\debug\obj/local/armeabi/objs/jnimain/F_\androidstudio\test\hellojni\src\main\jni', needed by `F:\androidstudio\test\hellojni\build\ndk\debug\obj/local/armeabi/objs/jnimain/F_\androidstudio\test\hellojni\src\main\jni\hellojni.o'.  Stop.
解決方案:
這是NDK在Windows下一個bug,當只編譯一個文件時出現,解決方法就是再添加一個空的文件便可。
This may come from a current NDK bug on Windows, when there is only one source file to compile. You only need to add one empty source to make it work again.
 
錯誤:
Could not determine the dependencies of task ':hellojni:compileArmDebugJava'.
> failed to find Build Tools revision 19.0.3
解決方案:
這個Build Tools是指「Android SDK Build-tools」,打開SDK Manager勾選相應版本(例如這裏是19.0.3)安裝便可。



 
錯誤:
FAILURE: Build failed with an exception.

* What went wrong:
Task 'assembleArmDebug' not found in project ':hellojni'. Some candidates are: 'assembleDebug'.

* Try:
Run gradle tasks to get a list of available tasks. Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
解決方案
android { }中添加:
    productFlavors{
        arm {
        }
    }
如有相似錯誤能夠參考加入相應的標籤:
    productFlavors {
        x86 {
            versionCode Integer.parseInt("6" + defaultConfig.versionCode)
            ndk {
                abiFilter "x86"
            }
        }
        mips {
            versionCode Integer.parseInt("4" + defaultConfig.versionCode)
            ndk {
                abiFilter "mips"
            }
        }
        armv7 {
            versionCode Integer.parseInt("2" + defaultConfig.versionCode)
            ndk {
                abiFilter "armeabi-v7a"
            }
        }
        arm {
            versionCode Integer.parseInt("1" + defaultConfig.versionCode)
            ndk {
                abiFilter "armeabi"
                //abiFilters "armeabi", "armeabi-v7a"
            }
        }
        fat
    }
 
錯誤:
Execution failed for task ':hellojni:compileDebugNdk'.
> java.io.IOException: Cannot run program "D:\ndk\ndk-build": CreateProcess error=193, %1 ??????Ч?? Win32 ??ó
解決方案:
在使用gradle1.9版本時遇到,使用gradle1.10版原本解決。
 
 
錯誤:
A problem occurred evaluating project ':app'.
> Could not create plugin of type 'AppPlugin'.
解決方案:
Don’t use latest Gradle (version 1.10), downgrade to 1.9。參考: http://blog.vyvazil.eu/tag/android-studio/
可是若是咱們使用gradle1.9版本的話又會出現 錯誤:
Execution failed for task ':hellojni:compileDebugNdk'.
> java.io.IOException: Cannot run program "D:\ndk\ndk-build": CreateProcess error=193, %1 ??????Ч?? Win32 ??ó
不管使用哪一個版本都有問題,後來仔細查看了下'AppPlugin'這個錯誤是出如今‘app’模塊上的而非‘hellojni’模塊上,因而考慮新建工程項目而且只在該工程下創建一個庫模塊,再也不建立app模塊,如圖:
 
這裏不勾選「Create custom launcher icon」和「Create activity」,直接finish完成,其餘配置參考前述,最後編譯後能夠生成so庫文件:
 
 
錯誤:
這個錯誤忘記記錄了囧
 
解決方案:
File-Settings-Gradle-Gradle VM options:-Xmx512m
相關文章
相關標籤/搜索