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;
我制作了一个简单的 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;