我使用了一个用 JNI 加载的高性能 c++ 实现。
更多详情请在评论中留言。
编辑:
JNI 的要求是Android NDK。对于 Windows,还需要 cygwin 或类似的东西。
如果你决定使用 cygwin,我会给你一些关于如何让它与 NDK 一起工作的小说明:
- 从 cygwin 下载 setup.exe 并执行。
- 点击下一步并选择从互联网安装确认下一步。
- 接下来的两个步骤根据需要调整设置,并一如既往地单击下一步。
- 选择您的互联网连接和与最后阶段相同的过程。
- 一个下载页面会吸引眼球,选择它或只选择一个下载页面,该页面位于您所在的国家/地区。没什么好说的了。
- 我们需要包 make 和 gcc-g++。您可以使用左上角的搜索找到它们,单击Skip 直到显示版本并选择第一个字段。做我们在选择后一直做的事情。
- 您将获得信息,即存在必须解决的依赖关系。一般不需要自己做和确认。
- 下载和安装开始。
- 如果您需要,您可以创建快捷方式,否则请点击特殊的完成。
- 下载 zip 文件并将 NDK 解压缩到不包含空格的路径。
- 您现在可以开始 cygwin。
- 导航到 NDK。路径 /cydrive 为您提供所有可用的驱动器 f.e.
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
# How the lib is called?
LOCAL_MODULE := SHA1Calc
# Which is your main SOURCE(!) file?
LOCAL_SRC_FILES := de_dhbw_file_sha1_SHA1Calc.cpp
include $(BUILD_SHARED_LIBRARY)
Java 代码:
我使用带有 ProgressDialog 的 AsyncTask 来向用户提供有关操作的一些反馈。
package de.dhbw.file.sha1;
// TODO: Add imports
public class SHA1HashFileAsyncTask extends AsyncTask<String, Integer, String> {
// [...]
static {
// loads a native library
System.loadLibrary("SHA1Calc");
}
// [...]
// native is the indicator for native written methods
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
那将是生成的文件:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class de_dhbw_file_sha1_SHA1HashFileAsyncTask */
#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
/*
* Class: de_dhbw_file_sha1_SHA1HashFileAsyncTask
* Method: calcFileSha1
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_calcFileSha1
(JNIEnv *, jobject, jstring);
/*
* Class: de_dhbw_file_sha1_SHA1HashFileAsyncTask
* Method: getProgress
* Signature: ()I
*/
JNIEXPORT jint JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_getProgress
(JNIEnv *, jobject);
/*
* Class: de_dhbw_file_sha1_SHA1HashFileAsyncTask
* Method: unlockMutex
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_de_dhbw_file_sha1_SHA1HashFileAsyncTask_unlockMutex
(JNIEnv *, jobject);
/*
* Class: de_dhbw_file_sha1_SHA1HashFileAsyncTask
* Method: getHash
* Signature: ()Ljava/lang/String;
*/
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 获得的签名。只需使用类 f.e. 的完整路径调用它即可。 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 功能 clean project 是有利的。
调试
java代码的调试和以前没有什么不同。
c++代码的调试将在下期进行。