编译Gobandroid(含NDK之Hello World)


  

GitHub上有几个项目,是用来在Android平台上与gnugo对弈的,分别是 gobandroid-ai-gnugogobandroid,我在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







  

More powered by