少壮不努力,老大徒伤悲。大学时光的潇洒散漫导致今天连C/C++编程都不会。作为一个程序员,不会C/C++说出去简直丢人啊。最近浏览公司招聘信息(Android职位),发现对NDK开发的要求越来越普遍了。笔者学习的是java,从事Android开发,对于Android底层的东西多少有点畏惧,因为没有涉及过,但是我们知道,不能因为怕就放弃。如我曾经签名所言:现在开始行动,就比还在犹豫的人快了一步。
回到正题,NDK开发真的很难吗?其实不是的,觉得难是因为你没懂,不懂是因为没学。那就来吧
NDK:什么是NDK
Android NDK(Native Development Kit )是一套工具集合,允许你用像C/C++语言那样实现应用程序的一部分。
NDK:什么时候使用NDK
别的不说,光看 提高性能 这一块就很有吸引力了。入门篇,不说那些虚的,先跑起来。
不会的话百度,这个不难,秒配置。1.下载ndk文件,解压;2.配置到环境变量;3.cmd测试ndk是否已经安装配置成功
目标实现: 从Android层面传递两个int值到底层,使用C/C++处理比较两个值的大小,返回结果信息(string)给Android使用。
项目问题: Android如何将java的数据类型传递给C/C++使用?如何将C/C++数据类型传递给java使用?中间的桥梁是什么?
项目分析: Android将java数据类型传递给native层,通过JNI“编码”转化为C/C++可用的数据类型,反过来一样也是使用JNI作为桥梁沟通java和C++
创建一个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);
}
首先配置JNI,在项目中新建 jni 文件夹。jni包含 Android.mk,Application.mk,头文件[cn_r_ndk_android_HelloNdk.h],C++文件
新建Android.mk,Application.mk,这两个文件内容不必记,配置文件嘛,理解一下什么意思就好,也可以从ndk的sample项目中拷贝,解压ndk之后随包附带有sample,找到 hello-jni 复制项目中的 Android.mk,Application.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]
前面两步完成了基本的配置,现在接下来进入重点的逻辑代码编写。
写代码之前就有几个问题啦:
1.java数据类型和C++数据类型怎么打通?
答:使用JNI。前面有提及到,JNI是java和C++这两种语言的桥梁,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类型拼接在一起这么麻烦,在 java 中 String 和 int 使用 + 可以直接拼接。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++。何乐而不为呢?
有什么不懂的地方可以留言讨论,有不足的地方还望指正批评。