当你为全世界的iPhone和iPad用户推出基于C/C++的iOS游戏时,你怎么忍心让忠实的Android用户无法享受同样的乐趣?我不能,所以我得把《Pop
Corny》移植到Android平台。这是一次有趣的经历,让我受益匪浅,所以我要把我的心得体会分享给读者。

 

英文文档见android-ndk-r5b的documentation.html

澳门太阳集团 1

1、Android
NDK

属于Android Native Development Kit (NDK)的一部分

pop corny(from harryballs.com)

 一、NDK产生的背景

基础

  Android平台从诞生起,就已经支持C、C++开发。众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三方应用都必须使用Java语言。但这并不等同于“第三方应用只能使用Java”。在Android SDK首次发布时,Google就宣称其虚拟机Dalvik支持JNI编程方式,也就是第三方应用完全可以通过JNI调用自己的C动态库,即在Android平台上,“Java+C”的编程方式是一直都可以实现的。

翻译仅个人见解

首先,如果你安于xcode、苹果生态圈的舒适开发环境,现在准备飞向那片叫作Android的大陆,那么,请准备迎接困难和挑战吧,因为Android提供的工具并不那么合理,且基本上没有文件编制。

  不过,Google也表示,使用原生SDK编程相比Dalvik虚拟机也有一些劣势,Android SDK文档里,找不到任何JNI方面的帮助。即使第三方应用开发者使用JNI完成了自己的C动态链接库(so)开发,但是so如何和应用程序一起打包成apk并发布?这里面也存在技术障碍。比如程序更加复杂,兼容性难以保障,无法访问Framework
API,Debug难度更大等。开发者需要自行斟酌使用。


NDK(开发Android的本地应用的工具链和类库)和SDK(软件开发工具包)没有关系。显然,为了使原生代码支持Android平台,谷歌已经很努力了,但我们开发原生代码还是不如用Java来得便利。

  于是NDK就应运而生了。NDK全称是Native Development Kit。

Android NDK Stable APIs:

生成工具和进程非常重要。谷歌给NDK的开发者提供的工具,与构建Android平台的工具相同,这里的工具我指的是一套壳脚本和生成文件。

  NDK的发布,使“Java+C”的开发方式终于转正,成为官方支持的开发方式。NDK将是Android平台支持C开发的开端。

 

为了生成你的项目,你要做的是编写生成文件部分,这部分包括由NDK提供的主要生成文件。这使用户的学习难度增大,一开始就可能吓倒一些人。然而,当你掌握它以后,你就会觉得还不错,之后构建你的自定义生成文件时,你可能会觉得更好了。

 

Android NDK稳定API:

最后,你建成的是一个动态链接库,Dalvic可以用JNI(Java的本地界面)装载它。对啊,你的游戏仍然是调用库的Dalvic
Java VM进程。

二、为什么使用NDK

========================

混合Java和本地代码

  1.代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。

 

所以你不能全完摆脱Java。你的代码必须与之兼容,这其实正是你想要的,因为几乎所有Android应用程序界面(API)仍然只用Java编写。另外,你可能想使用的大多数Android库也是用Java编写的。例如,如果你想采用Openfeint的排行榜和成就功能,使用Flurry分析工具,你就必须与Java打交道。

  2.可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。

This is the list of stable APIs/ABIs exposed by the Android NDK.

这是用Java Native Interface
(JNI)完成的。JNI使在VM中运行的Java代码可以被用C/C++编写的本地代码调出和调回。以下是代码如何从本地代码中调出Dashboard.open()
的例子:

  3.提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。

 

jclass cls =
javaEnv->FindClass(“com/openfeint/api/ui/Dashboard”);
jmethodID open = javaEnv->GetStaticMethodID(cls, “open”, “()V”);
javaEnv->CallStaticVoidMethod(cls, open);

  4.便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

这是Android NDK暴露的稳定API/ABI列表

以上代码唯一的问题是”()V”, 它是类函数的内部类型的署名。这是Java
VM描述参数和类函数返回值的方法。

 

 

这种语法很容易出错,我建议你始终使用”javap -s
myclass”指令,它将所有类函数与它们的署名一同输出。从那里复制和粘贴。记住,如果你拼错了一个署名,你就只能在运行时发现。

三、NDK简介

I. Purpose:

即使最新版的NDK允许你用全本地代码写一个活动,我仍然会按老方法在Java中写活动,然后从那里调用本地代码。

       1.NDK是一系列工具的集合

 

输入

      
NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。

一、目的:

在Android上,处理触摸输入比在iOS上更复杂一点儿,因为Android设计师认为有一个直接调用一系列“历史”触摸事件的系统比较酷,而不是让你挨个调用。除此之外,其他都是一样的。你只需要确保你用了ACTIONUP和
ACTIONPOINTER_UP事件。

NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。


然而,存在于移植细节的许多其他方面的大问题是,这些事件是不同线程的。这可能会使一些iOS开发者感到吃惊,因为他们习惯于让几乎所有事件都发生在主线程循环中。

NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

 

至少我是很意外,Android对线程非常大方。所以你要根据自己引擎的编码方式来排列事件,然后将它们从相应线程中传送到你的本地代码。

      
2.NDK提供了一份稳定、功能有限的API头文件声明

Each API corresponds to a set of headers files, and a shared library
file that contains the corresponding implementation, and which must be
linked against by your native code.

最后,还有按钮,即真正的硬件按钮——触摸。至少是后退键和主按键,要确保它们符合Android用户的操作习惯。

      
Google明确声明该API是稳定的,在后续所有版本中都稳定支持当前发布的API。从该版本的NDK中看出,这些API支持的功能非常有限,包含有:C标准库(libc)、标准数学库(libm)、压缩库(libz)、Log库(liblog)。

 

澳门太阳集团 2

 

每个API对应一组头文件,以及包含对应实现的动态库,它必须被你的原生代码链接。

Apple Android(from 2-soft.com)

四、NDK开发环境的搭建

 

声音

1.下载安装Android NDK

For example, to use system library “Foo”, you would include a header
like <foo.h> in your code, then tell the build system that your
native module needs to link to /system/lib/libfoo.so at load-time by
adding the following line to your Android.mk file:

这是Android让我吃惊的另一点。请做好思想准备——居然没有OpenAL!你一定难以置信,一脸绝望,不敢接受这个事实。

 地址:

 

但这就是真相。如果你希望轻松地将基于OpenAL的声音引擎移植到Android,恐怕你要大大地失望了。我认为这跟某些版权有关。所以,你能选择的只有MediaPlayer、SoundPool和OpenSL
ES了。前两个是Java API,第三个是本地API。

 

例如,为了使用系统库“Foo”,你会在你的代码中包含头文件如<foo.h>,然后通过添加以下行到你的Android.mk文件以告诉构建系统你的原生模块需要在加载期链接到/system/lib/libfoo.so:

MediaPlayer基本上是用于播放不需要低延迟的音乐和声音。我本可以用它播放音乐,但我决定尝试OpenSL。我试过OpenSL的引擎的音乐播放部分后,觉得不喜欢它的API。如果我一开始就知道,我就会直接选择非常简单的MediaPlayer。

2、Android JNI

 

SoundPool非常适合播放音效。它还帮你解压了声音,在内存中储存未压缩的现成样本。

 native    国的;土著的;天然的;与生俱来的;天赋的

 Native
App 本地应用 ; 原生应用 ; 客户端应用 ; 原生应用程式

 

一:基本概念

JNI :
Java Native Interface
(JNI)标准是java平台的一部分,它允许Java代码和其他语言写的代码进行交互。

       
 JNI 是本地编程接口,它使得在 Java 虚拟机 (VM) 内部运行的 Java
代码能够与用其它编程语言(如 C、C++
和汇编语言)编写的应用程序和库进行交互操作。

 

二:Android JNI
一般用途为:

1、主要是一些算法,因为c/c++比Java效率高,所以应用运行起来速度比较快,特别是一些游戏中的算法。
2、为了保密,都知道apk都可以被反编译,就算有代码混淆,也只是难看懂,并不是完全看不懂,但用jni编译成.so就不同了,所以可以达到商业机密不泄露的目的。
3、一个平台(C++代码)迁移到Android平台,底层逻辑是相同的,这样就可以通过移植,利用JNI调用底层C++代码,避免相同逻辑的代码重复去写,不过这个过程一定要注意底层对象的释放问题。

 

 3、NDK 与 JNI
的关系

 

JNI是java语言提供的Java和C/C++相互沟通的机制,Java可以通过JNI调用本地的C/C++代码,本地的C/C++的代码也可以调用java代码。

JNI
是本地编程接口,Java和C/C++互相通过的接口。Java通过C/C++使用本地的代码的一个关键性原因在于C/C++代码的高效性。

 

NDK是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的。

它集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。

它可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。

 

  LOCAL_LDLIBS := -lfoo

但它还是有自己的缺陷,在我的测试中,它不能支持超过1MB的效果。SoundPool之后还有一个很糟的历史记录。因为代码的紊乱情况,SoundPool会让所有在第一代双核手机中运行它的应用程序崩溃!最为典型的就是运行vanilla
Android版本的三星Galaxy S2。

 

你能想象吗?在店里,你的游戏运行得好好的,但有一天,让你的游戏崩溃的手机卖出了数百万台!三星在一年之后才解决了这个问题。从那以后,游戏开发者不得不放弃SoundPool,在OpenSL
ES上执行相同的功能。我跟你说过了,OpenSL ES并不好玩。

Note that the build system automatically links the C library, the Math
library and the C++ support library to your native code, there is no
need to list them in a LOCAL_LDLIBS line.

最坏的是,即使是现在,三星发布的更新版本Android不会有这样的问题了,但大多数用户都没有更新操作系统。所以甚至是在上个月,当我发布《Pop
Corny》时,大多数三星Galaxy
S2的SoundPool还是有漏洞。我决定不放弃SoundPool,在运行有漏洞版本的SoundPool时进行简单的检测,并且完全不播放音效。

 

图像

注意构建系统自动链接C库,数学库以及C++支持库到你的原生代码,所以没必要在LOCAL_LDLIBS配置行中列出它们。

谢天谢地,Android确实支持OpenGL!这下没问题了。但你还是要小心Android的多线程特点,这样就没事了(所有GL指令都必须来自GL线程)。

 

但你必须准备好对付各种Android手机和平板电脑的分辨率。你不再生活在iOS的生态系统中了,所以你要解决的不只是两种高宽比(iPhone和iPad)的问题了。

There are several “API Levels” defined. Each API level corresponds to a
given Android system platform release. The following levels are
currently supported:

对于《Pop
Corny》,游戏已经支持iPhone和iPad的高宽比了,所以我只让代码接受某个范围的高宽比,之后增加必要的黑条。

 

澳门太阳集团 3

定义了几个API级别。每个API级别对应一个给定的Android系统平台发布版。以下级别是当前支持的:

screen-sizes(from gamasutra)

 

例如,某些手机拥有480×854像素的古怪分辨率,不重新设计整个游戏居然就不能解决这一问题。所以,游戏在这些手机上显示的是黑条。

    android-3      -> Official Android 1.5 system images

只载入适当的MipMap或更低级的纹理,也非常有用,但这取决于屏幕的分辨率。这会节省宝贵的内存,特别是对于低端设备,因为它们的屏幕分辨率低。

 

当移植到Android时,你遇到的OpenGL主要问题是,处理活动生命周期。你可能已经知道了,Android上的任何事件都算一个活动。即使是一个小对话框也是一个活动。

    android-3      -> 官方Android 1.5系统镜像

问题是,当对话框出现时,它就会中断你的当前活动,并且如果那个活动是你的OpenGL视图,Android就会消除你的OpenGL活动!

 

这意味着,当对话框消失后,要返回你刚才的活动,你不得不重新载入所有OpenGL的资源。当用户后台运行你的游戏时,或当用户在游戏运行时打电话,相同的问题出现了。

    android-4      -> Official Android 1.6 system images

每次都要再次载入所有纹理,这是无论如何也不能接受的。我想了好一阵子才想出解决办法。这可能是因为我没有Android设备做测试,所以我只能依靠低beta测试器反复测试。

 

无论如何,3.0版的Android最终解决这个问题了。那个版本的GLViewSurface(游戏邦注:GLSurfaceView的作用是使用户能更容易更好地使用OpenGL渲染应用程序)加了一个名为setPreserveEGLContextOnPause(boolean)
的方法,当开启时,它就会保存GL活动。

    android-4      -> 官方Android 1.6系统镜像

但你知道在Android生态系统中很少人会升级操作系统。所以我要做的就是,从Android最新资源中获取GLSurfaceView的类,做些调整,然后使用,而不是使用用户手机中的。就这么简单。

 

然而,即使是那样,许多手机还是丢失了GL活动。结果是,当GPU是Adreno时,无论GPU是否支持多活动,GLSurfaceView都不能保存活动。

    android-5      -> Official Android 2.0 system images

好吧,我尝试的所有基于Adreno的设备都可以保存活动,只要移除在GLSurfaceView的资源中的测试,使游戏在活动中断后继续进行。

 

资源

    android-5      -> 官方Android 2.0系统镜像    

移植大业的最后一个障碍是,资源管理和载入。使用过iOS的人会很惊讶地发现,当安装程序时,Android居然不会像iOS那样解压程序包。

 

文件仍将保持.apk状态,但它实际上是一个zip文件。这引发了一连串的问题。你不能只是用自己信任的系统打开文件并读取。你必须打开apk文件,然后挨个寻找你的文件,解压,最后再使用。

    android-6      -> Official Android 2.0.1 system images

对于某些文件,你可以跳过解压部分,即某类构建过程储存未压缩成apk的文件。大多数媒体文件都已经压缩了。如果你使用ant构建,你其实可以在无压缩的列表当中添加更多文件拓展名。

 

不幸的是,我对Eclipse(游戏邦注:著名的跨平台自由集成开发环境)没办法做同样的事。使用apk的文件描述符、字符补偿和长度(可以从Java资源管理器中获得),可以轻松地载入这些文件(使用常用文件处理功能)。

    android-6      -> 官方Android 2.0.1系统镜像    

至于压缩的文件,你却不得不用Java资源管理器完全地载入,然后使用JNI将所有文件数据复制成C语言,这样效率会很低。

 

所幸的是,继2.3版本之后,谷歌加强了本地资源载入能力。所以如果你的设备只支持2.3或以上版本,你可以忽略以上问题,直接使用本地API。它会帮你解决所有问题。

    android-7      -> Official Android 2.1 system images

总结

 

正如你所见,Android平台有它自己的缺陷。大多数时候是因为NDK还不够成熟。不过,随着新版本的发布,它会越来越完善。当然,Android用户最好能勤快一点地更新版本……

    android-7      -> 官方Android 2.1系统镜像

对于以上所有问题,你可能想编译三个不同的CPU:ARM、ARM7和x86。现在仅有一些支持x86的平板电脑,但假以时日,我们还会看到更多这样的平板电脑。

 

如果你原本是开发iOS游戏,但移植到Android版本时不认真处理的话,字节顺序可能还是会给你带来一些麻烦。但这不是因为字节顺序的不同,而主要是因为会检测它的iOS/OSX特定C语言定义。

    android-8      -> Official Android 2.2 system images

有时候会有一点儿麻烦,但努力总会得到回报的。最后,一个全新的世界等着你的游戏去探索。Android用户也非常热情友好,我认为会比iOS用户还更热情得多。所以让Android用户也来玩我们的游戏吧!

 

via:游戏邦/gamerboom.com

    android-8      -> 官方Android 2.2系统镜像

更多阅读:

  • Sean
    Thompson:数据显示Android平台IAP盈利性高于iOS
  • Flurry:2011年移动开发者调查报告
    支持iOS平台开发者比例超过70%
  • 20款最受好评的iOS和Android游戏
  • Newzoo:2014年1月1月iOS/Google
    Play游戏报告
  • Appcelerator:调查显示iOS企业应用开发已领先Android平台
  • Distimo:2013年8月全球应用市场研究报告
  • Flurry:全球移动应用地域差异:中国爱娱乐
    日本爱音乐
  • Digi-Capital:2018年全球移动游戏收入将达450亿美元
    占游戏收入40%
  • Appannie:中美iOS游戏市场对比
    开发者9成收入来自海外
  • Crackberry:黑莓流失用户中20%打算使用Windows Phone
    8
  • Flurry:70%的新App选择iOS平台,Android平台只有31%
  • Business
    Insider:Android平台现状报告:从高歌猛进到面临挑战
  • comScore:2012年Q3谷歌Android平台占美国市场份额为52.5%
  • TinyCo:《Tiny
    Village》Android版ARPPU比iOS版本多25%-40%
  • 360安全中心:2011年中国手机安全状况报告

 

    android-9      -> Official Android 2.3 system images

 

    android-9      -> 官方Android 2.3系统镜像

 

Note that android-6 and android-7 are the same as android-5 for the NDK,
i.e. they provide exactly the same native ABIs!

 

注意android-6和android-7对应的NDK和android-5相同,即它们提供完全相同的原生ABI!

 

IMPORTANT:

    The headers corresponding to a given API level are now located under
$NDK/platforms/android-<level>/arch-arm/usr/include

 

重要:

对应给定API级别的头文件现在位于$NDK/platforms/android-<level>/arch-arm/usr/include目录下

 

II. Android-3 Stable Native APIs:

 

二、Android-3稳定原生API:


 

All the APIs listed below are available for developing native code that
runs on Android 1.5 system images and above.

 

下面列出的所有API对于开发运行在Android 1.5系统镜像和更高的原生代码可用。

 

The C Library:

 

C库:


 

The C library headers, as they are defined on Android 1.5 are available
through their standard names (<stdlib.h>, <stdio.h>,
etc…). If one header is not there at build time, it’s because its
implementation is not available on a 1.5 system image.

 

C库头文件,正如它们在Android
1.5所定义的那样通过它们的标准名称(<stdlib.h>、<stdio.h>等等)可用。如果一个头文件在构建期不在那里,是因为它的实现在1.5系统镜像上不可用。

 

The build system automatically links your native modules to the C
library, you don’t need to add it to LOCAL_LDLIBS.

 

构建系统自动地链接你的原生方法到C库,你不需要添加它到LOCAL_LDLIBS。

 

Note that the Android C library includes support for pthread
(<pthread.h>), so “LOCAL_LIBS := -lpthread” is not needed. The
same is true for real-time extensions (-lrt on typical Linux
distributions).

 

澳门太阳集团,注意Android C库包含对pthread(<pthread.h>)的支持,所以LOCAL_LIBS
:=
-lpthread是不需要的。对于实时扩展(在典型Linux分发版中的-lrt)同样不需要。

 

** VERY IMPORTANT NOTE:
******************************************************

 

非常重要的注意事项:

 

  The kernel-specific headers in <linux/…> and <asm/…>
are not considered stable at this point. Avoid including them directly
because some of them are likely to change in future releases of the
platform. This is especially true for anything related to specific
hardware definitions.

 

  <linux/…>和<asm/…>的内核特定头文件此时还不认为是稳定的。避免直接包含它们,因为它们中有一些可能在未来的平台发布版中将会被修改。对于与特定硬件定义相关的任何东西尤其如此。

 

******************************************************************************

 

 

The Math Library:

 

数学库:


 

<math.h> is available, and the math library is automatically
linked to your native modules at build time, so there is no need to list
“-lm” through LOCAL_LDLIBS.

 

<math.h>可用,而且数学库在构建期自动地链接到你的原生模块,所以不需要通过LOCAL_LDLIBS列出-lm。

 

C++ Library:

 

C++库


 

An *extremely* minimal C++ support API is available. For Android 1.5,
this is currently limited to the following headers:

 

一个极其小的C++支持API可用。对于Android
1.5,它当前被限制为以下的头文件:

 

   <cstddef>

   <new>

   <utility>

   <stl_pair.h>

 

They may not contain all definitions required by the standard. Notably,
support for C++ exceptions and RTTI is not available with Android 1.5
system images.

 

它们可能包含标准所需的所有定义。值得注意的是,C++异常和RTTI(注:Runtime
Type Information,运行时类型信息)对于Android 1.5系统镜像不可用。

 

The C++ support library (-lstdc++) is automatically linked to your
native modules too, so there is no need to list it through LOCAL_LDLIBS

 

C++支持库(-lstdc++)也被自动链接到你的原生模块,所以没必要通过LOCAL_LDLIBS列出它。

 

Android-specific Log Support:

 

Android特定的日志支持:

相关文章