Android 中如何将 UTF8 字符的 StringStream 加载到 StringList 中?

How to load a StringStream with UTF8 characters to a StringList in Android?

我制作了一个简单的 Indy HTTP GET 函数,它可以完美地使用 Windows 客户端和服务器,但是当在 Android 中使用客户端时,它在尝试将 StringStream 加载到 StringList 时卡住了,因为使用UTF8字符。

客户端(线程内):

var
ss:TStringStream;
st:TStringList;
begin
ss := TStringStream.Create('',TEncoding.UTF8);
IdHTTP1.Request.UserAgent := 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; MAAU)';
IdHTTP1.Get('http://motoristaajudante.ddns.net:37009/-23.671373,-046.700072',ss);
ss.Position := 0;
st := TStringList.Create;
st.LoadFromStream(ss); // <<< Crash in this line on Android, on Windows works fine
end;

服务器:

procedure TfrmMain.DownloadServer9CommandGet(AContext: TIdContext; 
  ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); // Indy HTTP Server
var
    Enviar:TStringList;
    EnvioStream:TMemoryStream;
begin
try
    Enviar := TStringList.Create;
    Enviar.Add('11111111111111111111111111111111111111 0006 Jardim São Caetano|Fique mais próximo'+' do conjunto de prédios do Jardim São Caetano para aumentar as chances de receber um chamado. Tente parar exatamente onde é apontado no mapa. ¬iMobby¬d19/09/2017 00:00:00  2359235923592359  0012  000  23590 ¬§');
    Enviar.Add('11111111111111111111111111111111111111 0006 Jardim São Caetano|Fique mais próximo'+' do conjunto de prédios do Jardim São Caetano para aumentar as chances de receber um chamado. Tente parar exatamente onde é apontado no mapa. ¬iMobby¬d19/09/2017 00:00:00  2359235923592359  0012  000  23590 ¬§');
    Enviar.Add('11111111111111111111111111111111111111 0006 Jardim São Caetano|Fique mais próximo'+' do conjunto de prédios do Jardim São Caetano para aumentar as chances de receber um chamado. Tente parar exatamente onde é apontado no mapa. ¬iMobby¬d19/09/2017 00:00:00  2359235923592359  0012  000  23590 ¬§');
    Enviar.Add('@');
    // if switching the above strings to only numbers and letters then the client loads the StringList normally on Android

    EnvioStream := TMemoryStream.Create;
    Enviar.SaveToStream(EnvioStream);

    AResponseInfo.ContentStream := EnvioStream;
    AResponseInfo.WriteContent;
finally
    Enviar.Free;
    Enviar := nil;
    EnvioStream.Free;
    EnvioStream := nil;
end;

虽然,如果将所有发送的字符串切换为仅数字和字母,则客户端会在 Android 上正常加载 StringList,但如果我只放入一个 UTF8 字符,它就会崩溃。如何解决?

编辑:消息是 "No mapping for the Unicode character exists in the target multi-byte code page"。

在这种情况下,您对 TEncoding.UTF8 的使用将被忽略。

TStringStream是D2009+中的一个字节流。您传递给其构造函数的 TEncoding 永远不会被使用(构造函数的输入字符串为空,并且您根本不使用 DataString 属性)。因此,这段代码正在下载原始字节,然后使用 TEncoding.Default 将这些字节按原样加载到 TStringList 中进行解析,只是 碰巧 是 UTF-8在 Android 上,但在 Windows 上不是 UTF-8。如果数据实际上不是 UTF-8,将其解码为 UTF-8 将失败。

如果您知道下载的数据是用 UTF-8 编码的,那么您需要在调用时明确指定 LoadFromStream():

var
  ms: TMemoryStream;
  st: TStringList;
begin
  st := TStringList.Create;
  try
    ms := TMemoryStream.Create;
    try
      //...
      IdHTTP1.Get('http://motoristaajudante.ddns.net:37009/-23.671373,-046.700072', ms);
      ms.Position := 0;
      st.LoadFromStream(ms, TEncoding.UTF8); // <-- here
    finally
      ms.Free;
    end;
    // use st as needed... 
  finally
    st.Free;
  end;
end;

但是,如果您不知道数据是否为 ​​UTF-8,最好让 TIdHTTP 根据 HTTP 服务器描述其编码的方式为您解码:

var
  st: TStringList;
begin
  st := TStringList.Create;
  try
    //...
    st.Text := IdHTTP1.Get('http://motoristaajudante.ddns.net:37009/-23.671373,-046.700072');
    // use st as needed... 
  finally
    st.Free;
  end;
end;

在服务器端,您应该做更多类似的事情来确保数据以 UTF-8 编码:

procedure TfrmMain.DownloadServer9CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); // Indy HTTP Server
var
  Enviar: TStringList;
  EnvioStream: TMemoryStream;
begin
  Enviar := TStringList.Create;
  try
    Enviar.Add('11111111111111111111111111111111111111 0006 Jardim São Caetano|Fique mais próximo'+' do conjunto de prédios do Jardim São Caetano para aumentar as chances de receber um chamado. Tente parar exatamente onde é apontado no mapa. ¬iMobby¬d19/09/2017 00:00:00 2359235923592359 0012 000 23590 ¬§');
    Enviar.Add('11111111111111111111111111111111111111 0006 Jardim São Caetano|Fique mais próximo'+' do conjunto de prédios do Jardim São Caetano para aumentar as chances de receber um chamado. Tente parar exatamente onde é apontado no mapa. ¬iMobby¬d19/09/2017 00:00:00 2359235923592359 0012 000 23590 ¬§');
    Enviar.Add('11111111111111111111111111111111111111 0006 Jardim São Caetano|Fique mais próximo'+' do conjunto de prédios do Jardim São Caetano para aumentar as chances de receber um chamado. Tente parar exatamente onde é apontado no mapa. ¬iMobby¬d19/09/2017 00:00:00 2359235923592359 0012 000 23590 ¬§');
    Enviar.Add('@');
    EnvioStream := TMemoryStream.Create;
    try
      Enviar.SaveToStream(EnvioStream, TEncoding.UTF8); // <-- here
    except
      EnvioStream.Free;
      raise;
    end;
    // AResponseInfo.ContentStream takes ownership of the stream
    // and will free it when AResponseInfo is freed. TIdHTTPServer
    // will send the response automatically when this OnCommandGet
    // handler exits, so you don't need to call WriteContent()
    // manually... 
    AResponseInfo.ContentStream := EnvioStream;
    AResponseInfo.ContentType := 'text/plain'; // <-- add this! 
    AResponseInfo.CharSet := 'utf-8'; // <-- add this! 
  finally
    Enviar.Free;
  end;
end;