我使用了高性能的C++实现,并通过JNI进行加载。更多细节请留言。
编辑:
要求JNI需要Android NDK。对于Windows来说,还需要cygwin或类似的东西。
如果您决定使用cygwin,我将给出一些小提示如何将其与NDK配合使用:
- 从cygwin下载setup.exe并执行它。
- 点击下一步并选择从Internet安装,并确认下一步。
- 接下来的两个步骤可以根据需要进行调整,然后像以往一样点击下一步。
- 选择您的互联网连接,然后按照最后阶段的相同程序操作。
- 一个下载页面会引起注意,选择它或者只是一个在您国家的下载页面。没有什么好说的了。
- 我们需要make和gcc-g++包。您可以使用左上角的搜索功能找到它们,点击Skip直到显示版本并选择第一个字段。之后做我们选择时常做的事情。
- 您将得到必须解决依赖项的信息。通常您不需要自己做,只需确认即可。
- 下载和安装开始。
- 如果需要,可以创建快捷方式,否则请点击完成。
- 下载zip文件并提取NDK到一个不含空格的路径。
- 现在可以启动cygwin了。
- 导航到NDK。路径/cydrive给出所有可用驱动器,例如
cd /cygdrive/d
导航到具有字母D的驱动器。 - 在NDK的根目录中,您可以使用
./ndk-build
执行文件ndk-build。应该会出现一个错误,如:Android NDK: Could not find application project directory !
。
您必须导航到Android项目中才能执行该命令。因此,让我们从一个项目开始。
在我们开始项目之前,请搜索哈希算法的C/C++实现。我从这个网站CSHA1中获取了代码。
您应该编辑源代码以满足您的要求。
现在我们可以开始JNI了。
在您的Android项目中创建一个名为jni的文件夹。它包含所有本地源文件和Android.mk(稍后会介绍该文件)。
将已下载(和编辑)的源文件复制到该文件夹中。
我的Java包名为de.dhbw.file.sha1,所以我将源文件命名类似以便轻松找到它们。
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS := -llog
LOCAL_MODULE := SHA1Calc
LOCAL_SRC_FILES := de_dhbw_file_sha1_SHA1Calc.cpp
include $(BUILD_SHARED_LIBRARY)
Java代码:
我使用了AsyncTask和ProgressDialog来向用户提供有关操作的反馈。
package de.dhbw.file.sha1;
public class SHA1HashFileAsyncTask extends AsyncTask<String, Integer, String> {
static {
System.loadLibrary("SHA1Calc");
}
protected native void calcFileSha1(String filePath);
protected native int getProgress();
protected native void unlockMutex();
protected native String getHash();
}
本地代码(C++):
记住,在本地代码中访问变量或使用线程时,需要同步,否则会很快出现分段错误!
对于JNI的使用,您需要添加#include < jni.h >
。
要记录日志,请插入以下包含#include <android/log.h>
。
现在您可以使用__android_log_print(ANDROID_LOG_DEBUG, DEBUG_TAG,“Version [%s]”, “19”);
来记录日志。
第一个参数是消息类型,第二个参数是导致该库的内容。
您可以看到我的代码中有一个版本号。这非常有用,因为有时apk构建器不使用新的本机库。如果错误的版本在线上,则故障排除时间可以大大缩短。
本地代码中的命名约定有点疯狂:Java_[package name]_[class name]_[method name]
。
前两个参数始终给出,但是根据应用程序的不同,您应该区分:
func(JNIEnv * env, jobject jobj)
-> JNI调用是实例方法
func(JNIEnv * env, jclass jclazz)
-> JNI调用是静态方法
calcFileSha1(...)
方法的头文件:
JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_calcFileSha1(JNIEnv * env, jobject jobj, jstring file)
JDK提供了二进制文件javah.exe,用于生成本地代码的头文件。使用非常简单,只需使用完全限定类名调用它:
javah de.dhbw.file.sha1.SHA1HashFileAsyncTask
在我的情况下,我还必须提供bootclasspath,因为我使用Android类:
javah -bootclasspath <path_to_the_used_android_api> de.dhbw.file.sha1.SHA1HashFileAsyncTask
这将是生成的文件:
#include <jni.h>
#ifndef _Included_de_dhbw_file_sha1_SHA1HashFileAsyncTask
#define _Included_de_dhbw_file_sha1_SHA1HashFileAsyncTask
#ifdef __cplusplus
extern "C" {
#endif
#undef de_dhbw_file_sha1_SHA1HashFileAsyncTask_ERROR_CODE
#define de_dhbw_file_sha1_SHA1HashFileAsyncTask_ERROR_CODE -1L
#undef de_dhbw_file_sha1_SHA1HashFileAsyncTask_PROGRESS_CODE
#define de_dhbw_file_sha1_SHA1HashFileAsyncTask_PROGRESS_CODE 1L
JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_calcFileSha1
(JNIEnv *, jobject, jstring);
JNIEXPORT jint JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_getProgress
(JNIEnv *, jobject);
JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_unlockMutex
(JNIEnv *, jobject);
JNIEXPORT jstring JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_getHash
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
文件可以在不另行通知的情况下更改。但请勿再次使用javah
!
类和方法
要获取类实例,您可以使用jclass clz = callEnv->FindClass(CALL_CLASS);
。在这种情况下,CALL_CLASS
是类de/dhbw/file/sha1/SHA1HashFileAsyncTask的完全限定路径。
要查找方法,您需要JNIEnv和类实例:
jmethodID midSet = callEnv->GetMethodID(callClass, "setFileSize", "(J)V");
第一个参数是类实例,第二个是方法名称,第三个是方法签名。
您可以使用JDK提供的二进制文件javap.exe获取签名。只需使用类的完全限定路径调用它,例如 javap -s de.dhbw.file.sha1.SHA1HashFileAsyncTask
。
您将得到如下结果:
Compiled from "SHA1HashFileAsyncTask.java"
public class de.dhbw.file.sha1.SHA1HashFileAsyncTask extends android.os.AsyncTas
k<java.lang.String, java.lang.Integer, java.lang.String> {
[...]
static {};
Signature: ()V
public de.dhbw.file.sha1.SHA1HashFileAsyncTask(android.content.Context, de.dhb
w.file.sha1.SHA1HashFileAsyncTask$SHA1AsyncTaskListener);
Signature: (Landroid/content/Context;Lde/dhbw/file/sha1/SHA1HashFileAsyncTas
k$SHA1AsyncTaskListener;)V
protected native void calcFileSha1(java.lang.String);
Signature: (Ljava/lang/String;)V
protected native int getProgress();
Signature: ()I
protected native void unlockMutex();
Signature: ()V
protected native java.lang.String getHash();
Signature: ()Ljava/lang/String;
[...]
public void setFileSize(long);
Signature: (J)V
[...]
}
如果找到了该方法,则变量不等于0。
调用该方法非常简单:
callEnv->CallVoidMethod(callObj, midSet, size);
第一个参数是从“main”方法给定的jobject,其他参数我认为很清楚。
请记住,您可以调用类的私有方法,因为本地代码是该类的一部分!
字符串
给定的字符串将使用以下代码进行转换:
jboolean jbol;
const char *fileName = env->GetStringUTFChars(file, &jbol);
另一种方法:
TCHAR* szReport = new TCHAR;
jstring result = callEnv->NewStringUTF(szReport);
它可以是任何一个char*
变量。
异常
可以通过JNIEnv抛出:
callEnv->ThrowNew(callEnv->FindClass("java/lang/Exception"),
"Hash generation failed");
您还可以使用JNIEnv检查是否发生了异常:
if (callEnv->ExceptionOccurred()) {
callEnv->ExceptionDescribe();
callEnv->ExceptionClear();
}
规格
构建/清理
构建
当我们创建了所有文件并填充了内容后,我们可以对其进行构建。
打开cygwin,导航到项目根目录,并从那里执行ndk-build,该文件位于NDK根目录中。
如果编译成功,您将获得类似于以下的输出:
$ /cygdrive/d/android-ndk-r5c/ndk-build
Compile++ thumb : SHA1Calc <= SHA1Calc.cpp
SharedLibrary : libSHA1Calc.so
Install : libSHA1Calc.so => libs/armeabi/libSHA1Calc.so
如果有任何错误,您将从编译器获得典型的输出。
清除
打开cygwin,转到Android项目并执行命令/cygdrive/d/android-ndk-r5c/ndk-build clean
。
构建apk
在构建本地库后,您可以构建项目。我发现使用eclipse特性清理项目是有优势的。
调试
Java代码的调试与以前没有区别。
下一步将介绍C++代码的调试。