关于这个 Bug 的详情,大家可以参考:https://quality.embarcadero.com/browse/RSP-12424
Berlin 中的 FMX.Graphics.Android 实现的原始代码如下:
class function TBitmapCodecAndroid.IsGIFStream(const Stream: TStream): Boolean; const IDCharCount = 3; var PrevPosition: Int64; Builder: TStringBuilder; I: Integer; Value: Char; begin if (Stream = nil) or (Stream.Size < IDCharCount) then Exit(False); PrevPosition := Stream.Position; try Builder := TStringBuilder.Create(IDCharCount); try for I := 0 to 2 do begin Stream.ReadBuffer(Value, 1); Builder.Append(Value); end; Result := SameText(Builder.ToString, 'GIF'); finally Builder.Free; end; finally Stream.Position := PrevPosition; end; end;
存在的问题:
- 第一个判定不完善,流的大小大于 GIF 头部大小并不代表内容从当前位置读取剩余的内容长度大于3,所以这块应该改成
if (Stream = nil) or (Stream.Size-Stream.Position < IDCharCount) then Exit(False);
- 另一个问题是一个低级的问题,Char 类型在 Android 上对应的是 WideChar,也就是说是 2 个字节,然后它没有初始化,直接读取。由于 Value 的值为随机值,所以,有可能两个字节的内容不为空,结果就造成添加时出现问题,造成正确的GIF文件格式无法读取。实际上只需要 Value 初始为 0 就可以了。
- 吐个槽:这么简单判定为啥要用 TStringBuilder?这让我想起一个笑话,说是为了让用户掏钱,故意在程序中加入 Sleep 来减慢程序的速度,这样以后借口优化就可以让用户掏更多的钱了。再怎么说,TStringBuilder也是一个对象,而且牵涉到内存的额外分配释放,纯属脱裤子放屁,多此一举。
好吧,再补充一点吧:GIF 实际上有两种头,一种头是 GIF89a,另外一种是 GIF87a,无论那种,头部实际上是6个字节,而不是 3 个,如果象上面只判断 GIF ,显然有失严谨。
给一个参考的修正代码:
class function TBitmapCodecAndroid.IsGIFStream(const Stream: TStream): Boolean; const IDCharCount = 6; ID_GIF89a: array [0 .. 7] of Byte = ($47, $49, $46, $38, $39, $61,$00,$00); ID_GIF87a: array [0 .. 7] of Byte = ($47, $49, $46, $38, $37, $61,$00,$00); var PrevPosition: Int64; I: Integer; Id: Int64; begin if (Stream = nil) or (Stream.Size - Stream.Position < IDCharCount) then Exit(False); PrevPosition := Stream.Position; try Id:=0; Stream.ReadBuffer(Id, IDCharCount); Result :=(Id=PInt64(@Id_GIF89a[0])^) or (Id=PInt64(@Id_GIF87a[0])^); finally Stream.Position := PrevPosition; end; end;
这里故意填充了ID到8位,以便直接将 GIF 头当成64位整数来比较,提高效率。