NDK学习笔记(一)

NDK学习笔记(一)

我们都知道Android应用是用Java写的,但是有时候我们需要使用C/C++来进行开发,或者说需要与底层进行一些交互。Java本身提供了JNI方法,但是在Android开发中其存在技术障碍。比如程序更加复杂,兼容性难以保障,无法访问Framework API,Debug难度更大等。于是于是NDK就应运而生了。NDK全称是Native Development Kit。NDK的发布,使“Java+C”的开发方式终于转正,成为官方支持的开发方式。NDK将是Android平台支持C开发的开端。

使用NDK的优点

  1. 便于移植,用C/C++写得库可以方便在其他的平台上再次使用。
  2. 代码的保护,由于java层代码很容易被反编译,而C/C++库反编译难度较大。
  3. 提高程序的执行效率,将要求高性能的应用逻辑使用C/C++开发,从而提高应用程序的执行效率。
  4. 访问现有开源库,需要访问底层的API或引用一些只有C/C++的库。

配置NDK环境

如果你已经下载了NDK,那么就可以设置其路径,在File->Project Structure里的Android NDK location,默认为你的SDK路径\ndk-bundle。如果没有下载,直接点击download下载。当然你也可以
自己下载其它版本放置任意位置,然后手动设置路径。
设置成功的话,会在Project的local.properties文件下添加NDK的路径:

ndk.dir=D\:\\Android\\Sdk\\ndk-bundle

如果没有,可手动添加。另在Build时,如果出现错误如下:

Error:(12, 0) Error: NDK integration is deprecated in the current plugin.  
Consider trying the new experimental plugin.  
For details, see http://tools.android.com/tech-docs/new-build-system/gradle-experimental.  
Set "android.useDeprecatedNdk=true" in gradle.properties to continue using the current NDK integration.

是由于你的NDK版本与AS版本不一致造成的,只需在gradle.properties文件下添加一行:android.useDeprecatedNdk=true即可。
(如果你想使用命令行的话,需要添加系统环境变量,新建变量名ANDROID_NDK_HOME,对应变量值为ndk根目录地址 ,即$SDKpath\ndk-bundle,然后将ANDROID_NDK_HOME添加到Path中去(%ANDROID_NDK_HOME%;),确定OK.)

自己编写Native文件

好了,现在我们可以来编写自己的第一个NDK应用了。最简单的例子,C输出一个字符串,然后在Android里调用显示出来。首先还是跟平常一样,New一个Project。接着New一个Java Class,我们将其命名为JniUtils。我们将在这里加载动态库,创建native方法。(其实也可以在MainActivity里做这些,但是为了让代码看起来更清晰,我们还是选择分开写。另外你也可以不在app Module里做这些,而是放在一个独立的module里。)
先创建一个native方法,即JNI接口,它是衔接C/C++与Java之间的桥梁。

public class JniUtils {
    
    
    static{
        System.loadLibrary("ndk");
    }
    public static native String getStringFromC();
}

这个时候我们看到getStringFromC的颜色是红色的,那是因为没有找到这个方法的实现。接下来我们就要编写这个方法了。
如果默认是Android视图的话,调整至Project视图。在$ProjectName/app/src/main下,New一个Folder,类型为JNI Folder。这个目录下主要存放jni文件,即c/cpp文件以及头文件等。接下来创建C文件有两种方式,取决于你的gradle版本,我们分开说,比较一下:

稳定版插件

如果你的Project下的build.gradle类似这样:

  dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0'
    }

既是稳定版插件。
上一步完成后clean project 再rebuild project ,看看$ProjectName/app/build/intermediats/classes/是否存在,再打开Terminal输入指令:

cd app/build/intermediates/classes/debug

然后再输入指令:

javah -jni com.example.yaoobs.ndkjnidemo.JniUtils

发现classes/debug下多了一个com_example_yaoobs_ndkjnidemo_JniUtils.h。将其剪切至之前创建的JNI Folder下,New一个C/C++ Source File,命名为jniUtils.c。按照com_example_yaoobs_ndkjnidemo_JniUtils.h里面的方法名修改jniUtils.c

实验性插件(Gradle Experimental Plugin)

Gradle Experimental Plugin基于Gradle的新特性开发,目的在于减少项目配置时间,并提供更好的NDK支持。
(注意,这只是一个插件,需要gradle本身的支持,并且与其版本对应。笔者的gradle版本是2.1.0,对应的gradle-experimental是 0.7.0。)
使用插件,我们需要先在项目的build.gradle里修改gradle的引用:

buildscript {
    ...
    dependencies {
//        classpath 'com.android.tools.build:gradle:2.1.0'
        classpath 'com.android.tools.build:gradle-experimental:0.7.0'
    }
...
}

接着修改module下的build.gradle,这里有一些语法上的不同,旧的注释掉了,可以对比着看:

//apply plugin: 'com.android.application'
apply plugin: 'com.android.model.application'
//
//android {
    
    
//    compileSdkVersion 23
//    buildToolsVersion "23.0.2"
//    defaultConfig {
    
    
//        applicationId "com.example.yaoobs.ndkjnidemo"
//        minSdkVersion 14
//        targetSdkVersion 23
//        versionCode 1
//        versionName "1.0"
//        ndk {
    
    
//            moduleName = "ndk"
//            abiFilters 'armeabi', 'x86', 'armeabi-v7a', 'x86_64', 'arm64-v8a', 'mips', 'x86_64'
//        }
//    buildTypes {
    
    
//        release {
    
    
//            minifyEnabled false
//            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
//        }
//    }
//}

model {
    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.2"
        defaultConfig.with {
            applicationId = "com.example.yaoobs.ndkjnidemo"
            minSdkVersion.apiLevel = 14
            targetSdkVersion.apiLevel = 23
            versionCode = 1
            versionName = "1.0"
        }
        ndk {
            moduleName = "ndk"
            toolchain = 'clang'  //必加项,否则c文件报错
            CFlags.addAll(['-Wall'])
        }
        buildTypes {
            release {
                minifyEnabled = false
                proguardFiles.add(file('proguard-rules.txt'))
            }
        }
        productFlavors {
            create("arm") {
                ndk.abiFilters.add("armeabi")
            }
            create("arm7") {
                ndk.abiFilters.add("armeabi-v7a")
            }
            create("arm8") {
                ndk.abiFilters.add("arm64-v8a")
            }
            create("x86") {
                ndk.abiFilters.add("x86")
            }
            create("x86-64") {
                ndk.abiFilters.add("x86_64")
            }
            create("mips") {
                ndk.abiFilters.add("mips")
            }
            create("mips-64") {
                ndk.abiFilters.add("mips64")
            }
            create("all")
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha1'
    compile 'com.android.support:appcompat-v7:23.0.0'
}

完成后你就会发现JniUtils.class里的native方法getStringFromC(),按Alt+Enter会出现提示,如下图:
出现提示
回车,会发现JNI Folder下创建了一个jniutils.c文件。

编写Native文件

首先,添加头文件,类似java中的导包,这里首先必加的是jni.h,这是java与c/c++之间语言转换的核心文件,具体可以查看ndk目录下D:\android-sdk\ndk-bundle\platforms\android-23\arch-arm\usr\include\jni.h,另一个这里需要处理字符串,所以还需要包含string.h的头文件,同样可以在上面目录include中找到.
然后,添加java中native方法的实现。jstring:返回值类型,Java_com_example_yaoobs_HelloJni_stringFromJNI(JNIEnv *env, jobject jobj):实现的方法名,固定格式,Java_所要实现的方法名所在java类用下划线替代点的引用地址_方法名(JNI环境变量 env,JNI环境对象 jobj);其中env和jobj方法中可能用不到,但也必须申明,源码中是这样说明的,大概作用就是env作为了一个JNINativeInterface指针,是java与c/c++之间的一个功能环境变量中间桥梁,我们先不深入。
完整的jniutils.c:

#include <string.h>
#include <jni.h>

jstring
Java_com_example_yaoobs_ndkjnidemo_JniUtils_getStringFromC( JNIEnv* env, jobject thiz ) {

    return (*env)->NewStringUTF(env, "Hello NDK! ");
}

最后在Activity里调用:

public class MainActivity extends Activity {
    
    

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ((TextView)findViewById(R.id.txt)).setText(JniUtils.getStringFromC());
    }
}

运行成功:
运行成功

直接使用动态链接库

大部分情况下,我们使用NDK不需要自己编写Native文件,只要把动态链接库,即.so文件直接拿来用就好了。许多第三方SDK都提供了完整的解决方案。
AS中so文件存放的目录默认是在main/jniLibs下,不过我们还是习惯把so文件和jar包一样都放在app/libs下,不过需要在gradle中添加一项配置,如下:

android {
...
 sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
     }
...

这样在系统加载so的时候会去libs目录下加载。
接着刚才的例子,在app\build\intermediates\ndk\debug\lib下面,会发现各种架构的so文件,命名是以lib+modulename组成的。
你可以把它们全部复制到libs下面,然后删掉jniutils.c,看看运行会不会报错。

源码已上传GitHub,地址:https://github.com/Yaoobs/NdkJniDemo

参考文章:
http://blog.csdn.net/yilip/article/details/45200861
http://www.jianshu.com/p/9aff422204eb
http://www.jianshu.com/p/d8cde65cb4f7
http://www.taoweiji.cn/2016/08/02/ndk/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
http://www.cnblogs.com/zhuyuliang/p/5007016.html

猜你喜欢

转载自blog.csdn.net/Yaoobs/article/details/51365126
今日推荐