前面两章介绍了android基础环境的搭建,下面以实现一个计算器的例子作为实战的过程
笔者不会介绍基础的Android语法和基本配置问题,这些基础可以查阅的资料很多,就不再献丑了。
第三章 Android之NDK的使用
宏观展示效果图
一、Android 的计算器页面实现
1、页面元素配置文件,activity_main.xml 内容如下:
<?xml version="1.0" encoding="utf-8"?>
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:rowCount="5"
android:columnCount="4"
android:id="@+id/grid" >
<EditText
android:id="@+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_columnSpan="4"
android:layout_gravity="fill"
android:enabled="false"
android:inputType="text|textMultiLine"
android:text="@string/init_default" >
<requestFocus />
</EditText>
<Button
android:id="@+id/btn_one"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/one" />
<Button
android:id="@+id/btn_two"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/two" />
<Button
android:id="@+id/btn_three"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/three" />
<Button
android:id="@+id/btn_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add" />
<Button
android:id="@+id/btn_four"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/four" />
<Button
android:id="@+id/btn_five"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/five" />
<Button
android:id="@+id/btn_six"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/six" />
<Button
android:id="@+id/btn_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/sub" />
<Button
android:id="@+id/btn_seven"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/seven" />
<Button
android:id="@+id/btn_eight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/eight" />
<Button
android:id="@+id/btn_nine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/nine" />
<Button
android:id="@+id/btn_mul"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/mul" />
<Button
android:id="@+id/btn_zero"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/zero" />
<Button
android:id="@+id/btn_equal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_columnSpan="2"
android:layout_gravity="fill"
android:text="@string/equal" />
<Button
android:id="@+id/btn_div"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_rowSpan="2"
android:layout_gravity="fill"
android:text="@string/div" />
<Button
android:id="@+id/btn_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_columnSpan="2"
android:layout_gravity="fill"
android:text="@string/clear" />
<Button
android:id="@+id/btn_mod"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/mod" />
</GridLayout>
哪一种Layout不重要,重要的是如何在页面中构造整齐的按钮排放,为了简单,笔者采用GridLayout布局,因为它可以自带表格对齐,
相对其它布局,在这方面的实现上要简单许多。
2、事件响应逻辑 MainActivity.java 的实现内容
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
static {
System.loadLibrary("HBIntegerVM");
}
private EditText result = null;
private Button btn_one = null;
private Button btn_two = null;
private Button btn_three = null;
private Button btn_four = null;
private Button btn_five = null;
private Button btn_six = null;
private Button btn_seven = null;
private Button btn_eight = null;
private Button btn_nine = null;
private Button btn_zero = null;
private Button btn_add = null;
private Button btn_sub = null;
private Button btn_mul = null;
private Button btn_div = null;
private Button btn_mod = null;
private Button btn_equal = null;
private Button btn_clear = null;
private String opt_num1 = "", opt_num2 = "", opt_result="";
private boolean second_entry = false;
private enum CALC_OPT {
ADD, SUB, MUL, DIV, MOD
}
private CALC_OPT opt;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
result = (EditText) super.findViewById(R.id.result);
opt_num1 = result.getText().toString();
btn_one = (Button) super.findViewById(R.id.btn_one);
btn_one.setOnClickListener(new showOnClickListener());
btn_two = (Button) super.findViewById(R.id.btn_two);
btn_two.setOnClickListener(new showOnClickListener());
btn_three = (Button) super.findViewById(R.id.btn_three);
btn_three.setOnClickListener(new showOnClickListener());
btn_four = (Button) super.findViewById(R.id.btn_four);
btn_four.setOnClickListener(new showOnClickListener());
btn_five = (Button) super.findViewById(R.id.btn_five);
btn_five.setOnClickListener(new showOnClickListener());
btn_six = (Button) super.findViewById(R.id.btn_six);
btn_six.setOnClickListener(new showOnClickListener());
btn_seven = (Button) super.findViewById(R.id.btn_seven);
btn_seven.setOnClickListener(new showOnClickListener());
btn_eight = (Button) super.findViewById(R.id.btn_eight);
btn_eight.setOnClickListener(new showOnClickListener());
btn_nine = (Button) super.findViewById(R.id.btn_nine);
btn_nine.setOnClickListener(new showOnClickListener());
btn_zero = (Button) super.findViewById(R.id.btn_zero);
btn_zero.setOnClickListener(new showOnClickListener());
btn_add = (Button) super.findViewById(R.id.btn_add);
btn_add.setOnClickListener(new optOnClickListener());
btn_sub = (Button) super.findViewById(R.id.btn_sub);
btn_sub.setOnClickListener(new optOnClickListener());
btn_mul = (Button) super.findViewById(R.id.btn_mul);
btn_mul.setOnClickListener(new optOnClickListener());
btn_div = (Button) super.findViewById(R.id.btn_div);
btn_div.setOnClickListener(new optOnClickListener());
btn_mod = (Button) super.findViewById(R.id.btn_mod);
btn_mod.setOnClickListener(new optOnClickListener());
btn_equal = (Button) super.findViewById(R.id.btn_equal);
btn_equal.setOnClickListener(new equalOnClickListener());
btn_clear = (Button) super.findViewById(R.id.btn_clear);
btn_clear.setOnClickListener(new OnClickListener(){
public void onClick(View v) {
opt_num1 = "0";
opt_num2 = "";
opt_result = "";
result.setText(opt_num1);
second_entry = false;
}
});
}
private class MyHBInteger extends ICalcHBInteger {
}
private class showOnClickListener implements OnClickListener {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(second_entry) {
if(opt_num2.equals("0")){
opt_num2 = ((Button)v).getText().toString();
} else {
opt_num2 += ((Button)v).getText().toString();
}
result.setText(opt_num2);
} else {
if(opt_result.length() != 0) {
if(opt_num2.equals("0")){
opt_num2 = ((Button)v).getText().toString();
} else {
opt_num2 += ((Button)v).getText().toString();
}
result.setText(opt_num2);
} else {
if(opt_num1.equals("0")){
opt_num1 = ((Button)v).getText().toString();
} else {
opt_num1 += ((Button)v).getText().toString();
}
result.setText(opt_num1);
}
}
}
}
private class optOnClickListener implements OnClickListener {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch(v.getId()){
case R.id.btn_add:
opt = CALC_OPT.ADD;
break;
case R.id.btn_sub:
opt = CALC_OPT.SUB;
break;
case R.id.btn_mul:
opt = CALC_OPT.MUL;
break;
case R.id.btn_div:
opt = CALC_OPT.DIV;
break;
case R.id.btn_mod:
opt = CALC_OPT.MOD;
break;
default: break;
}
if(second_entry){
MyHBInteger myhb = new MyHBInteger();
int resultLength = 0;
if(CALC_OPT.MUL != opt){
resultLength = opt_num1.length() > opt_num2.length() ? opt_num1.length() + 1 : opt_num2.length() + 1;
} else {
resultLength = opt_num1.length() * opt_num2.length();
}
myhb.init_HBInt(resultLength);
switch (opt) {
case ADD:
myhb.add_HBInt(opt_num1, opt_num2);
break;
case SUB:
myhb.sub_HBInt(opt_num1, opt_num2);
break;
case MUL:
myhb.mul_HBInt(opt_num1, opt_num2);
break;
case DIV:
myhb.div_HBInt(opt_num1, opt_num2);
break;
case MOD:
myhb.mod_HBInt(opt_num1, opt_num2);
break;
default:
break;
}
opt_result = myhb.output_HBInt();
result.setText(opt_result);
myhb.release_HBInt();
opt_num1 = opt_result;
opt_num2 = "";
} else second_entry = true;
}
}
private class equalOnClickListener implements OnClickListener {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (opt_num1.length() == 0 || opt_num2.length() == 0) {
Toast.makeText(getApplicationContext(), "No second number!",
Toast.LENGTH_SHORT).show();
return;
} else {
MyHBInteger myhb = new MyHBInteger();
int resultLength = 0;
if (opt == CALC_OPT.MUL)
resultLength = opt_num1.length() * opt_num2.length();
else
resultLength = opt_num1.length() > opt_num2.length() ? opt_num1
.length() + 1 : opt_num2.length() + 1;
myhb.init_HBInt(resultLength);
switch (opt) {
case ADD:
myhb.add_HBInt(opt_num1, opt_num2);
break;
case SUB:
myhb.sub_HBInt(opt_num1, opt_num2);
break;
case MUL:
myhb.mul_HBInt(opt_num1, opt_num2);
break;
case DIV:
myhb.div_HBInt(opt_num1, opt_num2);
break;
case MOD:
myhb.mod_HBInt(opt_num1, opt_num2);
break;
default:
break;
}
opt_result = myhb.output_HBInt();
result.setText(opt_result);
myhb.release_HBInt();
opt_num1 = opt_result;
opt_num2 = "";
second_entry = false;
}
}
}
}
二、NDK的使用
针对以上代码中实现的部分,逻辑上已经完成,但是核心部分的运算却没有出现,那是因为真正的计算部分是采用C去实现,然后由Java侧调用,即:
static {
System.loadLibrary("HBIntegerVM");
}
将C实现的计算逻辑采用动态库的方式被Java侧调用,这就是JNI的基本原理。
下面就如何实现调用与被调用的过程做一个实战介绍
首先得有已经实现运算逻辑的C侧实现,比如笔者已经实现了+,-,*,/,%等基本方法,在Java侧这里定义的名称为:
public abstract class ICalcHBInteger {
public native int init_HBInt(int size);
public native int release_HBInt();
public native void input_HBInt(String in);
public native String output_HBInt();
public native String add_HBInt(String a1,String a2);
public native String sub_HBInt(String s1,String s2);
public native String mul_HBInt(String m1,String m2);
public native String div_HBInt(String d1,String d2);
public native String mod_HBInt(String mod1,String mod2);
public native String powerMod_HBInt(String p1,String p2, String p3);
}
利用JNI解析命令:
javah -classpath bin/classes -d jni net.yjbqzsf.android.
ICalcHBInteger
-classpath:表示类的路劲
-d : 表示生成的头文件存放的目录
net.yjbqzsf.android
.ICalcHBInteger
则是完整类名,读者根据自己的实际情况配置
注意:执行javah命令是在该工程的根目录下,如果没有jni目录,则需要手动创建
执行成功后,会在jni目录下生成 net_yjbqzsf_android_ICalcHBInteger.h
这个头文件就是与Java侧交互的接口,C侧要被调用的部分,就要包含该头文件去实现(不明白原理的可以搜索JNI技术)
然后在自定义的C侧源码文件需要实现这个头文件中申明的方法。
利用NDK进行编译
所有的C侧源文件为了方便都需要在jni中存放,至于目录结构如何管理,读者根据自己的习惯安排。
笔者的jni路径下文件列表为:
Android.mk HBInteger.c net_yjbqzsf_android_ICalcHBInteger.h
Application.mk HBInteger.h
common.h HBIntegerVM.c
其中需要修改的是 Android.mk 与 Application.mk (不存在的可以从NDK安装路径的 $NDK/samples/hello-jni/jni 目录下拷贝)
其中Android.mk 修改配置如下:
LOCAL_MODULE := HBIntegerVM
LOCAL_SRC_FILES := HBIntegerVM.c HBInteger.c
Application.mk修改配置如下:
APP_ABI := x86
(因为笔者的机器是x86架构,且采用虚拟设备,读者如果有真机,根据真机的架构配置,无外乎arm与mips)
在工程的根目录下执行 ndk-build 命令,前提是第二章中介绍的配置是正确的。
如果一切正常,则在工程根目录的libs目录下生成 x86 / libHBIntegerVM.so 这样的目录与动态库。
好了,到此你的配置任务初步完成,直接运行工程Run as -> Android Application 吧,见证你的NDK项目运行情况。
小结
NDK可以自动在eclipse中运行,但是笔者没有这样做,因为在工作量且不复杂的情况下还是手动操作能感受的更多。
之前笔者使用JNI也是手动使用gcc进行编译后拷贝动态库至目标工程的适当位置(需要设置Native Location,或者直接libs目录),
在手动编译的过程中,任何一点疏忽都会导致不成功,只有在不断失败的基础之上,我们才能懂的更多,走的更远。
未完待续