这回用Android的NDK技术写一个计算器练练手,啥是Android的NDK开发?看看Google的介绍--传送门
为啥要用NDK开发呢,NDK是用C++ 写的,运行效率特别高,在一些需要复杂计算的地方(例如游戏开发),还有就是Java代码反编译比较简单,很容易被反编译暴露源码,C++的反编译难度较大,涉及到加密的代码可以用C++来写。当然用NDK开发也有缺点,C++代码上手比Java难,而且用C++后调试难度增加,所以是否要引入C++要权衡之后再决定。
我在暑假用C++写了一个能四则运算的小程序,现在想在Android端也写一个支持四则运算的计算器,要是再用Java重写一遍就太麻烦了,正好可以用这个练一练Android的NDK开发。
工欲善其事必先利其器,还么有配置好NDK开发环境的可以看我上一篇文章--传送门;
环境配好了,话不多说,开始撸代码吧。新建一个项目,
一定要把 Include C++ support 选中,然后next,然后到选中Android版本,选择Activity的地方,都默认即可,一路next。
到这还是默认就行,然后finish,等项目创建成功后会看到熟悉的界面
和我们普通项目有点区别的地方是在Activity的onCreate方法中会多出一段代码和一个方法
static {
System.loadLibrary("native-lib");
}
public native String stringFromJNI();
这段代码是用来加载C++生成的动态库,方法是一个本地方法,我理解为调用C++函数的方法。
先介绍到这,然后开始计算器界面的编写,这和普通的界面编写没有区别,就不多说了,上一个效果图吧
接下来就是Activity中的各种逻辑的编写,点击事件的处理,代码有点多,先不贴了,最后我会分享。
注意了,到这重点来了,要新建C++代码的源文件和头文件。在CPP文件夹上右键 New-> C++class,填写文件名-> OK。
你会发现在CPP目录下没有刚才新建的文件,我靠,AS出BUG了吧。不要急,在导航栏的最下边找到CMakeLists.text,点开,在图上标注的地方添加代码
在 src/main/cpp/native-lib.cpp 下面添加 src/main/cpp/×××××.cpp
××××是你的C++源文件的名。点击右上角的 Sync Now,等待同步完成。
这时你会发现CPP目录下有多了两个文件
这时回到 MainActivity 中,把
public native String stringFromJNI();
方法修改一下。改成这样public native double stringFromC(String s);
。为啥要改成这样呢,因为我们要做一个计算器,所有的按键逻辑和显示工作由Java代码完成,而计算工作由C++ 代码去做,Java层要传递一个字符串给C++层,然后C++层把计算结果返回给Java层,Java层来显示计算结果。所以要加一个String类型的形参,把返回值改成double。这时会报错,然后按 Alt+回车 键AS会自动修改。现在来到native-lib.cpp文件,把刚开始AS生产的函数删掉,只保留这一个函数
JNIEXPORT jdouble JNICALL
Java_com_bruce_counter_MainActivity_stringFromC(JNIEnv *env, jobject instance, jstring s_) {
const char *s = env->GetStringUTFChars(s_, 0);
// TODO
// env->ReleaseStringUTFChars(s_, s);
}
NIEXPORT jdouble JNICALL 是在 jni.h中定义的返回值类型,不清楚jni类型的可以看一下
如果有env->ReleaseStringUTFChars(s_, s);
代码,把他删掉,用不到。
完成的native-lib.cpp代码
#include <jni.h>
#include <string>
#include "Counter.h"
using std::string;
extern "C"
JNIEXPORT jdouble JNICALL
Java_com_bruce_counter_MainActivity_stringFromC(JNIEnv *env, jobject instance, jstring s_) {
const char *s = env->GetStringUTFChars(s_, 0);
// TODO
// env->ReleaseStringUTFChars(s_, s);
string str=s;
delete s;
Counter *c=new Counter();
double d=c->coutn(str);
delete c;
return d;
}
指针 ‘s’ 指向的就是我们从Java层传过来的字符串,我们把他保存下来,然后delete s,听说不delete会内存泄露,还没求证,就先这样写吧。Counter类是负责计算的,调用Counter的countn函数,就能得到结果。
为啥要在native-lib.cpp中调用这些方法呢?我的理解native-lib相当于main函数,Java调用C++会先调用这个。好了,整个C++层代码基本完成了。
到Java中去调用吧。
displayStr=displayStr.replace("×","*").replace("÷","/");
screen.setText(stringFromC(displayStr)+"");//计算结束更新屏幕
先说一个遇到的坑,在Java中乘号用的'×',除号用的'÷',Java用的unicode编码,没有问题,但是C++默认用的是ASCII编码,不能使用’ב和’÷‘,我们使用‘*’和‘/’代替,所以在传入字符串时要替换一下displayStr=displayStr.replace("×","*").replace("÷","/");
到这基本就好了,可以运行一下看看了。
发现写博客比写代码累多了,就先写到这吧,我已经把完整的demo上传到码云,需要的同学可以去下载。如果还有什么问题就贴在在评论区吧,一起学习。