(4.1.27.2)使用AndroidStudio编译NDK的方法及错误解决方案

来源:互联网 发布:php有cdn获取客户端ip 编辑:程序博客网 时间:2024/05/17 23:48

创建项目:

运行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/sdkndk.dir=D\:/ndk
打开项目gradle/wrapper目录下的gradle-wrapper.properties文件,修改:
#Wed Apr 10 15:27:10 PDT 2013distributionBase=GRADLE_USER_HOMEdistributionPath=wrapper/distszipStoreBase=GRADLE_USER_HOMEzipStorePath=wrapper/distsdistributionUrl=http\://services.gradle.org/distributions/gradle-1.9-all.zip
为:
#Wed Apr 10 15:27:10 PDT 2013distributionBase=GRADLE_USER_HOMEdistributionPath=wrapper/distszipStoreBase=GRADLE_USER_HOMEzipStorePath=wrapper/distsdistributionUrl=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.0Requires Gradle 1.9Requires Studio 0.4.0

0.9.0Compatible with Gradle 1.10 and 1.11Using 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信息:



0 0