V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
banxi1988
V2EX  ›  Android

Android Studio 基于 NDK 的开发初体验

  •  
  •   banxi1988 · 2015-10-22 21:53:19 +08:00 · 9807 次点击
    这是一个创建于 3321 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在接触 Dex 底层操作时,了解了下 NDK 的开发。
    虽然 Android Studio 对于 NDK 开发还是实验性的支持。但是对于我个人入门来说。
    我感觉比基于 Eclipse 的 NDK 开发方便很多。
    但是当我后面用 JNI 实现的一个 sayHi 的方法之后。
    我竟然心里突然觉得 Java 这个语言真是太方便好用了。

    项目创建与配置

    0x0 下载 安装 NDK SDK

    NDK Downloads

    0x1 创建一个新的空白的 Application

    然后修改 项目目录的 local.properties 添加 NDK-SDK 的路径配置。
    根据安装的位置自行修改路径,如下:
    ndk.dir=/Users/youjianame/android-ndk-r10e

    0x2 更新 gradle 版本到 2.5 .

    0x3 使用支持 NDK 的 gradle 编译插件

    修改项目目录下的 build.gradle 编译插件路径,由
    classpath com.android.tools.build:gradle:1.3.0
    改为
    classpath com.android.tools.build:gradle-experimental:0.2.1

    0x4. 修改 app/build.gradle

    应用的 DSL 需要的修改会比较大。
    由原来生成的:

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 23
        buildToolsVersion "23.0.1"
    
        defaultConfig {
            applicationId "com.banxi1988.ndk_jni_helloworld"
            minSdkVersion 16
            targetSdkVersion 23
            versionCode 1
            versionName "1.0"
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        testCompile 'junit:junit:4.12'
        compile 'com.android.support:appcompat-v7:23.1.0'
    }
    

    修改为如下:

    apply plugin: 'com.android.model.application'
    
    model{
        android {
            compileSdkVersion = 23
            buildToolsVersion = "23.0.1"
    
            defaultConfig.with{
                applicationId = "com.banxi1988.ndk_jni_helloworld"
                minSdkVersion.apiLevel = 16
                targetSdkVersion.apiLevel = 23
                versionCode =  1
                versionName = "1.0"
            }
            compileOptions.with{
                sourceCompatibility = JavaVersion.VERSION_1_7
                targetCompatibility = JavaVersion.VERSION_1_7
            }
        }
    
        compileOptions.with {
            sourceCompatibility = JavaVersion.VERSION_1_7
            targetCompatibility = JavaVersion.VERSION_1_7
        }
    
        android.buildTypes {
            release {
                minifyEnabled =  false
                proguardFiles += file('proguard-rules.pro')
            }
        }
        android.ndk{
            moduleName = "hello-jni"
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile 'com.android.support:appcompat-v7:23.1.0'
    }
    

    编写 Native 代码

    0x1 在 Java 类中编写静态方法声明,让 Studio 帮助生成 Native 代码。

    在应用主包下创建一个 名为 NativeGreetings的类
    如下:

    package com.banxi1988.ndk_jni_helloworld;
    public class NativeGreetings {
        public native String helloWorld();
    }
    

    此时将光标停留在 helloWorld 方法名上面,左右会出现一个修复错误的提示。
    点击小灯泡之后就会弹出如下修复菜单:
    第一个菜单项即是:
    Create function Java_com_banxi1988_ndk_1jni_1helloworld_NativeGreetings_helloWorld
    选择创建之后就会自动为我们创建相应的 C 函数文件及对应的方法。
    为我们创建了如下的文件 app/src/main/jni/nativegreetings.c

    #include <jni.h>
    
    JNIEXPORT jstring JNICALL
    Java_com_banxi1988_ndk_1jni_1helloworld_NativeGreetings_helloWorld(JNIEnv *env, jobject instance) {
        return (*env)->NewStringUTF(env, returnValue);
    }
    

    0x2 返回 Hello World 字符串

    下面只是简单的实现如下:

    JNIEXPORT jstring JNICALL
    Java_com_banxi1988_ndk_1jni_1helloworld_NativeGreetings_helloWorld(JNIEnv *env, jobject instance) {
        return (*env)->NewStringUTF(env, "A Hello World Greeting  From JNI! ");
    }
    

    0x3 在 NativeGreetings 类的静态区加载相应的静态原生库

    将 NativeGreetings 类修改如下。增加一个 static 区,在其中加载静态链接库。
    库名称即是我们在 android.ndk 中配置的 moduleName

    public class NativeGreetings {
        public native String helloWorld();
    
        static{
            System.loadLibrary("hello-jni");
        }
    }
    

    0x4 修改一下我们的应用以使用此原生方法:

    NativeGreetings greetings = new NativeGreetings();
    TextView greetingsView = (TextView) findViewById(R.id.greetings);
    greetingsView.setText(greetings.helloWorld());
    

    运行即可看到效果了。

    一个复杂一点 JNI 代码示例

    在 Java 代码中添加一个 sayHi的方法声明
    同时添加一模型字段。如下:

    public native String sayHi();
    
        public String firstName;
        public String lastName;
    
        public NativeGreetings(String firstName,String lastName){
            this.firstName = firstName;
            this.lastName = lastName;
        }
    

    这个 sayHi 想要实现的是类似下面的效果:

    public String sayHi(){
       return "Hi, "+this.firstName+" "+this.lastName;
    }
    

    下面是对应的 JNI 代码:

    JNIEXPORT jstring JNICALL
    Java_com_banxi1988_ndk_1jni_1helloworld_NativeGreetings_sayHi(JNIEnv *env, jobject instance) {
    
        jclass  class = (*env)->GetObjectClass(env,instance);
    
        jfieldID firstNameID = (*env)->GetFieldID(env,class,"firstName","Ljava/lang/String;");
        jfieldID lastNameID = (*env)->GetFieldID(env,class,"lastName","Ljava/lang/String;");
    
        jstring firstName = (*env)->GetObjectField(env,instance,firstNameID);
        jstring lastName = (*env)->GetObjectField(env,instance,lastNameID);
    
        jbyte *firstNameBytes = (*env)->GetStringUTFChars(env,firstName,NULL);
        jbyte *lastNameBytes = (*env)->GetStringUTFChars(env,lastName,NULL);
        char * hi = "Hi, ";
        char * greetingChars = malloc(strlen(hi) + strlen(firstNameBytes) + strlen(" ") +strlen(lastNameBytes) + 1);
        strcpy(greetingChars,hi);
        strcat(greetingChars,firstNameBytes);
        strcat(greetingChars," ");
        strcat(greetingChars,lastNameBytes);
    
        jstring greeting = (*env)->NewStringUTF(env,greetingChars);
        (*env)->ReleaseStringUTFChars(env,firstName,firstNameBytes);
        (*env)->ReleaseStringUTFChars(env,lastName,lastNameBytes);
        free(greetingChars);
        return greeting;
    }
    
    4 条回复    2015-10-23 09:08:55 +08:00
    ylqhust
        1
    ylqhust  
       2015-10-22 22:49:29 +08:00
    楼主有没有源码分享一下
    jukka
        2
    jukka  
       2015-10-22 23:19:03 +08:00
    不用去纠结 android studio, ndk-build , android.mk 走起。
    ybjaychou
        3
    ybjaychou  
       2015-10-23 00:47:33 +08:00 via Android
    我也是这几天在搞一个串口通讯的,结果老是 tcsetattr 函数报错,后来搞了好久原来是编译的时候需要把版本设置成跟目标机器一样的 SDK 版本才行……之前是直接在 mk 里面写的,现在好像是根据 build.gradle 里面的编译版本来的…
    r00tt
        4
    r00tt  
       2015-10-23 09:08:55 +08:00
    还是喜欢直接写好代码,写好 android.mk 然后 ndk-build
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1674 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 20ms · UTC 16:34 · PVG 00:34 · LAX 08:34 · JFK 11:34
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.