GitHub上有几个项目,是用来在Android平台上与gnugo对弈的,分别是
gobandroid-ai-gnugo和
gobandroid,我在Android Studio上编译了它们。
此外还有个名为
gobandroid-ai-gnugo NDK part的
项目,我比较了一下,与gobandroid-ai-gnugo差不多,这里没有编译。
由于没有用过NDK,我先尝试了一下NDK的Hello World。
我用的Android Studio 2.1.1,是网上下载的一个android-studio-bundle-143.2821654-windows.exe安装的,JDK用的是1.7版本(jdk-7u79-windows-586.exe)。
另有一个adt-bundle-windows-x86-20140702.zip可能装了,这个不知是for Eclipse还是for Android Studio的了。Google访问不了,但Android Studio
倒有了
国内镜像站,以及
开发者站点。
从gobandroid-ai-gnugo NDK part的说明得知,这个项目挺早的了,其0.2版本是用于从NDK 1.5到1.6的迁移。不过后面我们看到,我用了高版本的NDK,
项目构建似乎没有问题。
Android Studio从2.1到2.2有个较大的变化,2.2默认使用CMake来构建NDK项目(当然也可用ndk-build)。我用的2.1.1,没有使用CMake。
我的Android Studio装了SDK 5.1(API level 22)和6.0(API level 23)的支持。先安装NDK,这个有了Android Studio很方便,打开Tools |- Android |-
SDK manager,在SDK tools中选NDK和LLDB安装(它还加入了对SDK patch applier的依赖),它就给你装在了SDK文件夹下的ndk_bundle/下,
且在Android Studio中给你配置了该路径(在File |- Project Structure |- SDK Location中可以看到)。
只是为了在Windows的cmd下直接运行ndk-build,我需要在系统环境变量PATH中加入它,我的是F:\Android\sdk\ndk-bundle。它装的版本是NDK 16b的。
1. NDK之Hello World
新建工程ctest(或许应该建议不起带下划线“_”的名字)后,我在app的build.gradle里改buildToolsVersion为“23.0.3”(我的compileSdkVersion为“23”)。
先加一个java类文件helloJNI.java:
helloJNI.java:
package com.example.pc.ctest;
public class helloJNI{
static{
System.loadLibrary("hello");;
}
public native String sayHello();
}
然后我打开Windows的cmd,
cd ctest\app\src\main\java\com\example\pc\ctest
执行 javac helloJNI.java,
然后转至com/的上级目录, cd ..\..\..\..\
javah -jni com.example.pc.ctest.helloJNI,
这时它生成了一个文件com_example_pc_ctest_helloJNI.h。
在src\main\下(与java/同级)新建一个目录jni/(起名jni是有一定必要的,不然后面会看到对于ndk-build,除非你给它指定APP_BUILD_SCRIPT,它
缺省会到名为“jni”的目录下去找),进入该目录,把上面生成的头文件剪切进来,再把它复制为helloJNI.c,改改这个c文件为如下:
helloJNI.c:
#include <jni.h>
/* Header for class com_example_pc_ctest_helloJNI */
#include "com_example_pc_ctest_helloJNI.h"
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_pc_ctest_helloJNI
* Method: sayHello
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_pc_ctest_helloJNI_sayHello
(JNIEnv * env, jobject thisobj)
{
//printf("hello world JNI");
return (*env)->NewStringUTF(env, "string_来自_c");
}
#ifdef __cplusplus
}
#endif
由于是从头文件拷贝过来的,改动方便一点(但也有坏处,我曾因未去掉原头文件中的"#ifndef"而出错,找了半天。),头文件如下:
com_example_pc_ctest_helloJNI.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_example_pc_ctest_helloJNI */
#ifndef _Included_com_example_pc_ctest_helloJNI
#define _Included_com_example_pc_ctest_helloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_pc_ctest_helloJNI
* Method: sayHello
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_example_pc_ctest_helloJNI_sayHello
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
然后我改了改MainActivity来显示一下:
MainActivity.java:
package com.example.pc.ctest;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
import android.app.Activity;
import android.app.ActivityManager;
import android.view.Menu;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
TextView tv1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);
Log.v("debug", "------------try to call ndk ----------------");
tv1 = new TextView(this);
helloJNI obj = new helloJNI();
tv1.setText(obj.sayHello());
setContentView(tv1);
Log.v("debug", "------------call ndk end ----------------");
}
}
根据编译提示,改了gradle.properties,其中写上:
gradle.properties:
android.useDeprecatedNdk=true
。可能这Android Studio和NDK版本已过时了。
在build.gradle中的defaultConfig{...}中加:
build.gradle:
ndk{
moduleName "hello"
abiFilters "armeabi","armeabi-v7a","x86"
}
改了.gradle,可能要sync一下再build。据(网上)称.so将在app/build/intermediates/ndk下生成。
我运行了几下,却没发现结果,也没发现有.so生成。怎么回事?
网上
有人答问类似的问题时提到:
“可以先分析一下 apk 中是否有对应的 so。NDK 开发我觉得还是推荐使用 Android.mk 进行配置(或者 Cmake 的方式)。
只要在项目的 build.gradle 的 android 节点下添加
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
”
看来是要在这方面补充点什么。
第二天再干,主要是参照了王家林等著的《Android高级开发实战--UI、NDK与安全》,在jni/下写Android.mk如下:
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello
LOCAL_SRC_FILES := helloJNI.c
include $(BUILD_SHARED_LIBRARY)
进入Windows cmd,
cd app\src\main\jni (也可到jni/的上一级目录,即main/)
执行: ndk-build (重新生成库需先ndk-build clean)
ndk-build生成的库会出现在与jni/同级的libs/中,即
main/
|- java/
|- jni/
|- libs/
这时Android Studio工程可成功构建,在模拟器中运行该程序了。
其实也可不显式运行ndk-build,实际上在有了Android.mk后Android Studio工程会自动将其加入工程,此时即可成功生成.so,但其生成的库
在app/build/intermediates/ndk/...之下(我曾以为要将库转移放在上面ndk-build放置的同样位置,即app/src/main/libs/处,后来发现不必要)。
在我的Android Studio版本中给build.gradle中的defaultConfig{...}添加"ndk{...}"看来是对的。查了一下,上面网上答问的人所说的添加
externalNativeBuild{ ndkBuild{...}}可能适用于Android Studio 2.2之上、gradle 2.2.0、JDK 1.8版本的情况。还有人提到Android Studio
2.2后默认使用CMake后写JNI不需要配置mk文件了。
2. 编译gobandroid-ai-gnugo
gobandroid-ai-gnugo是用JNI包装了GNUGo的Android的后台服务。其目录树gobandroid-ai-gnugo/src/main/jni/project/下包含gnugo-3.8/的目录,
这是修改过后的gnugo 3.8的源代码。我先把该目录重命名保留下来(还要把这个改名了的文件夹移出到项目外,因为不然ndk-build会把其中的
Android.mk又找出来用)。然后新建gnugo-3.8/的目录,其中从GNUGo官网下载下来gnugo 3.8的原始源代码。
根据本项目的README所说的,我在Cygwin环境下,
在gnugo-3.8/目录下 ./configure
make
make clean
然后在Windows的Android Studio中打开本工程(选择gobandroid-ai-gnugo/文件夹),照例将build.gradle中buildToolsVersion(由“23.0.2”)改为“23.0.3”。
拷贝本项目中原gnugo-3.8/下的Android.mk到新建的gnugo-3.8/中,原gnugo-3.8/interface/下的新增的java_bridge.c到新建的gnugo-3.8/interface/下。
在Windows的cmd下尝试运行:
cd gobandroid-ai-gnugo\src\main\jni
ndk-build
要作一些修改,我注释掉了config.h中的几个开关:
config.h:
/* Define to 1 if you have the <ncurses/curses.h> header file. */
//#define HAVE_NCURSES_CURSES_H 1
/* Define to 1 if you have the <ncurses/term.h> header file. */
//#define HAVE_NCURSES_TERM_H 1
/* Define to 1 if termcap/terminfo is available. */
//#define TERMINFO 1
可能可以作更多的修改,参看本项目中使用的config.h,例如它里面还“#define SIZEOF_LONG 8”,我的是“#define SIZEOF_LONG 4”。
然后加入作者AGR在interface/子目录的源码改动,还要加utils/、engine/下的源码改动。这样ndk-build在gobandroid-ai-gnugo/src/main/下
生成了libs/。(这时可能需要通过ndk-build来生成库,原工程好象不行。)
这个项目工程是一个后台服务,点击运行后是看不出效果的,要等运行下面的gobandroid才能开始起作用。
3. 编译gobandroid
不象gobandroid-ai-gnugo很早以前就开发好了,gobandroid开发仍在活跃中,我比较了一下其GitHub上的各版本,最后选择了gobandroid的
tag为2.4.0的版本,这个版本build.gradle显示compileSdkVersion为23,buildToolsVersion为"23.0.3",gradle版本2.1.2,都适合我正使用
的Android Studio。果然,没有改动什么,这个项目就可以构建好了。在人机对弈时,它会寻找使用上面gobandroid-ai-gnugo的服务。
4. 尾声:c代码中打印log
这篇网文讲得挺清楚。
我的应用:
在interface/java_bridge.c中:
interface/java_bridge.c:
#include <jni.h>
#include <android/log.h>
#include <stdio.h>
#include <string.h>
#include "gtp.h"
#define LOG_TAG "MyLOG"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
char gtp_input_line[100];
char gtp_output_line[GTP_BUFSIZE*2];
void
Java_org_ligi_gobandroidhd_ai_gnugo_GnuGoConnection_initGTP (
JNIEnv* env,
jclass clasz,
jfloat memory
)
{
init_gtp (memory);
}
jstring
Java_org_ligi_gobandroidhd_ai_gnugo_GnuGoConnection_playGTP (
JNIEnv* env,
jclass clasz,
jstring input
)
{
char *cinput = (char*)(*env)->GetStringUTFChars (env, input, NULL);
strcpy (gtp_input_line, cinput);
static int num = 0;
// printf("!-->gtp_input_line %d is %s\n", num, gtp_input_line);
LOGD("!-->gtp_input_line %d is %s\n", num, gtp_input_line);
gtp_output_line[0] = '\0';
play_gtp ();
(*env)->ReleaseStringUTFChars (env, input, cinput);
//printf("!-->gtp_output_line %d is %s\n", num, gtp_output_line);
LOGD("!-->gtp_output_line %d is %s\n", num, gtp_output_line);
num++;
return (*env)->NewStringUTF (env, gtp_output_line);
}
在gnugo-3.8/Android.mk中:
project/gnugo-3.8/Android.mk:
...
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
P.S.: 在Android Studio app下新建目录要把工作区上方选项选到Project(不能是Android)。这里不需从AS新建,手动在java/同级
目录建即可,AS工程会找到自动加入项目。
为提高速度和效率,构建时我需要先单独运行模拟器(从Tools |- Android |- AVD manager),待其完全启动后(可能需要20分钟)(从
从Tools |- Android |- Android Device Monitor观察),再进行编译和运行。
TODO:尚未试过将.so打包到.apk中。据说在Android Studio 1.1.0中,要在模块的build.gradle中修改如:
build.gradle:
android{
defaultConfig{
...
}
buildTypes{
...
}
sourceSets{
main{
jniLibs.srcDirs = ['libs']
}
}
}
选中module右击sync同步,module目录下会生成一个jniLibs目录,再打包。(可能关键是有jniLibs目录的内容。)
TODO: patterns/下的mkpat.c编译成的mkpat程序如何使用尚不了解。
一篇相关的文章参见
编译gnugo4ios