AndroidNDK使用简介
Posted 轩辕223
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AndroidNDK使用简介相关的知识,希望对你有一定的参考价值。
例行推广一下我的博客http://zwgeek.com
今天我们来简单说一下android NDK的使用方法。众所周知,so文件在Android的开发过程中起到了很重要的作用,无论与底层设备打交道还是在Android安全领域。so文件都格外受人青睐。NDK就是Android发布的用于编译so文件的一套工具,
引用自百度百科的一段解释
Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK”。
众所周知,Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C / C++之类的原生代码语言执行部分程序。
NDK包括了:
- 从C / C++生成原生代码库所需要的工具和build files。
- 将一致的原生库嵌入可以在Android设备上部署的应用程序包文件(application packages files ,即.apk文件)中。
- 支持所有未来Android平台的一系列原生系统头文件和库
为何要用到NDK?
概括来说主要分为以下几种情况:
- 代码的保护,由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。
- 在NDK中调用第三方C/C++库,因为大部分的开源库都是用C/C++代码编写的。
- 便于移植,用C/C++写的库可以方便在其他的嵌入式平台上再次使用。
本文从以下三个方面讲解NDK的使用
- 直接在命令行中用NDK进行编译
- Android Studio2.2以前对NDK的支持
- Android Studio2.2及以后对NDK的支持
直接在命令行使用NDK
NDK本来就是一套编译工具,自然是在命令行中执行,其实后面两种方法都是对这种方法的自动化处理,万变不离其宗, 要理解后面两种方法,还是应该熟悉一下不借助任何工具时的操作。
SDK默认是不带NDK的,所以NDK需要额外下载,下载后还需要配置环境变量。具体方法可以查看百度,配置环境变量很简单,只需要把NDK根目录,也就是ndk-build所在的目录加入环境变量即可。
用NDK-BUILD构建一个NDK程序,我们知道就是将C文件编译成so文件,其实原理很简单,用gcc进行编译。哦,因为我是mac环境,所以自带GCC编译环境,如果是windows下的话,还需要安装Cygwin环境来模拟linux,不过听说最新的NDK自带Cygwin,所以不再需要额外安装,Windows的同学可以试一下,有问题可以在评论区提问, 有机会我会补充Win下的使用方法。
编译c程序需要makefile,其实简单说就是告诉GCC怎么编译,先编什么在编什么,需要哪些包等等。这个熟悉c的同学应该知道的。一个简单的so项目包含以下四个文件。
除了.h和.c文件,还有两个makefile,Application.mk是项目makefile,它会指定调用哪个子makefile,然后Android.mk是具体执行操作的makefile。Application.mk的名字不能变,因为NDK会默认去找这个文件,后面也会讲到,Android.mk的名字可以变,是配置在Application.mk中的。
然后NDK还有一些规定,看.h文件的名字,c文件中的方法与java中某个方法是一一对应的,出于安全考量,NDK要求C中的方法名应该以对应java文件的包名+类名+方法名来命名。
头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_zzw_helloworld_JniUtils */
#ifndef _Included_com_example_zzw_helloworld_JniUtils
#define _Included_com_example_zzw_helloworld_JniUtils
#ifdef __cplusplus
extern "C"
#endif
/*
* Class: com_example_zzw_helloworld_JniUtils
* Method: stringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_zzw_helloworld_JniUtils_stringFromJNI
(JNIEnv *, jobject);
#ifdef __cplusplus
#endif
#endif
这个头文件很简单,就声明了一个方法,这两个参数是固定的,这个方法在java中的表现形式为stringFromJNI()
返回类型对应的是java中的string。
然后创建C文件,实现该方法。
//
// Created by zzw on 16/10/11.
//
#include "com_example_zzw_helloworld_JniUtils.h"
JNIEXPORT jstring JNICALL Java_com_example_zzw_helloworld_JniUtils_stringFromJNI
(JNIEnv *env, jobject instance)
return (*env)->NewStringUTF(env, "Hello from JNI !");
然后我们还需要两个make文件,一个是Application.mk另一个是Android.mk
Application.mk内容如下:
APP_BUILD_SCRIPT := /Users/zzw/Desktop/jni/Android.mk
其实就是声明Android.mk的位置
Android.mk如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
然后运行
./ndk-build NDK_PROJECT_PATH=/Users/zzw/Desktop
指定PROJECT的位置,然后NDK会自动寻找该路径下的jni文件夹中的Application.mk,然后去编译。
如果编译成功,则会在该文件夹下生成libs文件夹,里面就是各种架构下的so文件
生成的文件放在Android的libs中,对应的java文件调用方法为
package com.example.zzw.helloworld;
/**
* Created by zzw on 16/10/11.
*/
public class JniUtils
static
System.loadLibrary("hello-jni");
public static native String stringFromJNI();
public static String stringFromJNINative()
return stringFromJNI();
先用loadLibrary引入so文件,然后用native声明底层方法,然后我们就可以在程序中调用方法了。
这种方法完全没有借助任何的IDE,不过你也能看出来,有些工作是可以简化的,下面我们就说下在IDE中的做法。
Android Studio2.2 之前使用NDK
老的Android Studio支持NDK的方式可以根据java类帮我们生成头文件,然后编译过程可以写在gradle中,而不需要先编出so,再编android这样,具体过程如下
先写一个Java类,因为我们可以用jni工具根据java类来生成头文件,java类载入so文件并且声明底层方法,这个和前面一样,如下
package com.example.zzw.helloworld;
/**
* Created by zzw on 16/10/11.
*/
public class JniUtils
static
System.loadLibrary("hello-jni");
public static native String stringFromJNI();
public static String stringFromJNINative()
return stringFromJNI();
然后编译程序,注意只编译不运行,因为此时运行会报错,编译后,在build的文件夹下面能看到这个class文件
接下来我们可以用jni工具来根据这个class文件自动生成头文件了。命令行cd到build文件夹一层,然后执行如下命令
cd app/build/intermediates/classes/debug/
javah -jni com.example.zzw.helloworld.JniUtils
完整路径的类名,如果这一步报错,可以检查一下NDK有没有配到环境变量里。方法可以自行百度。
如果成功的话,你应该能看到在build文件夹下生成了一个.h文件,如下
把这个文件拷贝到main下面jni文件夹下,这个文件夹也不是固定的,可以配置。但是我们一般习惯于放在这个地方。
复制过来之后这就是我们的头文件,然后我们可以创建一个c文件,实现头文件的方法
文件内容如下
#include "com_example_zzw_helloworld_JniUtils.h"
JNIEXPORT jstring JNICALL Java_com_example_zzw_helloworld_JniUtils_stringFromJNI
(JNIEnv *env, jobject instance)
return (*env)->NewStringUTF(env, "Hello from JNI !");
build.gradle中需要配置ndk
然后点击运行。
遇到的问题,
报错
Error:Execution failed for task ':app:compileDebugNdk'.
> 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 "$USE_DEPRECATED_NDK=true" in gradle.properties to continue using the current NDK integration.
这是因为gradle插件版本太高,已经不支持这个方法了,它提示我们在gradle.properties里面加一句话,但经过我测试,那句话是不对的,应该加如下一句:
android.useDeprecatedNdk=true
然后运行,正常。
Android Studio2.2之后使用NDK
前面我们看到其实比起第一种方法也没有简化多少,所以在Studio2.2的时候google又尝试简化了做法,Android Studio 2.2开始支持用內建的方法来执行复杂的NDK编译,这意味着开发者只需要写好c文件,其他所有的编译,链接都可以交给系统去做。
注意:gradle版本需要2.2及以上
这个特性的实现要依赖于一个build标签,叫externalNativeBuild。标签配置如下
defaultConfig
externalNativeBuild
ndkBuild
arguments "NDK_LIBS_OUT=$jniLibsDir", "-j$numProcs", "all"
abiFilters "armeabi-v7a"
externalNativeBuild
ndkBuild
path "src/main/jni/Android.mk"
arguments指定编译的参数,abiFilters指定编译的平台,这些参数都可以省略以使用默认参数。下面path指定make文件的位置。
之后,NDK执行的task配置如下
task ndkBuild(type: Exec)
commandLine getNdkBuildCmd(),
'-C', file('src/main/jni').absolutePath,
'-j', Runtime.runtime.availableProcessors(),
"NDK_LIBS_OUT=$jniLibsDir",
'all',
'NDK_DEBUG=1'
dependsOn 'generateLuaBytecodes'
doFirst
println '== ndkBuild =='
接下来让我们来看一下,具体的实现步骤是什么样的,不需要像以前一样自己写头文件,然后再编译,现在只需要关注c文件即可。以hello world为例,我们写一个C文件如下:
#include <string.h>
#include <jni.h>
JNIEXPORT jstring JNICALL
Java_com_example_zzw_helloworld_MainActivity_stringFromJNI(JNIEnv *env, jobject instance)
return (*env)->NewStringUTF(env, "Hello from JNI !");
注意方法名要以调用它的JAVA文件的包名+类名+方法名命名。
这样写完之后,我们就可以在相应的JAVA文件中调用了,代码如下:
package com.example.zzw.helloworld;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity
static
System.loadLibrary("hello-jni");
public native String stringFromJNI();
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.out.println(stringFromJNI());
先用loadLibrary引入so文件,然后用native声明底层方法,然后我们就可以在程序中调用方法了。
当然前面提到了make文件,我们要创建一个Android.mk文件在externalNativeBuild中声明的位置,如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
之后,make project,运行程序应该就能看的效果了,通过这种方法生成的so文件放在app/.externalNativeBuild/debug/obj/local/下,并以lib+类名命名文件,如下:
感觉现在应该很简单了,只需要关注方法实现就可以了,其他基本都不要开发者关心了。
喜欢这篇文章的朋友,可以关注我的博客http://zwgeek.com
以上是关于AndroidNDK使用简介的主要内容,如果未能解决你的问题,请参考以下文章
使用ActiveSheet.Range时运行时错误13“类型不匹配”