标签归档:NDK

Android native代码单元测试

Android为Java层应用程序提供了基于JUnit的单元测试框架,本文探讨的是native代码的单元测试。native代码的单元测试框架选择很多,如CppUnit/UnitTest++/gtest等等。惭愧的是,虽然一直在android下从事native程序的开发,却一直没有写过单元测试用例,究其原因,主要有两个:一是赶项目进度,上头关心的是代码何时完成/功能何时完成,这也符合中国的国情,第一时间忽悠客户才是要务。至于代码质量,no one care,只要演示时不出状况就行,至于问题,可以慢慢解决;二是没有顺手的单元测试工具,从单元测试用例的编写,到部署,都是一个非常耗时的工作,如果没有工具自动化,积极性容易受挫。

其实写代码的人都知道,bug发现越晚,修复的代价越大,但是习惯上的惰性使得我将排查问题的时机尽量拖后,基本上没有写过单元测试用例。近期一直在关注chromium for android项目,左等右看,就是看不到完整的android移植代码。兵书上说,兵马未动,粮草先行,最先出现的android编译目标就是base_unittests,细看代码,chromium代码中有着非常多的unittest代码,使用了gtest/gmock单元测试框架。为了达到自动化测试的目的,src/build/android下还有大量的python脚本,可以实现编译/部署/运行的一系列动作的自动化。

chromium项目非常的庞大,为了能够在android native项目中使用它的单元测试框架,我从chromium中抽取出gtest/gmock/base等代码,使用ndk编译成库,还提取了build下的python脚本,稍微做了些修改。现将相关代码放到github,有兴趣的可以访问https://github.com/mogoweb/ndk-gtest.

gtest单元测试用例通常在命令行程序中运行,但有时需要测试Jni调用接口,这时就需要APK包了。这带来了一个问题,因为gtest默认是往stdout/stderr输出信息的,而Android系统默认将送往stdout/stderr的输出输向/dev/null。为了能够看到gtest信息,需要重定向输出。可以使用如下命令做到:

$ adb shell stop
$ adb shell setprop log.redirect-stdio true
$ adb shell start

使用ndk-stack追踪程序崩溃

程序崩溃无疑是程序员最头疼的事情,而android native程序崩溃简直是令程序员崩溃。Android java程序在异常之前还打印出代码调用栈,让程序员有迹可寻,结合单步调试,定位问题相对容易些。而native程序崩溃,只会打印出一段天书,让人摸不着头脑。比如,下面就是一段native程序异常后,在logcat中打印出的信息:

09-01 07:20:39.170: INFO/DEBUG(31): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
09-01 07:20:39.170: INFO/DEBUG(31): Build fingerprint: ‘generic/sdk/generic:2.3.3/GRI34/101070:eng/test-keys’
09-01 07:20:39.180: INFO/DEBUG(31): pid: 339, tid: 347  >>> mogoweb.browser.app <<<
09-01 07:20:39.180: INFO/DEBUG(31): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr bbadbeef
09-01 07:20:39.180: INFO/DEBUG(31):  r0 00000093  r1 ffffff1c  r2 00000000  r3 bbadbeef
09-01 07:20:39.180: INFO/DEBUG(31):  r4 0029d378  r5 0029d2c0  r6 00000000  r7 43eaeeb4
09-01 07:20:39.180: INFO/DEBUG(31):  r8 43fcdb4c  r9 43eaeea8  10 43eaee90  fp 43fcda3c
09-01 07:20:39.180: INFO/DEBUG(31):  ip 00000029  sp 43fcda28  lr 822d6ee8  pc 822d6ef0  cpsr 00000010
09-01 07:20:39.270: INFO/ActivityManager(61): Displayed mogoweb.browser.app/.BrowserActivity: +1s98ms
09-01 07:20:39.669: INFO/DEBUG(31):          #00  pc 002d6ef0  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.669: INFO/DEBUG(31):          #01  lr 822d6ee8  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.669: INFO/DEBUG(31): code around pc:
09-01 07:20:39.669: INFO/DEBUG(31): 822d6ed0 e08f3003 e1a02003 e59f3038 e08f3003
09-01 07:20:39.669: INFO/DEBUG(31): 822d6ee0 eb2a28bf eb2a290b e59f302c e3a02000
09-01 07:20:39.669: INFO/DEBUG(31): 822d6ef0 e5832000 e3a03000 e12fff33 e51b3008
09-01 07:20:39.669: INFO/DEBUG(31): 822d6f00 e1a00003 e24bd004 e8bd8800 0173faf0
09-01 07:20:39.669: INFO/DEBUG(31): 822d6f10 00cea68c 00cec48c 00cea6c0 bbadbeef
09-01 07:20:39.669: INFO/DEBUG(31): code around lr:
09-01 07:20:39.669: INFO/DEBUG(31): 822d6ec8 e3a01040 e59f3040 e08f3003 e1a02003
09-01 07:20:39.669: INFO/DEBUG(31): 822d6ed8 e59f3038 e08f3003 eb2a28bf eb2a290b
09-01 07:20:39.669: INFO/DEBUG(31): 822d6ee8 e59f302c e3a02000 e5832000 e3a03000
09-01 07:20:39.669: INFO/DEBUG(31): 822d6ef8 e12fff33 e51b3008 e1a00003 e24bd004
09-01 07:20:39.679: INFO/DEBUG(31): 822d6f08 e8bd8800 0173faf0 00cea68c 00cec48c
09-01 07:20:39.679: INFO/DEBUG(31): stack:
09-01 07:20:39.679: INFO/DEBUG(31):     43fcd9e8  83875a54  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.679: INFO/DEBUG(31):     43fcd9ec  82fc1554  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.679: INFO/DEBUG(31):     43fcd9f0  00000040 
09-01 07:20:39.679: INFO/DEBUG(31):     43fcd9f4  82fc3364  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.679: INFO/DEBUG(31):     43fcd9f8  43fcda24 
09-01 07:20:39.679: INFO/DEBUG(31):     43fcd9fc  82fc3364  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.679: INFO/DEBUG(31):     43fcda00  00000040 
09-01 07:20:39.679: INFO/DEBUG(31):     43fcda04  82fc1554  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.679: INFO/DEBUG(31):     43fcda08  43fcda24 
09-01 07:20:39.679: INFO/DEBUG(31):     43fcda0c  82d61244  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.679: INFO/DEBUG(31):     43fcda10  82fc15a4  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.679: INFO/DEBUG(31):     43fcda14  82fc3364  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.679: INFO/DEBUG(31):     43fcda18  00000040 
09-01 07:20:39.679: INFO/DEBUG(31):     43fcda1c  82fc1554  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.679: INFO/DEBUG(31):     43fcda20  df002777 
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda24  e3a070ad 
09-01 07:20:39.690: INFO/DEBUG(31): #00 43fcda28  4051f020  /dev/ashmem/dalvik-heap (deleted)
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda2c  00000000 
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda30  0029d2c0  [heap]
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda34  0029d378  [heap]
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda38  43fcdaac 
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda3c  82330ec0  /data/data/mogoweb.browser.app/lib/libmogowebcore.so
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda40  43fcdac8 
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda44  0029d2c0  [heap]
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda48  00000003 
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda4c  0029d2c0  [heap]
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda50  c0000000 
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda54  000000da 
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda58  43fcdb4c 
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda5c  43eaeea8 
09-01 07:20:39.690: INFO/DEBUG(31):     43fcda60  43eaee90 
09-01 07:20:39.699: INFO/DEBUG(31):     43fcda64  afd1413f  /system/lib/libc.so
09-01 07:20:39.699: INFO/DEBUG(31):     43fcda68  40098e88  /dev/ashmem/dalvik-heap (deleted)
09-01 07:20:39.699: INFO/DEBUG(31):     43fcda6c  800680e3  /system/lib/libdvm.so
09-01 07:20:40.800: DEBUG/Zygote(33): Process 339 terminated by signal (11)
09-01 07:20:40.810: INFO/ActivityManager(61): Process mogoweb.browser.app (pid 339) has died.

从NDK r5b开始,增加了调试的支持,引入了ndk-gdb脚本,可以单步调试程序。在单步调试了hello-jni后,欣喜若狂,以为结束了之前用log调试代码的痛苦日子。但在浏览器项目中使用ndk-gdb,却死活无法调试,至今都不明白是因为程序太大,还是因为程序中有多线程的代码导致的。

NDK r6给我们带来了一个惊喜,那就是ndk-stack工具,其作用就是将上面的天书翻译成我们能懂的描述。下面就看看ndk-stack是如何使用的吧。

首先,要求动态链接库带调试信息(注:并不要求在模拟器/设备中的动态库带调式信息,放心的strip掉调试信息)。如果是用的ndk-build编译native代码,在$PROJECT_PATH/obj/local/<ab>下就有,<ab>代表设备的ABI(比如,缺省就是armeabi)。如果是用的cmake编译native代码,需要将CMAKE_BUILD_TYPE定义成Debug,判断是否编译了带调试信息的版本,可以检查最后的编译命令有没有带-g参数。编译出的so通常位于$PROJECT_PATH/libs/<ab>下。

接下来输入如下命令,指定带调式符号的so所在的路径(用$SYMBOL_SO_PATH指代):

adb logcat | ndk-stack –sym $SYMBOL_SO_PATH

下面就是之前的一段天书翻译出来的结果:

********** Crash dump: **********
Build fingerprint: ‘generic/sdk/generic:2.3.3/GRI34/101070:eng/test-keys’
pid: 339, tid: 347  >>> mogoweb.browser.app <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr bbadbeef
Stack frame #00  pc 002d6ef0  /data/data/mogoweb.browser.app/lib/libmogowebcore.so: Routine Chrome in /home/alex/webkit/android2.3/src/third/WebKit/Source/WebCore/page/Chrome.cpp:67

对比当初调试浏览器代码,为了定位程序崩溃的位置,从代码入口开始一路printf,这无疑是一个重大的进步。

webkit android移植(1):开篇

看到这个标题,您可能会觉得奇怪,android浏览器本来就是基于webkit的,而且android还提供了一个非常方便的WebView及相关类。系统的浏览器当然稳定而且强大,但在某些情况下,可能还是不够,比如android浏览器不支持wml,也不支持html5 video。其实webkit本身是支持的,只不过android没有打开编译开关。另外一个原因,就是android中使用的webkit内核比较老,对于html5的支持比较差。因此萌发了自己编译libwebcore,作为独立的应用进行开发。这样做的一个缺点就是和android平台相关了,没有办法做到2.1/2.2/2.3通用。

首先说说开发环境,webkit选择android4.0版本包含的webkit。构建系统选择的是android的源码构建系统。这样选择也是为了选择一个稳定的版本,不在build system上花费太多的时间。开发机环境为ubuntu 11.10,64位系统。

阅读webkit android的代码,我们可以知道,实际上浏览器的代码包括两部分:一部分是java代码,是对webkit的一个封装,提供方便使用的WebView及相关类;一部分是c++代码,也就是webkit代码(包括WebCore和JavaScriptCore,在android 2.2之后,google用V8取代了JavaScriptCore),编译成so,供Java侧代码使用。

分析webkit的代码接口,主要需要做如下工作:

1. WTF库的移植

2. WebCore platform代码的移植

3. WebCore核心代码的移植

4. WebCore Support代码的实现

5. WebKit JNI

6. WebKit Java封装

使用CMake进行android native开发

Android NDK中提供了ndk-build脚本,以及若干mk文件,以简化ndk的开发,这对于开发一些小型应用来说足够了,但是对于一些大型项目,特别是涉及到很多第三方库时,管理起来就不是那么方便了(个人意见,Makefile写得好的人,可以无视)。在linux开源界,用得比较广泛的就是automake和cmake。本篇文章就是介绍如何使用cmake构建native程序的。

我之前写了一篇博客使用CMake构建android原生库,不过介绍的方法比较繁琐,最近在google code看到一个项目android-cmake,里面提供了脚本,这样使用起来就更方便了。下面就是介绍android-cmake的安装及使用方法。首先交代一下我的工作环境,ubuntu 10.10 32bit、Android NDK r6及Android SDK r12。接下来的步骤是:

1)下载android-cmake

    hg clone https://code.google.com/p/android-cmake/ $HOME/android/android-cmake

2)使用NDK创建单独的工具链

    export NDK=~/android/android-ndk-r6

    $NDK/build/tools/make-standalone-toolchain.sh -platform=android-5 -install-dir=$HOME/android/android-toolchain

3)将如下代码加入$HOME/.profile

    export ANDROID_NDK_TOOLCHAIN_ROOT=$HOME/android/android-toolchain

    export ANDTOOLCHAIN=$HOME/android/android-cmake/toolchain/android.toolchain.cmake

    alias android-cmake=’cmake –DCMAKE_TOOLCHAIN_FILE=$ANDTOOLCHAIN ’

android-cmake包含了若干示例,从helloworld到复杂的boost库,先从一个简单的hello-cmake开始吧。

    cd samples/hello-cmake

    mkdir androidbuild

    cd androidbuild

    android-cmake ..

    make

即可编译出libhello-cmake.so,位于libs/armeabi-v7a。可以使用cmake GUI来修改某些设置:

    cmake-gui ..

image 

注:这个例子只包含了java和jni的代码,并没有包括android工程,android工程在hello-android-cmake目录下。

在编译过程中,可能会出现如下错误:

- SWIG is not found
- The C compiler identification is GNU
- The CXX compiler identification is GNU
- Check for working C compiler: /home/alex/android/android-toolchain/bin/arm-linux-androideabi-gcc
- Check for working C compiler: /home/alex/android/android-toolchain/bin/arm-linux-androideabi-gcc — works
- Detecting C compiler ABI info
CMake Error: Could not COPY_FILE.
  OutputFile: ”
    copyFile: ‘/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeDetermineCompilerABI_C.bin’
Unable to find executable for try_compile: tried "/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeTmp/cmTryCompileExec" and "/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeTmp/Debug/cmTryCompileExec" and "/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeTmp/Development/cmTryCompileExec".
- Detecting C compiler ABI info - done
CMake Error at /usr/share/cmake-2.8/Modules/CMakeDetermineCompilerABI.cmake:40 (FILE):
  file STRINGS file
  "/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeDetermineCompilerABI_C.bin"
  cannot be read.
Call Stack (most recent call first):
  /usr/share/cmake-2.8/Modules/CMakeTestCCompiler.cmake:71 (CMAKE_DETERMINE_COMPILER_ABI)
  CMakeLists.txt:3 (project)
- Check for working CXX compiler: /home/alex/android/android-toolchain/bin/arm-linux-androideabi-g++
- Check for working CXX compiler: /home/alex/android/android-toolchain/bin/arm-linux-androideabi-g++ — works
- Detecting CXX compiler ABI info
CMake Error: Could not COPY_FILE.
  OutputFile: ”
    copyFile: ‘/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeDetermineCompilerABI_CXX.bin’
Unable to find executable for try_compile: tried "/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeTmp/cmTryCompileExec" and "/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeTmp/Debug/cmTryCompileExec" and "/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeTmp/Development/cmTryCompileExec".
- Detecting CXX compiler ABI info - done
CMake Error at /usr/share/cmake-2.8/Modules/CMakeDetermineCompilerABI.cmake:40 (FILE):
  file STRINGS file
  "/home/alex/android/android-cmake/samples/hello-cmake/androidbuild/CMakeFiles/CMakeDetermineCompilerABI_CXX.bin"
  cannot be read.
Call Stack (most recent call first):
  /usr/share/cmake-2.8/Modules/CMakeTestCXXCompiler.cmake:64 (CMAKE_DETERMINE_COMPILER_ABI)
  CMakeLists.txt:3 (project)
- Configuring incomplete, errors occurred!

原因在于在测试编译器时,链接后的程序默认放在bin/armeabi-v7a目录下,而测试代码在另外的目录下去查找生成文件,解决的方法是修改$ANDTOOLCHAIN文件,将

set( EXECUTABLE_OUTPUT_PATH ${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ARMEABI_NDK_NAME} CACHE PATH "Output directory for applications" FORCE)

改成
set( EXECUTABLE_OUTPUT_PATH ${LIBRARY_OUTPUT_PATH_ROOT} CACHE PATH "Output directory for applications" FORCE)

补充:

在使用cmake构建webkit android porting时,出现错误:

Could NOT find BISON (missing: BISON_EXECUTABLE)

原因在于$ANDTOOLCHAIN文件中修改了CMake系统变量:

set( CMAKE_FIND_ROOT_PATH_MODE_PROGRAM ONLY )

解决的方法是将代码中这样的语句注释掉。

android-ndk-r5c发布

一段时间没有关注android了,今天上android官方网站,发现好多更新呀。android 2.3.4和android 3.1的SDK都已经出来了,我比较关注的ndk也更新到了r5c,大致浏览了一下android-ndk-r5c的changes,没有新的特性,只是修复了r5b的一些bug。

重要的bug修复有:

  • 修正一处输入错误,该错误使得LOCAL_WHOLE_STATIC_LIBRARIES不能在新的工具链上正常工作,也修正了docs/ADROID-MK.html中关于该变量的说明。
  • 修正了一处错误,链接了gnustl_static的代码在android2.2之前的平台上运行会崩溃。
  • android/input.h: 该头文件中自API 9(即android 2.3)中引入的两个函数不正确,本次得到修复,这破坏了源码的API接口,但对系统的二进制接口没有变化。

    这两个函数缺失了第三个参数’history_index’,它们正确的定义为:

float AMotionEvent_getHistoricalRawX(const AInputEvent* motion_event,
                                     size_t pointer_index,
                                     size_t history_index);

float AMotionEvent_getHistoricalRawY(const AInputEvent* motion_event,
                                     size_t pointer_index,
                                     size_t history_index);

  • 更新android-9 C库的arm二进制文件,在链接时暴露一些gingerbread才加入的新函数(比如pthread_rwlock_init)。
  • 修复一处使得gdbserver在某些HoneyComb设备(如Motorola Xoom)上崩溃的bug。

其它修复:

  • object文件总是按照它们出现在LOCAL_SRC_FILES的次序连接。之前的版本并不是这样,因为文件根据源文件后缀进行了分组。
  • download-toolchain-sources.sh: 修正一处愚蠢的错误,该错误导致-git-date参数在下载主分支时不工作。
  • 修复一个当模块导入自身而引起GNU Make死循环的问题。
  • 当import-modules失败时,打印出搜索路径。这对检查build系统的NDK_MODULE_PATH定义是否正确非常有用。
  • 当import-modules成功时,打印该模所在的路径到日志(在NDK_LOG=1时可以看到)。
  • <pthread.h>: 修复API level 9及以上的PTHREAD_RWLOCK_INITIALIZER定义。
  • 修复一个bug,如果LOCAL_ARM_NEON定义为true,build会失败(build/core/build-binary.mk的输入错误)。
  • 修复一处不编译.s汇编文件(但.S文件正确)的bug。
  • 提高在工程中包含很多头文件路径时,编译debug版本的速度。
  • ndk-gdb: 更好的检查’adb shell’失败(更好的错误信息)。
  • ndk-build: 修复一个很难遇到的bug,该bug在并行build可调试工程可能会出现。