最近准备研究一下在android利用opencv进行开发,那么遇到的第一个问题就是NDK的环境搭建。
网上的很多用到的cygwin什么的,其实现在不用了,Android SDK已经自带了GCC
不过如果想体验下控制台编译的装逼感,还是可以下载个cygwin或者mingw什么的。
在windows下面不推荐cygwin,我之前就是用这个遇到了些乱七八糟的环境问题,使用mingw相对问题就不多。
正文
以我自己的经验,总结一下步骤。
1.安装JDK
2.安装Android SDK,(自带ADT)
3.下载安装NDK
4.配置ADT
5.编写一个hello world。
1~3这里就省略了,这里从ADT的配置开始说起。
配置ADT
配置ADT的主要工作就是让SDK和NDK可以一起工作。
首先我们要做的是,打开ADT
然后[Window]->[Preferences]->[Android]->[NDK] 然后选择你的NDK目录,如图
这里配置完基本上就差不多了,接下来通过一个hello world来更加详细的说明。
Hello NDK
1.首先,创建一个android工程。打开Activity主文件,写下如下代码
package com.example.hellondk;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.widget.TextView;
public class NDKActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ndk);
TextView tv = (TextView)findViewById(R.id.hello);
tv.setText(stringFromJni(this.getClass().getName()));
}
public native String stringFromJni(String name);
static{
System.loadLibrary("HelloNDK");
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.ndk, menu);
return true;
}
}
有过jni基础的着代码一看就懂。
native修饰的方法是需要用C来实现的,loadLibrary加载的就是最后编译成型的.so文件的名称。
2.然后右击你的工程项,[Android Tools] -> [Add Native Support] ,填入之前加载的那个库名
点击[Finish]后,会在工程目录下会出现一个jni的文件夹和两个文件
这时候HelloNDK.cpp里面什么都还没有,这时我们需要使用javah来生成可用的头文件。
3.生成头文件有两种方式,一种是控制台敲指令,另一种是通过ecplise直接生成 详情点击
这里我们使用控制台来进行。
进入到工程的根目录,输入指令
javah -classpath src -o jni/HelloNDK.h -jni com.example.hellondk.HelloNDK
说明: classpath指源文件目录,因为在根目录的缘故,所以制定src即可,-o指输出文件,如果对文件名没有要求,-d jni也是可以的,-jni可以省略,后面的类需要类全名,且不加后缀。
生成的头文件大概是这样
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_hellondk_NDKActivity */
#ifndef _Included_com_example_hellondk_NDKActivity
#define _Included_com_example_hellondk_NDKActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_hellondk_NDKActivity
* Method: stringFromJni
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_hellondk_NDKActivity_stringFromJni
(JNIEnv *, jobject, jstring);
#ifdef __cplusplus
}
#endif
#endif
这里我们在具体实现的时候,最好直接从这里复制方法声明,减少出错的机会。因为你的方法的参数返回值,是否是静态的都会影响到具体函数声明的形式。
生成头文件后,我们实际上只需要方法的申明,其他的可以不用。
4.编写HelloNDK.cpp文件,下面是我代码
#include <jni.h>
#include <string.h>
#include <stdio.h>
//char* to jstring
jstring stoJstring(JNIEnv* env, const char* pat)
{
jclass strClass = env->FindClass("java/lang/String");
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jbyteArray bytes = env->NewByteArray(strlen(pat));
env->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
jstring encoding = env->NewStringUTF("utf-8");
return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}
char* jstringTostring(JNIEnv* env, jstring jstr)
{
char* rtn = NULL;
jclass clsstring = env->FindClass("java/lang/String");
jstring strencode = env->NewStringUTF("utf-8");
jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
jbyteArray barr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsize alen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
if (alen > 0)
{
rtn = (char*)malloc(alen + 1);
memcpy(rtn, ba, alen);
rtn[alen] = 0;
}
env->ReleaseByteArrayElements(barr, ba, 0);
return rtn;
}
extern "C"
{
JNIEXPORT jstring JNICALL Java_com_example_hellondk_NDKActivity_stringFromJni
(JNIEnv * env, jobject obj, jstring name)
{
char str[128];
char* cname = jstringTostring(env, name);
sprintf(str,"%s say hello NDK", cname);
jstring js = stoJstring(env, str);
return js;
}
}
代码有点长,前面两个函数分别是jstring到char*的转换方法,需要调用jdk的方法, 详情可下载
需要注意的是,如果我们实现的方法没有被extern "C" 包含的话就会出现如下的错误
E/AndroidRuntime(4029): java.lang.UnsatisfiedLinkError: Native method not found:
com.example.hellondk.NDKActivity.stringFromJni:(Ljava/lang/String;)Ljava/lang/String;
这是因为编译的问题。由于我们的文件是.cpp文件,所以这里按照c++来编译的,这样对于c风格的代码就会出现问题,更多的说明
5.ndk-build编译
将NDK的根目录加入环境变量后,我们在工程的根目录输入 ndk-build指令后即可完成编译,但实际上并不需要。ADT可以帮我们做完这些事情,现在需要的之只要运行即可。
以下是运行截图。
囧...这截图好大。
还有很多被忽略的问题,比如Android.mk文件的描述等等。.mk文件应该就是类似makefile的东西,具体的还有待之后的研究。