我们知道,android架构中上层应用是以java来编写的,而底层则用C/C++编写。一般而言,上层android工程师在开发过程中,不会或很少涉及到C/C++层,但项目中一旦涉及到复杂的算法或耗时操作时(例如图像处理等),通常使用C/C++完成算法实现并提供给java层(通过JNI)调用,以此提升软件的运行效率。在这个过程中,C/C++所实现的代码以及JNI调用相关的代码被打包为.so库,这即是所谓的目录设备上的二进制动态库(SHARED LIBRARY)。本文在android源码平台(ubuntu)下编写了一个小程序:由上层JAVA代码通过JNI调用底层C/C++代码(.so库),完成加法运算的操作。注意,对于不具备源码环境的情况下,GOOGLE提供了NDK工具以完成C/C++层的开发,关于NDK配置使用不在本文讨论范围内。
1 JAVA层的开发
首先给出程序的运行效果图,如下所示:
图1 运行效果截图
在两个输入框中分别输入两个数字(45,35),点击"Confirm"则在上面显示出运算结果(sum = 80)。此过程中,加法操作用C代码实现。整个工程的根目录路径设置如下:
图2 Helloworld根目录
我们需要关注jni目录,此目录下包含着C/C++相关的代码。
1.1 MainActivity.java
package com.eton.helloworld;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity {
private TextView tvSum;
private EditText etAdd1;
private EditText etAdd2;
private Button btnConfirm;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvSum = (TextView) findViewById(R.id.tv_sum);
etAdd1 = (EditText) findViewById(R.id.et_add1);
etAdd2 = (EditText) findViewById(R.id.et_add2);
btnConfirm = (Button) findViewById(R.id.bt_Confirm);
System.loadLibrary("jnicall");
btnConfirm.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
int add1 = Integer.parseInt(etAdd1.getText().toString());
int add2 = Integer.parseInt(etAdd2.getText().toString());
tvSum.setText("sum = " + add(add1, add2));
}
});
}
public native int add(int add1, int add2);
}
代码29行:显式加载.so类库--libjnicall--我们自己的类库,注意,代码中为"jnicall",对应的是类库libjnicall.so;
代码43行:通过关键字native声明本地函数add,此函数为C/C++实现;
代码37行:调用本地函数add。
1.2 Android.mk编写
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := HelloWorld
LOCAL_JNI_SHARED_LIBRARIES := libjnicall
LOCAL_CERTIFICATE := platform
include $(BUILD_PACKAGE)
include $(LOCAL_PATH)/jni/Android.mk
代码06行:声明程序所需要的JNI共享库libjnicall;
代码09行:编译JNI模块,生成libjnicall.so。
2 .so库的编译
在本例中,so库的编译分为两部分,第一部分实现核心功能:加法运行,并打包为二进制静态库(static library);第二部分将静态库及JNI代码编译为.so库libjnicall.so。这两部分代码全部存放在jni目录中,如下所示:
图3 jni目录中的内容
2.1 tools.h代码
#ifndef TOOLS_H
#define TOOLS_H
extern int add(int add1, int add2);
#endif
头文件,声明函数add。
2.2 tools.c代码
#include "tools.h"
int add(int add1, int add2){
return add1 + add2;
}
源码文件,实现了此项目核心功能add。
2.3 jni_call.c代码
#include "tools.h"
#include <jni.h>
jint
Java_com_eton_helloworld_MainActivity_add(JNIEnv* env, jobject this, jint add1, jint add2){
return add(add1, add2);
}
JNI调用接口函数,JAVA层就是通过此代码来调用C/C++中的代码。需要注意的是此函数的命名方式Java_<包路径>_<类名>_<方法名>,以及参数列表等。
2.4 Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libtools
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := tools.c
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libjnicall
LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := jni_call.c
LOCAL_STATIC_LIBRARIES := libtools
include $(BUILD_SHARED_LIBRARY)
如前所述,编译分为两部分进行,
代码01-06,将tools.h,tools.c编译为二进制静态库(libtools.a文件);
代码07-结尾,将libtools.a及JNI代码编译为.so库。
至此完成,编译运行即可。