zoukankan      html  css  js  c++  java
  • 移动跨平台框架开发之二:android重用c++库

    android平台下重用c++库的原理比较古老,就是java与c++的jni。它的难度比ios下要大不少。Obj-c与c++可以混合编码,无缝集成,而java与c++不能混合,对象间不能互相引用。此难点一。

    另一个难点与ios下相似,就是对第三方库的编译。虽然有ios的经验,但似乎并没有可供android借鉴之处。这里需要说明的是,我准备作的是在代码中以c++的方式调用这些第三方库,因此它们不需要提供java的接口,也就是说不需要这些库的java binding。

    以下除了cryptopp是在ubuntu 12.04上编译的以外,其余的编译环境均为macos 10.7.4。

    1. 准备ndk环境

    参考https://developer.android.com/tools/sdk/ndk/index.html

    system参数指定你的编译平台。

    platform参数指定你想支持的最低版本,跟你在AndroidManifest.xml中的 android:minSdkVersion值一致。

    1.1.     macos 10.7.4

    cd /Users/chenfeng/program/ android-ndk-r8e

    sudo . /build/tools/make-standalone-toolchain.sh --system=darwin-x86_64 --platform=android-8 --install-dir=/opt/android-toolchain

    1.2.     ubuntu 12.04

    cd /home/chenfeng/program/android-ndk-r8e

    sudo ./build/tools/make-standalone-toolchain.sh --system=linux-x86_64 --platform=android-8 --install-dir=/opt/android-toolchain

    1. 编译zmq

    2.1.     编译zmq c++库

    参考http://www.zeromq.org/build:android

    export OUTPUT_DIR=/Users/chenfeng/lib/android/zeromq-android

    2.1.1.    常见问题:config.sub和config.guess版本太旧

    问题:

    连续执行这两步

    ./autogen.sh

    ./configure --enable-static --disable-shared --host=arm-linux-androideabi --prefix=$OUTPUT_DIR --with-uuid=$OUTPUT_DIR LDFLAGS="-L$OUTPUT_DIR/lib" CPPFLAGS="-fPIC -I$OUTPUT_DIR/include" LIBS="-lgcc"

    执行后面一步时,提示

    checking host system type... Invalid configuration `arm-linux-androideabi': system `androideabi' not recognized

    configure: error: /bin/sh config/config.sub arm-linux-androideabi failed

    原因分析:

    是config.sub 和 config.guess这两个文件太旧。这是因为你画蛇添足地执行了./autogen.sh,导致config下的这两个文件被系统自带的覆盖。

    解决方案:

    以下两个都是可行的。

    l   执行./configure…之前不执行./autogen.sh。

    l   下载最新的config.guess和config.sub,覆盖系统自带的。

    n   到http://git.savannah.gnu.org/gitweb/?p=config.git;a=tree下载config.guess和config.sub两个文件

    n   将此两个文件拷贝到/usr/local/share/automake-1.11   //automake的安装目录

    n   然后执行前面两步

    2.2.     编译jzmq库

    由于我们并不会调用zmq的java接口。因此这一步并非必需。供

    2.2.1.    常见问题:未安装pkg-config

    问题:

    在执行./autogen.sh时找不到pkg-config

    解决方案:

    Get pkg-config from http://pkgconfig.freedesktop.org/releases/pkg-config-0.28.tar.gz
    Unzip this in home directory and pkg-config-0.22 will be created.
    Run the following commands:

    1. cd ~/pkg-config-0.22 
    2. ./configure --with-internal-glib
    3. make
    4. sudo make install

    2.2.2.    常见问题:找不到java include files

    问题:

    在执行./configure --host=arm-linux-androideabi --prefix=$OUTPUT_DIR --with-zeromq=$OUTPUT_DIR CPPFLAGS="-fPIC -I$OUTPUT_DIR/include" LDFLAGS="-L$OUTPUT_DIR/lib" --disable-version LIBS="-luuid"

    提示

    configure: error: cannot find java include files

    解决方案:

    export JAVA_HOME=`/usr/libexec/java_home -v 1.7`

    export JAVAC=$JAVA_HOME/bin/javac

    2.2.3.    常见问题:找不到jni_md.h

    问题:

    make时提示

    /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/include/jni.h:45:20: fatal error: jni_md.h: No such file or directory

    解决方案

    cd /Library/Java/JavaVirtualMachines/1.7.0.jdk/Contents/Home/include

    cp darwin/* .

    1. 编译protobuf

    依照zmq,依序执行:

    export PATH=/opt/android-toolchain/bin:$PATH

    export OUTPUT_DIR=/Users/chenfeng/lib/android/protobuf-android     //存放.h 和lib.a的目录

    ./configure --enable-static --disable-shared --host=arm-linux-androideabi --prefix=$OUTPUT_DIR LDFLAGS="-L$OUTPUT_DIR/lib" CPPFLAGS="-fPIC -I$OUTPUT_DIR/include" --enable-cross-compile --with-protoc=protoc LIBS="-lgcc"

    make

    make install

    与zmq的不同之处在于以上两个红字选项。这个是在参考多个文档后的总结。

    1. 编译cryptopp

    在macox上尝试失败,转而在ubuntu 12.04上编译。

    cryptopp与上述两个库的不同之处在于源代码工程没有用autotool这一套东西,因此无法通过为configure指定选项来生成交叉编译的makefile。因此,有两种方法可供选择。一种是修改makefile,另一种是在android工程中通过写jni的android.mk来编译。显然前者更为方便。

    参考http://morgwai.pl/ndkTutorial/

    对GNUmakefile作以下修改

    l   switch the target architecture (-march option) from native to armv5te

    l   remove linker option to use glibc pthreads (LDFLAGS += -pthread option)

    l   添加LDLIBS += -lgnustl_shared

    依序执行。

    export PATH=/opt/android-toolchain/bin:$PATH

    export CXX=/opt/android-toolchain/bin/arm-linux-androideabi-g++

    export PREFIX=/home/chenfeng/lib/android/cryptopp-android

    make

    make install

    1. 集成c++源代码和lib的android工程

    这是本篇最为困难的部分。前面说过,android重用c++库比ios复杂得多。因为obj-c与c++可以混合,而java与c++之间是隔离的,因此无法在java代码中直接生成c++对象。

    如果你对这一领域一片空白,建议你首先作两件事:

    5.1.     ovewview文档

    参考https://developer.android.com/tools/sdk/ndk/index.html的Exploring the hello-jni Sample这一章节

    参考下载的android-ndk-r8e/docs/OVERVIEW.html的III. NDK development in practice: 这一章节

    5.2.     典型sample

    建议参考下载的android-ndk-r8e/samples/two-libs这个例子。原因是它既生成了一个lib.a库,相当于我们这里的zmq/protobuf/cryptopp这些第三方库,又生成了一个lib.so库,相当于我们要重用的自身的库。

    有了以上基础,就可以动手开始编码了。与ios类似,这里要解决两个问题:java调用c++函数,c++回调java函数。如果像在大多数示例中展示的,由java对象调用c++函数,在该c++函数中直接回调该java对象的方法,那就太简单了。我们两个方向的调用是在不同的上下文中,由独立的事件触发。

    5.3.     java调用c++

    主要涉及两方面的工作。

    5.3.1.    一个独立的wrapper(或称adapter)c++文件

    以下是我的MsgAdapter.cpp片段。

    static MsgSender *msgSender;

    JavaVM *g_jvm;

    jobject listener = 0;

    extern "C" {

           JNIEXPORT void JNICALL Java_com_roadclouding_aholdem_ZMQService_makeMsgSender(JNIEnv* env, jobject thiz);

           …

                  JNIEXPORT void JNICALL Java_com_roadclouding_aholdem_ZMQService_00024ZMQThread_setListener(JNIEnv* env, jobject thiz, jobject jlistener);

    };

    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved)

    {

           g_jvm = jvm;  // cache the JavaVM pointer

           return JNI_VERSION_1_6;

    }

    JNIEXPORT void JNICALL Java_com_roadclouding_aholdem_ZMQService_makeMsgSender(JNIEnv* env, jobject thiz)

    {

        g_socketLocalSvr.bind("inproc://lifecycle");

        msgSender = new MsgSender(g_socketLocalSvr);

    }

    JNIEXPORT void JNICALL Java_com_roadclouding_aholdem_ZMQService_00024ZMQThread_setListener(JNIEnv* env, jobject thiz, jobject jlistener)

    {

           AndroidGameController *controller =  new AndroidGameController();

           msgDispatcher->setController(controller);

           listener = env->NewGlobalRef(jlistener);

           controller->_listener = listener;

    }

    注意几点:

    l   你可以在一个函数中生成全局c++对象,供以后在另一个函数中调用。如上面的msgSender。

    l   extern "C"不可以省略。

    l   JNIEXPORT void JNICALL不可以省略。

    l   Java的嵌套类的表示法为outerClass_00024innerClass。

    l   必须保存JavaVM *jvm供后续回调中使用。下节进一步解释。

    l   如果要保存java对象供后续引用,必须用NewGlobalRef把local reference转为global reference。

    5.3.2.    在java类中声明native c++函数

    这就比较简单,在声明前加native;在需要的地方直接调用,就像调用java函数一样。

    以下是我的代码片段。

    public class ZMQService extends Service {

           …

        private native void makeMsgSender();

        private native void sendMsg(String msg);

        private native void reconnect();

    private native void checkin();

    public class ConnectivityChangeReceiver extends BroadcastReceiver {

          @Override

          public void onReceive(Context context, Intent intent) {

                 if (isConnectedToInternet()) {

                        Log.d(TAG, "reconnect");

                        reconnect();

                        checkin();

                 }

          }

    }

    }

    5.4.     c++回调java

    也涉及两方面的工作。

    5.4.1.    取得java对象的方法入口

    c++回调java的复杂性已经部分体现在上一节中。g_jvm和用 NewGlobalRef 得到的listener就是为取得java对象的方法入口进而回调准备的。

    以下是我的代码片段,它由收到特定消息触发。

    extern JavaVM *g_jvm;

    void AndroidGameController::onCheckin()

    {

        JNIEnv * g_env;

        int getEnvStat = g_jvm->GetEnv((void **)&g_env, JNI_VERSION_1_6);

           …

        jclass cls = g_env->GetObjectClass(_listener);

        assert (cls != 0);

        jmethodID mid = g_env->GetMethodID(cls, "onCheckin", "()V");

        assert (mid != 0);

        g_env->CallVoidMethod(_listener, mid);

           …

    }

    注意,由于取得对象和方法的入口必须用到JNIEnv,这就是上一步要保存JavaVM的原因,由它通过GetEnv来取得。

    5.4.2.    在java中实现回调函数

    这个非常简单。

    public class ViewMsgListener implements MsgListener {

           @Override

           public void onCheckin() {

                  // TODO Auto-generated method stub

                  Log.d(TAG, "ViewMsgListener onCheckin");

           }

    }

    5.5.     编译调试常见问题

    编译也分为两步。

    l   ndk-build把c++文件编译出lib.so

    l   在eclipse环境下与编译纯java一样编译整个工程。

    中间碰到了不少问题。

    5.5.1.    不认识string

    问题:

    fatal error: string: No such file or directory

    解决方案:

    这是没有加入stl库导致的。

    Create a "Application.mk" file and write "APP_STL := gnustl_static " in it.

    用APP_STL:= stlport_static可以解决这个问题,但产生下面这个问题。

    5.5.2.    stl库不兼容

    问题:

    /Users/chenfeng/program/android-ndk-r8e/sources/cxx-stl/stlport/stlport/stl/_cstdlib.h:131:13: error: conflicting types for 'abs'

    解决方案:

    Application.mk中用APP_STL:= gnustl_static取代APP_STL := stlport_static

    5.5.3.    不认识'namespace'

    问题:

    /Users/chenfeng/program/android-ndk-r8e/sources/cxx-stl/gnu-libstdc++/4.6/include/bits/stringfwd.h:43:1: error: unknown type name 'namespace'

    解决方案:

    这是因为它被当成c文件。

    把.c重命名为.cpp就可以了。

    按下葫芦起了瓢,出现以下问题。

    5.5.4.    函数原型不一致

    问题:

    error: base operand of '->' has non-pointer type 'JNIEnv {aka _JNIEnv}'

    解决方案:

    这是因为'JNIEnv在c和c++下的宏定义不同。

    把适用于c的语法:const char *str = (*env)->GetStringUTFChars(env, prompt, 0);

    改为适用于c++的语法:const char *str = env->GetStringUTFChars(msg, 0);

    5.5.5.    未链接stl库

    问题:

    stl_tree.h:1013: error: undefined reference to 'std::_Rb_tree_insert_and_rebalance(bool, std::_Rb_tree_node_base*, std::_Rb_tree_node_base*, std::_Rb_tree_node_base&)'

    解决方案:

    拷贝android-ndk-r8e/sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a下的 libgnustl_static.a到工程里,并在android.mk中指定

    LOCAL_LDFLAGS += -L$(LOCAL_PATH)/network         //你拷贝目的地的工程的子目录

    LOCAL_LDLIBS := … -lgnustl_static

    还碰到其它问题,google搜索都能搞定。

  • 相关阅读:
    BZOJ2648: SJY摆棋子
    BZOJ1925: [Sdoi2010]地精部落
    BZOJ1941: [Sdoi2010]Hide and Seek
    BZOJ2434: [Noi2011]阿狸的打字机
    BZOJ3295: [Cqoi2011]动态逆序对
    BZOJ1406: [AHOI2007]密码箱
    BZOJ1115: [POI2009]石子游戏Kam
    BZOJ1531: [POI2005]Bank notes
    BZOJ2730: [HNOI2012]矿场搭建
    计算几何《简单》入土芝士
  • 原文地址:https://www.cnblogs.com/mobileinternet/p/3209534.html
Copyright © 2011-2022 走看看