Android NDK开发技术与技巧总结与心得

嘛,好久不来了,忙成狗,不过收获很多,本文就是其中之一。

最近把JNI这玩意儿深度学习了下,之前虽然也做过,但都比较松散,没有系统的把整个知识框架和技术体系梳理过。网上也看了很多博文,基本说的都是环境配置然后一个Hello from jni的玩意儿,然后就没有然后了,基本很少有具体的应用以及对相应核心api的介绍,对其思想的介绍就更少了,所以想自己还是总结一篇出来,并不是为了装逼,就是怕过半年自己又全还给自己了(谁信啊)……

先来张葛大爷的图镇楼!

一、JNI到底是干嘛用的

百度都能查到的官方解释我就不多说了。我自己的理解是两方面,一方面主要用于各种复杂算法的执行,C的效率高自不必说,更重要的是so的破解难度要远远大于apk了,很多公司就靠算法活着的,没有JNI用他们要死了~;另一方面,JavaNativeInterface,作为Java和C间的bridge,我们可以把很多java代码可能无法实现的事情丢到C层去搞。

二、怎么跑起来一个带JNI代码的工程

我用的是AS2.1,主要学姿势,所以默认各位看官的环境已经折腾好,如果没折腾好百度一找一洗脸盆,学一下就好了。AS现在对NDK的支持还是很感人的low,不过编译很轻松,所以凑合着用吧。简单步骤总结下,不一样的同学不用喷,这不是重点:

1.写个类,写好自己需要的native方法,像这样:

package com.amuro.ndkcompactdemo.chapter_1;

/**
 * Created by Amuro on 2016/6/23.
 */
public class Chapter1JNI
{
    static
    {
        System.loadLibrary("ndk_compact");
    }

    public native String stringFromJNI();
    public native int add(int a, int b);
    public native String addString(String str);
    public native int[] addArray(int[] array);
}

2.写好之后,编译你的代码,保证通过。然后用AS自带的Termial进入工程根目录,我的根目录是这样的:

D:\DevelopAS\workspace\NDKCompactDemo

好,然后进入根目录的以下目录:

D:\DevelopAS\workspace\NDKCompactDemo\app\build\intermediates\classes\debug

然后输入命令javah -jni com.amuro.ndkcompactdemo.chapter_1.Chapter1JNI

就这样会在刚才那个目录下生成一个.h头文件。不要问我javah是什么也不要问我h文件是什么,自己百度。生成头文件代码如下:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_amuro_ndkcompactdemo_chapter_1_Chapter1JNI */

#ifndef _Included_com_amuro_ndkcompactdemo_chapter_1_Chapter1JNI
#define _Included_com_amuro_ndkcompactdemo_chapter_1_Chapter1JNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_amuro_ndkcompactdemo_chapter_1_Chapter1JNI
 * Method:    stringFromJNI
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_amuro_ndkcompactdemo_chapter_11_Chapter1JNI_stringFromJNI
  (JNIEnv *, jobject);

/*
 * Class:     com_amuro_ndkcompactdemo_chapter_1_Chapter1JNI
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_amuro_ndkcompactdemo_chapter_11_Chapter1JNI_add
  (JNIEnv *, jobject, jint, jint);

/*
 * Class:     com_amuro_ndkcompactdemo_chapter_1_Chapter1JNI
 * Method:    addString
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_amuro_ndkcompactdemo_chapter_11_Chapter1JNI_addString
  (JNIEnv *, jobject, jstring);

/*
 * Class:     com_amuro_ndkcompactdemo_chapter_1_Chapter1JNI
 * Method:    addArray
 * Signature: ([I)[I
 */
JNIEXPORT jintArray JNICALL Java_com_amuro_ndkcompactdemo_chapter_11_Chapter1JNI_addArray
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

3.在工程的src/main目录下创建一个jni文件夹,把刚才生成的h文件复制过来。

4.对Gradle进行配置

1)app module的build.gradle下增加ndk配置,完整gradle文件如下:

apply plugin: ‘com.android.application‘

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.amuro.ndkcompactdemo"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"

        ndk{
            moduleName "ndk_compact"
            ldLibs "log", "z", "m"  //添加依赖库文件,因为有log打印等
            abiFilters "armeabi", "armeabi-v7a", "x86"
        }
    }

    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.3.0‘
}

第一个是你自己的so库名称,第二个看注释,第三个复制粘贴就好

2)在gradle.properties中添加:

android.useDeprecatedNdk=true

不要问我为什么。

5.针对之前生成的h文件,写自己的源文件,c或cpp均可,记得要把h文件include进来。

6.然后就可以编译执行啦,是不是好简单,AS帮我们把以前Eclipse需要的Android.mk文件的编写给自动化了。

三、用JNI你需要知道的一些知识

1.指针和引用

Java把C最复杂的知识给封装了,而回到C的我们不得不把他们重新捡起来,记得我当年刚学也是被折腾的死去活来,其实想明白之后真的不复杂。如果你C的知识都还给谭老师了,建议你花个一两天先去捡回来,再回头搞会发现很多源码理解起来就轻松多了。这里举个例子,看代码:

先定义一个结构体:

struct Student
{
    int age;
    char* name;
};

然后定义一个指针指向它:

typedef const struct Student* Stu;

然后我们要怎么用呢:

func(Stu* student)
{
    printf("%d", (*student)->age);
    printf("%d", (**student).age);
}

int main()
{
    struct Student s = {10, "amuro", 100};
    Stu stu = &s;

    printf("%d", stu->age);
    printf("%d", (*stu).age);

    func(&stu);
}

运行结果四者打印出来的是10。大概解释一下:

stu是s的指针,也就是说stu里是s的地址,所以stu在赋值时等号右边必须是一个地址值,而&就是取地址符。

从指针中取值的时候有两种方法,代码里都写了。

函数传递指针的时候要特别小心func的参数其实是一个指向stu的指针,也就是指针的指针。所以传值的时候应该把stu的地址传过去。而函数中的student保存的是stu的地址,所以*student才是取出stu地址里保存的s的地址,这时候就可以进行取值操作了。画个图大家就更好理解了:

举这个例子是为了方便大家理解JNIEnv *env这个玩意儿,其实我们自己函数中拿到的env就是刚才例子里的student,所以我们要调用env中的方法的时候,才会这样写:(*env)->MethodName

2.Java标准类和JNI接口的对应关系

图我就直接盗了,反正口诀就是java类前面加个j就好了,然后首字母改小写。

3.jni.h在哪里

很多帖子都会列一堆jni函数,看了一头雾水,其实只要我们找到源码,很多问题会非常容易解决。jni.h的源码在我们下载的NDK包里。我的地址是:

D:\DevelopAS\asSDK\ndk-bundle\platforms\android-23\arch-arm\usr\include

所有的方法都在里面了,然后根据需求去里面找就好了。

文档的话看这里:

http://docs.oracle.com/javase/7/docs/technotes/guides/jni

看到这种密密麻麻的文档当时就想死了。

4.函数签名

很重要,在C层反射的时候,都需要传这东西,百度能找到很多详细的帖子,我这里就取其精华了。

注意注意,这个表写错了,long的签名是J,我当时就被坑死了,查了好久才查出来!

举个例子,比如

public String doSomething(int a, String x, long[] b);

签名就是:(ILjava/lang/String;[J)Ljava/lang/String;

括号里是参数列表,注意非原生类一定要在前面加L后面加;,括号后面是返回值。分号一定不要忘记!!!!

其实还能用javap命令来搞的,但是个人觉得还不如理解之后自己手写,熟悉之后效率高多了。这里就不赘述了。

5.JNIEnv是个啥

1)JNIEnv是一个线程相关的结构体, 该结构体代表了 Java 在本线程的运行环境 ;

2)JNIEnv 与 JavaVM : 注意区分这两个概念;

– JavaVM : JavaVM 是 Java虚拟机在 JNI 层的代表, JNI 全局只有一个;

– JNIEnv : JavaVM 在线程中的代表, 每个线程都有一个, JNI 中可能有很多个 JNIEnv;

3)JNIEnv 作用 :

– 调用 Java 函数 : JNIEnv 代表 Java 运行环境, 可以使用 JNIEnv 调用 Java 中的代码;

– 操作 Java 对象 : Java 对象传入 JNI 层就是 Jobject 对象, 需要使用 JNIEnv 来操作这个 Java 对象;

4)JNIEnv 体系结构

线程相关 : JNIEnv 是线程相关的, 即 在 每个线程中 都有一个 JNIEnv 指针, 每个JNIEnv 都是线程专有的, 其它线程不能使用本线程中的 JNIEnv, 线程 A 不能调用 线程 B 的 JNIEnv;

– 当前线程有效 : JNIEnv 只在当前线程有效, JNIEnv 不能在 线程之间进行传递, 在同一个线程中, 多次调用 JNI层方法, 传入的 JNIEnv 是相同的;

– 本地方法匹配多JNIEnv : 在 Java 层定义的本地方法, 可以在不同的线程调用, 因此 可以接受不同的 JNIEnv;

5)JNIEnv 结构 : 由上面的代码可以得出, JNIEnv 是一个指针, 指向一个线程相关的结构, 线程相关结构指向 JNI 函数指针 数组, 这个数组中存放了大量的 JNI 函数指针, 这些指针指向了具体的 JNI 函数;

四、JNI要怎么用

这个话题太大了,就我自己的总结来看,比较常用的其实无非就是四种,一种是操作基本类型,一种是String,一种是操作我们自定义的类型,一种就是数组。

1.常用核心API接口

妈蛋,做表格好蛋疼,就继续用截图代替吧……

基本看到名字都能猜到意思了,有了方法名字,想要知道怎么用,传哪些参数的话,去查文档或者我刚才说的jni.h源文件就好啦。

2.还是回去用Eclipse吧

用了一下AS之后真是觉得弱爆了,虽然编译方便了很多。要搞复杂的NDK开发还是要靠Eclipse,自己写Android.mk文件和Application.mk文件吧。

下面总结下这两个文件的常用配置:

3.我能想到最痛苦的事,是没有代码提示~

记忆力拔群的手写代码大神请绕道。

反正一开始用AS我是快疯了,基本没法写代码,写一行就要去查一下jni.h的头文件看看方法名和传入参数的定义,同时也不能调试,只能跑起来之后看日志或者报错,效率低下不能忍。那怎样让熟悉的代码提示出现呢,这里提供两个方法。

方法一:

1)你需要一个微软的VisualStudio,版本不必太高,我用的2008,建一个工程,选dll工程。

2)从前面说的jni.h的头文件目录下,把jni.h和另一个叫jni_md.h的文件,加上你自己javah生成的头文件一起copy到studio项目目录下,像这样:

然后在你的工程里把这些头文件导入:

然后自动提示就出来啦啦啦啦~

也没有要死的各种红线绿线。

3)编译出的dll文件其实是可以拿来用的(学过操作系统的童鞋都知道,dll动态链接库其实就是windows上的so),这里有一个注意点,就是如果你安装的是64位的java,记得一定要把VS的编译条件改成x64,不然编译出来的dll文件java是没法解析的。把你生成的dll文件的路径配置到环境变量的PATH下面,然后写一个java程序就能用load这个dll啦。我的配置是这样的:

D:\VisualStudio\Projects\NDKTest\x64\Debug;(这里面有个NDKTest.dll文件)

4)写个Java程序去测试这个JNI代码吧,System.loadLibrary的时候,lib名就传“NDKTest”就OK了。这个适合测试算法类的JNI,测试成功后直接拿到Android程序里用就好了。毕竟对C的支持,VS是无出其右的。

方法2:Eclipse增加代码提示

1)我相信这个才是很多童鞋想要的~为了把这个搞出来花了我一整天折腾各种环境和配置,Orz

2)确认你的eclipse安装了cdt,如果没有,去官网找下载地址去,我的是这里:

http://download.eclipse.org/tools/cdt/releases/juno

注意这个地址是在eclipse的install new software中使用的,不要直接网页打开。

3)下载MinGW,安装MinGW,配置MinGW,请看教程:

http://blog.csdn.net/hujingn/article/details/5849516

4)新建C项目,选择如图,记得右边一定要选MinGW

5)好了,出现了我们熟悉的代码界面,stdio.h,stdlib.h都能看到源码了,爽,但是这时候并没有jni的源码,别急,右键项目,把ndk-bundle里源码的地址配置进去:

具体看图,我就不贴了。配置完成确认OK。

6)激动人心的时候终于到了,加上jni.h的include,看到变蓝色没,变就对了。这时候我们就可以直接F3啦~然后,我们就可以把我们的javah生成的头文件拷过来了,然后的然后请看图:

这下想看什么源码,都轻松愉快了,有些小算法,还可以在下面的main函数里自己测试。

差不多就是这些东西了,剩下的就是学习各种API的使用了,作为程序员这个是必备技能了。跪求谷歌尽快完美支持NDK开发,这样折腾实在是太痛苦了。如果各位有更好更优秀的方式,欢迎留言讨论~

就酱~

时间: 2024-10-11 10:34:19

Android NDK开发技术与技巧总结与心得的相关文章

Android NDK开发的一些技巧

Android NDK(Native Development Kit)是基于Java JNI的使用C/C++和Java来混合开发应用的一种方式,甚至在Android 2.3(API 9)以后可以用Native(Native这个字在不同的语境中意义是不一样的,这里指的是JNI中的C/C++的代码:如果放在Hybrid中Native就是指平台原生的语言和API)代码来创建标准界面组件Activity,换句话说就可以用纯C/C++来写一个Android应用,但是貌似还没有人这么干过,这只是一种理论上的

【Android应用开发技术:用户界面】章节列表

作者:郭孝星 微博:郭孝星的新浪微博 邮箱:[email protected] 博客:http://blog.csdn.net/allenwells Github:https://github.com/AllenWells [Android应用开发技术:用户界面]章节列表 [Android应用开发技术:用户界面]用户界面基本原理 [Android应用开发技术:用户界面]设备适配 [Android应用开发技术:用户界面]用户界面布局技巧 [Android应用开发技术:用户界面]View基本原理 [

Android NDK开发初识

神秘的Android NDK开发往往众多程序员感到兴奋,但又不知它为何物,由于近期开发应用时,为了是开发的.apk文件不被他人解读(反编译),查阅了很多资料,其中有提到使用NDK开发,怀着好奇的心理,通过在线视频教育网站,我初步了解了NDK的神秘面纱,好东西自然要分享,接下来我们就一起来认识一下Android NDK开发. 一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三

如何定位Android NDK开发中遇到的错误

做android应用的调试,最怕就是报错,crash,看到这篇好文章,记录一下: 转自:http://www.csdn.net/article/2014-12-30/2823366-Locate-Android-NDK Android NDK是什么? Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK”.众所周知,Android程序运行在Dalvik虚拟机中,NDK允许用户使用类似C / C++之类的原生代

Android NDK开发篇(五):Java与原生代码通信(数据操作)

尽管说使用NDK能够提高Android程序的运行效率,可是调用起来还是略微有点麻烦.NDK能够直接使用Java的原生数据类型,而引用类型,由于Java的引用类型的实如今NDK被屏蔽了,所以在NDK使用Java的引用类型则要做对应的处理. 一.对引用数据类型的操作 尽管Java的引用类型的实如今NDK被屏蔽了,JNI还是提供了一组API,通过JNIEnv接口指针提供原生方法改动和使用Java的引用类型. 1.字符串操作 JNI把Java的字符串当作引用来处理,在NDK中使用Java的字符串,须要相

Android NDK 开发(三)--常见错误锦集合Log的使用【转】

转载请注明出处:http://blog.csdn.net/allen315410/article/details/41826511  Android NDK开发经常因某些因素会出现一些意想不到的错误,很多时候调试这些错误的时候,显得比调试Java代码要复杂,一方面是导致错误的原因很多很杂,另一方面NDK开发涉及到C/C++代码的编写,很多程序员对此不熟悉.那么这篇博客就总结一下,在NDK开发中经常出现的一些问题,并且尝试提供一些正确的解决方案,方便在开发时能够快速定位到错误,更改错误,当然了,错

Android NDK 开发

NDK全称:Native Development Kit. 1.NDK是一系列工具的集合. * NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk.这些工具对开发者的帮助是巨大的. * NDK集成了交叉编译器,并提供了相应的mk文件隔离平台.CPU.API等差异,开发人员只需要简单修改mk文件(指出"哪些文件需要编译"."编译特性要求"等),就可以创建出so. * NDK可以自动地将so和Java应用一起

C++开发安卓、windows下搭建Android NDK开发环境

1. NDK(Native Development Kit) 1.1 NDK简介 Android NDK是一套允许开发人员使用本地代码(如C/C++)进行Android APP功能开发的工具,通过这个工具,我们可以把用C/C++代码编译成可以直接运行在Android平台上的本地代码,这些本地代码以动态链接库( *.so )的形式存在,也正因为这样,我们可以通过复用这些动态链接库从而复用本地代码. 那么,通过NDK这个开发工具包,那么我们是否可以将一个APK完全使用C/C++来编写呢? 答案是不可

本人讲课时录制的Android应用开发技术教学视频

网盘地址:http://yun.baidu.com/pcloud/album/info?query_uk=1963923831&album_id=3523786484935252365 本人讲课时录制的视频,采用webex录制,视频文件内容相对较小30-50兆左右,1个视频文件平均大概有1个小时左右的时间,每个例子基本上从建立项目开始边做边讲. 由于讲课范围是Android应用开发技术,视频没涉及搭建环境,基础控件的使用等基础内容. 主要内容包括: 后台服务. 服务的绑定.服务和线程.远程服务和