[工作积累] Google/Amazon平台的各种坑
所谓坑, 就是文档中没有标明的特别需要处理的细节, 工作中会被无故的卡住各种令人恼火的问题. 包括系统级的bug和没有文档化的限制.
继Android的各种坑后, 现在做Amazon平台, 遇到的坑很多, 这里记录一下备忘:
先汇总下Android Native下的各种问题, 当然有些限制有明确文档说明,不算坑,但是限制太多还是很不爽:
android平台下的某些限制: android下的各种坑 (我的C/C++/汇编/计算机原理博客)
OBB的各种bug: OBB的解决方案
arm gcc toolchain 的链接错误和编译器崩溃: NDK: GCC 4.6 crashes [原]android 链接错误
Modified UTF8 崩溃: crash - JNI WARNING: input is not valid modified utf-8: illegal continuation byte
BACK键无消息:[工作记录] NDK: AKEYCODE_DEL not notified
以上这些还不包括其他API, 如GLES, OpenSL ES的坑.
比如GLES2.0用复杂shader编译通过但是runtime崩溃或者渲染不正确, 比如下面代码, GLES2.0某些硬件shader条件分支代码渲染不正确或者黑屏,
//GLES2.0 fragment shader float factor = ( dir >= 0 ) ? 1 : 0; //doesn't work! artifacts! float factor = step(0, dir); //f*******k! it works! the hell why?
还比如GLES2.0竟然没有统一的压缩贴图格式, 而且ETC1竟然不支持Alpha等等, 总的来说GLES2.0不适合做大一点儿的3D游戏.
而OpenSLES上也是各种限制, 比如声音数量的限制, 音频解码接口的限制(导致可用声音数量减半)等等, 这些比iOS差远了.
再来说说最近Amazon平台遇到的坑:
1. GameCircle无法登录, 一直为GUEST.
文档里相关的 FAQ 如下
Q: Which countries does Amazon GameCircle support?A: Amazon GameCircle is available in more than 200 countries, including the United States, the United Kingdom (England, Scotland, Wales, and Northern Ireland), Germany, France, Spain, Italy, Japan, and Brazil. In China and other non-supported countries, your customers can earn achievements and track their high scores without logging in; the data is stored locally and not synced to the cloud.
那么在天朝做global app development, 也没法调试了么, 有什么解决办法没? 虽然手里的Kindle Fire设备有全局VPN设定,但是还没有VPN可用啊... 最后在支持论坛(https://forums.developer.amazon.com/forums/thread.jspa?threadID=2946)看到这句话:
GameCircle is not supported for the users using Chinese Amazon account. The users with a non-China account may attempt to use GameCircle from China, however they will be connecting to the US and may be subject to China firewall issues.
好吧, 怎么创建非中国账号? 我的账号是在amazon.com, 非amazon.cn上创建的, 应该不是中国账号吧, 为什么还是不行?... 最后拿着设备折腾了一番, 终于搞定了:
设置 => 我的账号 => 当前国家 : 设置为美国
这样就可以了...
2. GameCircle Jni崩溃.
logcat 输出一堆错误: ... NoClassDefFoundError: com/amazon/ags/api/AGResponseHandle(关键字) ...
一开始以为是library reference 或者 class loader的线程问题, 搜索了所有错误信息后, 最后使用上面关键字, 得到的一下解答(https://forums.developer.amazon.com/forums/thread.jspa?threadID=767):
From my own experimentation it seems that the handles just don't work for me.
They make the calls to java and return a handle, but after this, the amazon C++ code try to do something (what I don't know) that creates a classdefnotfound exception which crashes the application. I switched to using the callbacks.
查看自己代码发现有部分代码为了省事使用了Handle, 而没有使用callbak, 所以一部分函数调用会崩溃, 一部分不会.
全部改用callback 解决.
3. Amazon AppStore: 没有APK expansion
这个起码有文档说明. 但Google Play的APK和Amazon的限制不同, 导致非常恶心.
Amazon的APK没有大小限制, 100M以上需要用FTP传, 没有expansion file, 这样OBB什么的都是浮云了, 而且要为Amazon做新的包了. 综合之前Google Play的OBB问题,
最佳方案是使用ZIP格式, 因为APK是zip包, 那么使用ZIP可以同时支持OBB模式和APK模式,
使用同一套代码库的维护成本低, 加上复用度高导致稳定性高, 比两套代码要好很多.
目前工作中由于进度原因, 可能选择最快的,而且不降低运行效率的解决方案:
http://stackoverflow.com/questions/7937368/how-to-pass-arguments-to-aapt-when-building-android-apk
http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/
即把OBB文件放入APK, 同时禁止APK对OBB的zip压缩, 然后native直接读文件, 这样只需要初始化时通过Jni调用一次Java, 获取seeking offset和size,之后都可以在native读取. 准备着手做这一步.
更新:
上面方法经过测试可行, 除了遇到几个小问题.
问题1. OBB文件过大, 导致AAssetManager_open()打开失败, logcat输出 mmap失败, 不管使用哪种打开方式( AASSET_MODE_UNKNOWN, AASSET_MODE_BUFFER, AASSET_MODE_STREAMING) 都是同样错误, 也就是说系统固定使用mmap来把APK内文件映射到进程地址空间. 当然我只需要获取offset和size, 所以可以不要求mmap, 但是没有这个选项.
解决办法: 不使用obb嵌套, 直接把打包前的所有文件打包进apk.
问题2: AAsset_openFileDescriptor/AAsset_openFileDescriptor64 在一定次数执行成功后, 返回值-1
这是个坑啊:
根据注释/文档, 只有无法直接访问的文件才会返回失败, 比如被压缩过的文件. 但是用rar/zip工具查看过, 可以肯定APK里面的文件都没有压缩.
最后发现问题是 返回的fd需要关闭, 否则fd数量有限制或者占用内存太多, 后续创建fd会失败.
1 int fd = ::AAsset_openFileDescriptor64(asset, (off64_t*)&Offset, (off64_t*)&Size); 2 ... 3 ::close(fd);
可以看注释中没有任何说明需要关闭, 或者如何处理函数返回的资源.
/** * Open a new file descriptor that can be used to read the asset data. If the * start or length cannot be represented by a 32-bit number, it will be * truncated. If the file is large, use AAsset_openFileDescriptor64 instead. * * Returns < 0 if direct fd access is not possible (for example, if the asset is * compressed). */
或许有人会笑了: fd当然是要关闭的啊. 但最重要的原因在这里:
/** * Close the asset, freeing all associated resources. */ void AAsset_close(AAsset* asset);
既然fd是由asset创建出来的, 那么是否属于"associated resources"? 如果我脑补为"是"的话, 调用完AAsset_close()后就不需要close(fd)了, 有什么问题(兵库北笑脸)?
这种不说清楚的文档太坑爹了, 真心令人恼火. 网上甚至有人说这个是泄露的bug.
android ndk里真的是遍地坑啊, 自从接触了android natvie开发, 就经常感叹, 以往只靠c/c++标准文档,和msdn,man文档就可以轻松编写代码的日子, 一去不复返了 :D.
更新2:
图标大小
http://developer.android.com/design/style/iconography.html
LDPI – 36 x 36
MDPI – 48 x 48
HDPI – 72 x 72
XHDPI – 96 x 96
XXHDPI – 144 x 144
XXXHDPI – 192 x 192
Kindle Fire (1st Gen) – 322 x 322
Kindle Fire (2nd Gen) – 365 x 365
Kindle Fire HD 7″ – 425 x 425
Kindle Fire HD 8.9″ – 675 x 675
Kindle Fire HD 7″ (2nd Gen) – 425 x 425
Kindle Fire HD 8.9″ (2nd Gen) – 675 x 675
Kindle Fire HDX 7″ - 562 x 562
Kindle Fire HDX 8.9″ - 624 x 624
更新2(08.29.2014): 发现一个新bug
最近在引用第三方jar的时候总是说找不到symbol(引用class失败)
设置了jar的引用lib无效:
ar.libs.dir=../external/libs
然后又折腾了一番,
最后发现这个是ant的bug, 但是issue将不被修复, 因为那帮哥们儿忙着搞Gradle, 准备放弃ANT了.
http://code.google.com/p/android/issues/detail?id=33194
修复方法在这里:
http://stackoverflow.com/questions/11637852/is-jar-libs-dir-not-being-overridden-properly
Fire Phone Hero widget:
the home widget (HeroWidget stuff of FirePhone) initialization takes too long time on Activity.onCreate() (7-8 secs), and we found using resource id directly will take too long time
here's the code copied from Fire phone sample, with the change that we don't use uri to access remote URL (line 36):
1 /** 2 * Create a ListEntry based on the given parameters. 3 * @param visualStyle 4 * @param groupName 5 * @param iconUri 6 * @param showExtraText true if the sample should set the secondary text 7 * @param startActivity true if the sample should start activities, otherwise just log clicks. 8 * @param index index of this Entry in the list. Used for setting UI text only. 9 * @return A new ListEntry object 10 */ 11 private ListEntry createListEntry(final VisualStyle visualStyle, 12 final String groupName, final Uri iconUri, 13 final boolean showExtraText, final boolean startActivity, final int index) { 14 final HeroWidgetIntent heroIntent; 15 if (startActivity) { 16 // For intents that start Activities, use HeroWidgetActivityStarterIntent 17 heroIntent = new HeroWidgetActivityStarterIntent(LIST_TARGET_ACTIVITY); 18 } else { 19 // Otherwise, use WidgetBroadcastReceiver to receive intents 20 heroIntent = new HeroWidgetIntent(WIDGET_BROADCAST_RECEIVER); 21 } 22 23 heroIntent.setData(getString(R.string.group_item_in_group, index, groupName)); 24 25 // Sets the texts, intent, etc. 26 final ListEntry listEntry = new ListEntry(this); 27 listEntry.setContentIntent(heroIntent); 28 listEntry.setVisualStyle(visualStyle); 29 listEntry.setPrimaryText(getString(R.string.group_primary, index)); 30 // Star rating and review count have to be set simultaneously for them to show up. 31 // listEntry.setStarRating(5f - (index % 10) / 2f); 32 // Review count cannot be set to 0, otherwise it won't show. 33 listEntry.setReviewCount(index + 1); 34 // Sets the icons 35 log.d(TAG, "start set icon"); 36 listEntry.setPrimaryIcon(R.drawable.towk_icon); //WTF? this operation takes too long time! 37 log.d(TAG, "end set icon"); 38 39 if (showExtraText) { 40 // Show extra text in the ListEntry 41 listEntry.setSecondaryText(getString(R.string.group_secondary, index)); 42 listEntry.setTertiaryText(getString(R.string.group_tertiary, index)); 43 } 44 45 return listEntry; 46 }
here's the doc:
and our icon is 512x512, tried to change it to smaller size(32x32),
by using the URL, i.e. https://images-na.ssl-images-amazon.com/images/G/01/AmazonMobileApps/amazon-apps-store-us-white.png, as the sample does, we don't have this problem.
current workaournd is extracting the png from APK to local path( i.e. /data/data/appHome/files/cacheIcon.png ), and use Uri to locate the extracted png icon.