AIR在iOS7上的Microphone权限问题解决

近期提交到AppStore上的应用被拒,原因如下:

2.3
In addition, during review we were prompted to provide consent to use the microphone, however, we were not able to find any features or functionality that use the microphone for audio recording.
Please see the attached screenshot’s for more information.
The microphone consent request is generated by the use of either AVAudioSessionCategoryRecord or AVAudioSessionCategoryPlayAndRecord audio categories.
If you do not intend to record audio with your application, it would be appropriate to choose the AVAudioSession session category that fits your application’s needs or modify your app to include audio-recording features.
……

大概的意思是说我的应用有请求Microphone权限,但没有使用这个权限。Apple同时发来一张截图:

microphonenedded

但是我清楚的知道,我的应用绝对是没有使用麦克风的。只有某卫士某地图才会无耻地跑去请求发短信权限麦克风权限银行帐号权限…… 继续阅读AIR在iOS7上的Microphone权限问题解决

朝鲜语/韩文字符在Anrdoid4.2.2上不显示Korean text isn’t shown in Android 4.2.2

Korean text isn’t shown in Android 4.2.2

环境

  • 编译SDK:AIR 3.6/3.7
  • 测试设备:Nexus 7(Android 4.2.2)/Samsung Note 2(Android 4.1.2)/Moto Atrix 2(Android 4.1.2)
  • 测试模拟器:Android 4.2.2/4.1.2

现象

  • App中使用朝鲜语/韩文字符(UTF-8编码)的文本,在Android4.2.2系统上不能显示,模拟器和设备均出现这个问题;
  • 不仅是App的显示有这个问题,App在Android系统App列表中的名称也无法显示;
  • 无论设备语言是英文、朝鲜语/韩文还是中文,这个问题都会出现;
  • 同样的App放到Android 4.1.2及以下系统中均表现正常;
  • 使用中文字符(UTF-8编码)的文本,无论在Android 4.2还是其他系统上,均表现正常;
  • 如果希望看现象截图,可以看这里:http://forums.adobe.com/message/5361043#5361043

解决过程

  • 我在 Android 项目的 issue 列表中发现了同样的问题 Korean font/locale unavailable in Android 4.2 ,但该问题没有得到正面答复。从截图上看,提出该问题的也是中国开发者。这是个巧合吗?
  • 我怀疑是AIR的问题,因为如果这是Android操作系统问题,那韩国的Android 4.2开发者怎么办?为什么我用英文搜索不到相关信息?我在 AIR Bugs and Performance Issues 上询问了这个问题;
  • 2013-05-29,Adobe的员工Pahup回复我AIR 3.8 beta解决了这个问题。我在labs上看了一下,3.8 beta确实更新了,更新时间就是今天。但经过实际测试,这个问题依然存在。
  • 2013-05-30,Pahup回复说需要设置字体属性,尝试之后,果然OK。

解决方法

首先确保使用AIR 3.8SDK。

若使用Flex,可以通过设置 fontFamily 来支持Korean字符显示,例如:

<s:Label text="고속도로"  fontFamily="NanumGothic"/>

若使用TextField,可以通过设置 TextFormat 的 font 属性来支持Korean字符显示, 例如:

var __label:TextField = new TextField();
var __ft:TextFormat = new TextFormat();
__ft.font= "NanumGothic";
__label.defaultTextFormat = __ft;
__label.text = "고속도로";

以上做法,只有AIR 3.8支持。因此,我还必须要等待AIR 3.8发布。

ADT error 100 Descriptor cannot be parsed

使用AIR打包Android APK的时候,碰到了error 100错误,具体错误提示为:

D:\works\tools\anetoolkit\project\sample\src\ANEToolkitSample-app.xml: error 100
: Descriptor cannot be parsed

根据 Adobe提供的文档 ,error 100属于应用程序描述文件XML语法错误。

但是,我的XML语法明显是正确的。

花了1个小时测试,发现问题出在注释的位置上。 继续阅读ADT error 100 Descriptor cannot be parsed

AIR 3.7 SDK Bug:You uploaded an unsigned APK

AIR 3.7 SDK Bug:You uploaded an unsigned APK

2013-04-18更新:使用AIR 3.7 SDK打包的APK文件,出现了在某些手机上无法安装的情况,换回AIR SDK 3.6就一切正常。
2013-04-19更新:使用AIR 3.7 SDK打包的APK文件,在Google In-app Billing支付的时候,也出现了问题。具体表现为,Google Play支付成功,信用卡扣款成功,但Google Playe并没有正常返回支付信息,导致最终的支付无法完成。但改为AIR 3.6 SDK打包就一切正常。
我推测,是由于Google Play中上传的那个APK是使用AIR 3.6 SDK打包,必须使用AIR 3.7 SDK打包的APK文件替换原来的AIR 3.6 SDK打包的APK,才会支付正常。但由于AIR 3.7 SDK打包的APK在某些设备上无法安装的问题,我不可能再使用AIR 3.7 SDK去打包了。
2013-04-30更新:根据赵客的建议,向Adobe提交了此BUG:https://bugbase.adobe.com/index.cfm?event=bug&id=3552540


将一个需要更新的APK上传到Google Play的时候,Google Play提示我这样的错误:

Uploaded failed
You uploaded an unsigned APK. You need to create a signed APK.

截图如下:

upload_apk_error

这个提示无疑是 错误 的。我并没有修改过任何编译参数,也从来没有修改过打包使用的证书。在程序编译正常的情况下去修改编译参数和证书?我不是吃饱了撑的么。

这个APK在Android设备上是可以正常安装和运行的,因此程序本身没有什么问题。

我怀疑了许多地方,比如是否有ANE需要签名,或者是否有某些Android权限比较特殊,都没有找到什么线索。
继续阅读AIR 3.7 SDK Bug:You uploaded an unsigned APK

BUG?AIR打包的iOS程序在整数比较上的问题

BUG?AIR打包的iOS程序在整数比较上的问题

以前找到过一个 FlashPlayer在执行NetStream.play的时候崩溃的BUG ,没想到今天又让我碰到一个AIR的BUG。

和上个BUG不同,这个BUG再现起来相当容易,但我还是找了1天才找到再现的方式。

不说了,直接上代码:

package
{
import flash.display.Sprite;
import flash.filesystem.File;
import flash.text.TextField;

/**
 * 测试在iOS分发包中的unit与int不能比较的问题
 */
public class IOSUintTest extends Sprite
{
    public function IOSUintTest()
    {
        super();
        init();
        showInfo(-1 <= ZERO_INT);
        showInfo(-1 <= ZERO_UINT);
        showInfo(_num <= ZERO_INT);
        showInfo(_num <= ZERO_UINT);
    }

    private var _tf:TextField;

    private var _num:int = -1;

    public static const ZERO_INT:int = 0;

    public static const ZERO_UINT:uint = 0;

    private function init():void
    {
        _tf = new TextField();
        _tf.width = 400;
        _tf.height = 400;
        this.addChild(_tf);
    }

    private function showInfo($info:*):void
    {
        _tf.appendText(String($info) + File.lineEnding);
    }
}
}

地球人都知道,showInfo中的4个比较表达式的值应该都为true。恩,是的,在adb提供的调试版ipa中,它们的值都是true。

但是,在用于发布的ipa中,它们的值并非都是true!

我这里所说的“用于发布的ipa”,如果用Adobe的话来说,就是“限制分发的临时包”和“部署到Apple App Store的最终发行包“。

将这种包安装到iOS设备上,得到的4个值分别是 true,false,true,false

问题出在int与uint的比较上。因为AIR打包成ipa,实际上是直接将AIR程序打包成2进制代码,而不是采取虚拟机的形式(APK是采取的这种形式)。因此,使用AIR制作的ipa,理论上与使用Objective-C写的ipa没有什么不同。这也是为什么AIR写的ipa能堂而皇之的登上App Store的原因。否则,以苹果那个独裁政策,不卡死Adobe才怪!

既然是Objective-C代码,那么Objective-C的类型转换规则也同样适用与这个比较表达式。在Objective-C中,将int与uint互相比较的时候,会先将int转换成uint,得到4294967294,(4294967294 <= 0) 的值应为false。

Objective-C是基于C语言的。在C语言中,这种情况叫做整型提升。

以下摘自《The C Programming Language》

A character, a short integer, or an integer bit-field, all either signed or not, or an object of enumeration type, may be used in an expression wherever an integer maybe used. If an int can represent all the values of the original type, then the value is converted to int; otherwise the value is converted to unsigned int. This process is called integral promotion.

在ActionScript中,int与uint比较的时候,是不会进行整型提升的。int和uint都是基于Number,在AVM中,我不知道它们是否进行了严格的划分。

从这个观点上说,这并不是BUG,而是不同语言的特性所致。但是,Adobe既然在大力推广AIR开发iOS应用,就要考虑到不同语言之间的差别,避免出现这种容易被忽视的错误。

这个BUG说起来简单,但是在一个已经存在的大型项目中发现这样的小错误,还是非常困难的。

困难的关键点在于,允许调试的ipa文件(target ipa-debug)中,并不会出现整型提升的问题。这就导致调试的时候正常的程序,在发布的时候不正常。何况发布的文件还无法调试!这绝对是Adobe的工作失误。

这个问题,我不准备报告给Adobe了。大家在开发中养成更严谨的习惯吧。

FlashPlayer/AIR在new Vector(-1)的时候崩溃

FlashPlayer/AIR在new Vector(-1)的时候崩溃

试试这段代码:

var __length:int = -1;
var __v:Vector.<String> = new Vector.<String>(__length);

如果你用Flash builder编译,不会显示任何错误。编译后的swf无法双击打开,或打开后立即退出。

如果你用编译的是AIR程序,程序运行后会立即崩溃,同时弹出下面的提示信息:

ADL错误

如果你用Flash IDE来编译,则会看到错误提示:

Error: Error #1000: 系统内存不足。
at Vector$object/set length()
at Vector$object()
at aaa_fla::MainTimeline/frame1()

这本来不是什么大问题,毕竟极少极少有人会使用 -1 这个值来作为Vector的length属性。

可是,起码给点提示好不好?起码让我不要找错方向!

测试平台:

  • FlashPlayer 10.3
  • Flash Builder 4.5.1
  • Flex SDK 4.5.1
  • AIR 1.7

FlashPlayer在执行NetStream.play的时候崩溃的解决办法

FlashPlayer在执行NetStream.play的时候崩溃的解决办法

这是个隐藏非常深的BUG,我都怀疑如果再做一次,我能不能把它找出来。它耗费了我宝贵的三天时间,三天啊……

BUG表现

在使用NetStream连接FMS发布的流时,在执行NetStream.play(‘streamName’)方法时,FlashPlayer会崩溃。独立版、调试版以及基于浏览器的插件版均如此。

但是,这还不是全部。必须满足以下几点,该BUG才会出现。

  • 使用Windows 7操作系统。也就是说,Windows XP不会出现这个问题;
  • 播放的必须是RTMP流,RTMP流可以由Flash Media Server或者Red5来发布。也就是说,使用NetStream播放本地的flv/f4v/mp4视频不会出现这个问题;
  • 播放的流包含音频。也就是说,如果该流只包含视频,不会出现这个问题;
  • 播放的流中包含的音频声音较大。也就是说,即使该流包含音频,但如果发布方没有发出声音,或者发出的声音很小,该问题不会出现;当然,不需要很大的声音就能让播放端立即崩溃;
  • 使用了Frame标签来做预加载。不了解Frame标签预加载的,看这篇文章:Preloaders in AS3;
  • 在预加载完毕之后,使用removeChild移除了预加载类的实例(BUG就在这里)。

开发和测试平台(出现BUG的平台)

  • Flex SDK 4.5.1
  • Flash Media Server 4.0
  • Flash Player 10.3独立版/调试版/插件版
  • Windows 7 旗舰版
  • Chrome12/Opera11.5/Firefox5/IE9

BUG再现

我写了两个简单的Demo(一个发布端,一个接收端)来重现这个BUG。Demo需要FMS的支持。

错误的重点在于预加载类(PreloaderNSPlay.as)。由于预加载类在完成加载后就不再需要,一般的处理方法是将其从Stage中移除。只要将移除,就会出现这个BUG(并非移除后立即出现,而是在接收音频流的视频出现)。而如果使用visible将预加载类隐藏,就不会出现这个问题。

Demo的使用方法(服务端以FMS为例):

  1. 安装FMS,在安装目录下建立 /applications/testspeed/ 文件夹;
  2. 编译NSPulish和NSPlay,或者 在这里直接下载
  3. 确认本机安装了摄像头和麦克风,运行NSPublish.swf,单击“连接”按钮,查看log信息确定连接正常,见下图:
    发布流
  4. 运行NSPlay.swf,单击“连接”按钮,查看log信息确认连接正常。此时会看到发布端的摄像头视频。如果FlashPlaye没有崩溃的话,就向着麦克风吹口气……呼……整个世界清静了……
    播放流

下面只贴出了 PreloaderNSPlay.as 的源码,需要整个项目源码可以在 这里 下载。

PreloaderNSPlay.as

package
{
import flash.display.MovieClip;
import flash.display.DisplayObject;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.ProgressEvent;
import flash.text.TextField;
import flash.text.TextFormat;
import flash.utils.getDefinitionByName;

public class PreloaderNSPlay extends MovieClip 
{
    public function PreloaderNSPlay()
    {
        _mainClassName = 'NSPlay';
        stage.scaleMode = StageScaleMode.NO_SCALE;
        stage.align = StageAlign.TOP_LEFT;
        stage.showDefaultContextMenu = false;
        _tf = new TextField();
        _tf.defaultTextFormat = new TextFormat(null,12,0,null,null,null,null,null,"center");
        _tf.mouseEnabled = false;
        _tf.height = 20;
        _tf.x = (stage.stageWidth-_tf.width)*.5;
        _tf.y = stage.stageHeight*.5;
        this.addChild(_tf);

        this.loaderInfo.addEventListener(ProgressEvent.PROGRESS,progress);
        this.loaderInfo.addEventListener(Event.COMPLETE,complete);
    }

    protected var _tf:TextField;
    protected var _mainClassName:String;

    private function progress(e:ProgressEvent):void
    {
        _tf.text = int(e.bytesLoaded/e.bytesTotal*100)+"% 载入中……";
    }

    private function complete(e:Event):void
    {
        gotoAndStop(2);
        var mainClass:Class = Class(getDefinitionByName(_mainClassName));
        stage.addChild(new mainClass() as DisplayObject);
        destroy();
    }

    private function destroy():void
    {
        this.loaderInfo.removeEventListener(ProgressEvent.PROGRESS,progress);
        this.loaderInfo.removeEventListener(Event.COMPLETE,complete);
        //将预加载类从舞台移除(parent.removeChild也一样,因为parent就是舞台),就会导致Flash Player崩溃
        stage.removeChild(this);
        //parent.removeChild(this);

        //如果只移除显示进度的文本,或者只将自身隐藏而不移除,就不会出现这个Bug
        //this.removeChild(_tf);
        //this.visible = false;
    }
}
}