Android JNI/NDK开发(一)NDK真的很难吗?

少壮不努力,老大徒伤悲。大学时光的潇洒散漫导致今天连C/C++编程都不会。作为一个程序员,不会C/C++说出去简直丢人啊。最近浏览公司招聘信息(Android职位),发现对NDK开发的要求越来越普遍了。笔者学习的是java,从事Android开发,对于Android底层的东西多少有点畏惧,因为没有涉及过,但是我们知道,不能因为怕就放弃。如我曾经签名所言:现在开始行动,就比还在犹豫的人快了一步。

回到正题,NDK开发真的很难吗?其实不是的,觉得难是因为你没懂,不懂是因为没学。那就来吧

NDK:什么是NDK

Android NDK(Native Development Kit )是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。

NDK:什么时候使用NDK

  • 提高性能。在某些情况下使用C/C++处理逻辑算法的效率高于java(图片相关处理算法等)
  • 使用第三方库。许多第三方库由C/C++语言编写,而Android应用程序需要使用现有的第三方库,如Ffmpeg、OpenCV这样的库。
  • 底层程序设计。例如,应用程序不依赖Dalvik Java虚拟机 。

别的不说,光看 提高性能 这一块就很有吸引力了。入门篇,不说那些虚的,先跑起来。

1.搭建NDK开发环境

不会的话百度,这个不难,秒配置。1.下载ndk文件,解压;2.配置到环境变量;3.cmd测试ndk是否已经安装配置成功

2.实现第一个NDK项目

目标实现: 从Android层面传递两个int值到底层,使用C/C++处理比较两个值的大小,返回结果信息(string)给Android使用。
项目问题: Android如何将java的数据类型传递给C/C++使用?如何将C/C++数据类型传递给java使用?中间的桥梁是什么?
项目分析: Android将java数据类型传递给native层,通过JNI“编码”转化为C/C++可用的数据类型,反过来一样也是使用JNI作为桥梁沟通java和C++

2.1 Native (Android层面开发)

创建一个Android项目。
HelloNdk 中定义 native 接口,在C++中计算两个int数的大小,返回结果信息string

public native String getMaxInt(int first, int second);

HelloNdk 中使用static 静态块调用so库。

static {
        System.loadLibrary("hi-ndk");
    }

HelloNdk(Activity)完整代码

public class HelloNdk extends Activity {

    static {
        System.loadLibrary("hi-ndk");//"hi-ndk" so 名称
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView tv = new TextView(this);
        tv.setText(getMaxInt(15, 10));
        setContentView(tv);
    }

    public native String getMaxInt(int first, int second);

}

2.2 JNI(Android和C/C++连接层处理)

首先配置JNI,在项目中新建 jni 文件夹。jni包含 Android.mkApplication.mk头文件[cn_r_ndk_android_HelloNdk.h]C++文件

这里写图片描述

新建Android.mkApplication.mk,这两个文件内容不必记,配置文件嘛,理解一下什么意思就好,也可以从ndk的sample项目中拷贝,解压ndk之后随包附带有sample,找到 hello-jni 复制项目中的 Android.mkApplication.mk

这里写图片描述

Android.mk 文件代码。(看文件中注释)

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hi-ndk   
#so项目名称

LOCAL_SRC_FILES := hi-ndk.cpp
#资源文件名称,对应的c/c++代码文件

include $(BUILD_SHARED_LIBRARY)

Application.mk 文件代码。(看文件中注释)

APP_ABI := all

#APP_ABI:= armeabi armeabi-v7a x86 mips mips-r2 mips-r2-sf
#如果是普通arm处理器的Android手机,使用APP_ABI := armeabi;
#如果是x86处理器的,使用APP_ABI := x86,等等。
#如果APP_ABI := all,会编译所有指令的so

APP_STL := stlport_static

#NDK中C++标准库、STL的配置
#如未配置可能出现错误: fatal error: iostream: No such file or directory

头文件[cn_r_ndk_android_HelloNdk.h]。C++头文件,通过 javah 生成头文件。javah 命令使用详情,可以在 cmd 中查看敲入 javah 查看

这里写图片描述

生成头文件方式:打开cmd面板,直接进入项目src目录,然后执行

javah -d ../jni cn.r.ndk.android.HelloNdk
备注:cn.r.ndk.android.HelloNdk 是(定义了native接口)类的完整路径

这里写图片描述

执行成功之后可以在 jni 文件夹中查看 [cn_r_ndk_android_HelloNdk.h]

2.3 C/C++(逻辑处理层)

前面两步完成了基本的配置,现在接下来进入重点的逻辑代码编写。
写代码之前就有几个问题啦:

1.java数据类型和C++数据类型怎么打通?
答:使用JNI。前面有提及到,JNI是java和C++这两种语言的桥梁,JNI提供了一种规范,打通两种语言之间的障碍

2.java数据类型如何转化为JNI?
基本数据类型:
这里写图片描述

引用类型:
这里写图片描述

通过上表,不难看出,通过JNI规范,java中的基础类型转化成JNI数据类型基本都是加上了 j
例:int:int -> jint

3.JNI数据类型与C++数据类型 如何相互转换?
这个就比较麻烦啦,需要自行查阅资料了解JNI编程中如何处理字符串以及对象类型的数据。
先看一下主要需要解释的代码。(这里使用C++语言实现,引入头文件什么的就不说了,笔者也是一边学习NDK,一边学习C++,所以C++代码写的很水的话请不要嘲笑,麻烦指正出来)

jstring Java_cn_r_ndk_android_HelloNdk_getMaxInt(JNIEnv * env, jobject thiz,
        jint first, jint second) {

    int first_j = first;
    int second_j = second;

    int max = maxInt(first_j, second_j);

    /**
     * int > string
     */
    string msg_result = xToString(max);

    string msg_tip = "the max number:";

    string msg = msg_tip + msg_result;

    const char* msg_j_a = msg.c_str();

    jstring msg_j = env->NewStringUTF(msg_j_a);

    return msg_j;
}

C++语言跟java很相似,笔者基本是按照java的思想写代码

这里的基本数据类型 jint直接转换成C++的int。接下来调用方法计算两个数的最大值。这里想吐槽一下的是,原来C++将 int 数据类型和 string类型拼接在一起这么麻烦,在 javaStringint 使用 + 可以直接拼接。C++中需要使用转换好多次。
我们想要把将 “the max number: XX” 这样的结果返回给 java。这里使用

    env->NewStringUTF(msg_j_a);

由于NewStringUTF()方法中接收的参数是 char* ch类型的,所以才有代码中各种转换。
笔者先将int转化为string,然后拼接两个string,接着使用string.c_str()将字符串转化为char*,最终才调用NewStringUTF()方法返回jstring
备注:如果使用C语言调用JNI的方法有所不同

(*env)->NewStringUTF(env, "Hello from JNI ! ");

关于C/C++部分的代码这里就不多说了,因为我也不太会,万一说错了,那就尴尬了。读者只能自行捉摸了。
关于C/C++和JNI之间的数据类型“转换”有两点我还是要分享一下:
1.基本数据类型可以直接转换
2.string类型处理的几个方法

http://blog.csdn.net/xyang81/article/details/42066665

看看完整版的代码 hi-ndk.cpp

#include <iostream>
#include <string>
#include <jni.h>
#include "cn_r_ndk_android_HelloNdk.h"

using namespace std;

// 函数声明
int maxInt(int first, int second);
string xToString(int x);

jstring Java_cn_r_ndk_android_HelloNdk_getMaxInt(JNIEnv * env, jobject thiz,
        jint first, jint second) {

    int first_j = first;
    int second_j = second;

    int max = maxInt(first_j, second_j);

    /**
     * int > string
     */
    string msg_result = xToString(max);

    string msg_tip = "the max number:";

    string msg = msg_tip + msg_result;

    const char* msg_j_a = msg.c_str();

    jstring msg_j = env->NewStringUTF(msg_j_a);

    return msg_j;
}

/**
 * 比较两个数大小
 */
int maxInt(int first, int second) {
    if (first > second) {
        return first;
    } else {
        return second;
    }
}

/**
 * X转化为string
 */
string xToString(int x) {
    char ch[10];
    sprintf(ch, "%d", x);
    string result(ch);
    return result;
}

OK,到这里基本的工作就做完了,会不会有点蒙圈?不会最好,如果蒙圈那就休息一下,再理一理思路,停下来想想实现步骤:
1. Native (Android层面开发)
简述:【一个定义了native方法的类】
2. JNI(Android和C/C++连接层处理)
简述:【一个JNI文件夹,里面有四个重要文件, Android.mk,Application.mk,头文件,C++文件】
3. C/C++(逻辑处理层)
简述:【java->jni;jni<->c++;业务逻辑处理】
这样一看是不是就简单明了了呢?三部分组成,一看简述能知道每一步大概做什么了,如果还是一片空白就回去那一步再看看。

如果没问题了,那就进行最后一步了,ndk-build 编译 so 文件。

这里写图片描述

编译成功的画面总是看的那么自然,整齐划一。
人们说黎明前最黑暗,成功前最渺茫。是的没错,在这一步你也可能卡很久,不过别怕,这一部分编译出错基本上是C/C++文件代码的问题了,如果是数据类型转换问题可参考上面提到的两点,1基本数据类型直接使用,2.string问题参考友情链接。如果是代码逻辑和规范问题的话,那就只能问度娘了,耐心点,你总能编译成功。说出来不怕别人笑话,那个数字转换和字符串拼接的需求我弄了很久,C++语言不熟悉,硬是问着百度写完的。

编译成功之后可以在lib文件夹中看到so文件,名称为:hi-ndk

这里写图片描述

你会不会想问那我Android怎么使用这个so文件啊?怎么拿到C++计算得到结果?

其实一开始就给出了,HelloNdk中代码
类静态块中加载so文件,注意这里不要写 libhi-ndk.so ,除去前面的 【lib】 和后面的 【.so】 ,只需要hi-ndk

static {
        System.loadLibrary("hi-ndk");//"hi-ndk" so 名称
    }

方法调用

TextView tv = new TextView(this);
tv.setText(getMaxInt(15, 10));

OK,到这里第一个NDK项目就写完了,很难吗?也不是很难啊。用点心,一切没多难,只是看你有多想做成一件事情。成功的那种喜悦是用钱买不到的,哈哈。同时还学习了新的语言C++。何乐而不为呢?

有什么不懂的地方可以留言讨论,有不足的地方还望指正批评。

代码下载

2017.04.24后续

JNI/NDK在Android studio 上基本配置

参考链接 http://blog.csdn.net/krubo1/article/details/50547681

备注1:
在jni文件下添加一个空的C文件 empty.c 能解决如下报错

Error:Execution failed for task ‘:app:compileDebugNdk’.
NDK_PROJECT_PATH=null

备注2:
Android Studio 定制快速生成Jni 头文件工具 NDK教程

http://blog.csdn.net/silver_R/article/details/48457077

猜你喜欢

转载自blog.csdn.net/u014702653/article/details/51861013