Android 上的 HTTP 服務相關函式 (III)

The introduction of HTTP connection APIs on Android platform - Part III.

在 這裡 和 這裡 我已經介紹要如何透過 http 向伺服器要資料。

在這,我想要談的是如何在 http 傳輸中,處理中文資料。

上傳的資料內有中文

首先,我們常常要上傳一個表單內的資料給伺服器。如果用 post 的方式,程式大概像這樣。

HttpClient hc = new DefaultHttpClient();  
HttpPost post = new HttpPost("http://xxx.com");  
  
List <NameValuePair> nvps = new ArrayList <NameValuePair>();  
nvps.add(new BasicNameValuePair("username", "陳水"));  
nvps.add(new BasicNameValuePair("password", "24588"));  
nvps.add(new BasicNameValuePair("nickname", "欠扁"));  
post.setEntity(new UrlEncodedFormEntity(nvps));  
  
HttpResponse rp = hc.execute(post);  
... 

如果伺服器端是 PHP 的話,我保證 $_POST['username'], $_POST['nickname'] 這兩個變數看到的都是亂碼。這原因是,在第 8 行中,你並沒有告訴 UrlEncodedFormEntity(),你傳進的那些參數值字串的編碼為何。預設的編碼是 ISO-8859-1,這當然會把你的傳入的 UTF-8 中文搞亂了。

正確的寫法,應該將第 8 行,改成 post.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8));

另外,熟 http 的人,可能會問,你不用設定 "application/x-www-form-urlencoded" 嗎?答案是,不用。因為,UrlEncodedFormEntity() 最重要的功能,就是幫你加上 setContentType("application/x-www-form-urlencoded")。

下載的資料內有中文

延續上面的例子,如果要抓這伺服器回傳的字串資料。不少人會將程式寫成下面這樣。

    if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  
      InputStream is = rp.getEntity().getContent();   
          
      byte[] data = new byte[1024];  
      int n  
      String str = "";  
      while ((n = is.read(data)) != -1)  
        str += new String(data);  
    }  

當回傳的字串資料,只有簡單的英文字元時,這樣的程式都可以處理。可是遇到中文時,我相信又是亂碼一堆。這樣的程式有兩個問題:
1. 要先從 InputStream 中將所有的資料都讀到 byte array 中。像這樣,一邊讀,一邊轉成 String,可能會將一個中文字,切成兩半。
2. 假設這資料中的中文編碼是 UTF-8,要將 byte array 轉成 String,那你要用 new String(data, "UTF-8")。

正確的寫法,應該像這樣。

    if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  
      InputStream is = rp.getEntity().getContent();   
        
      byte[] data = new byte[1024];  
      int n;  
      ByteArrayBuffer buf = new ByteArrayBuffer(1024);  
      while ((n = is.read(data)) != -1)  
        buf.append(data, 0, n);  
          
      String str = new String(buf.toByteArray(), HTTP.UTF_8);  
    }  

不過,如果你是用 HttpClient,那有個更好用的函式 - EntityUtils.toString()。用這個函式,上面的程式可以改寫成這樣。

    if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {  
      String str = EntityUtils.toString(rp.getEntity());  
    }  

怎麼這麼神奇!都不用告訴他 "UTF-8" 這個編碼資訊嗎?

讓我們來看一下 EntityUtils.toString() 的原始程式,就可以了解他是如何做到的。

public static String   
toString(HttpEntity entity, String defaultCharset)  
  throws IOException, ParseException  
{  
  Reader reader;  
  CharArrayBuffer buffer;  
  
  InputStream instream = entity.getContent();  
  int i = (int)entity.getContentLength();  
  if(i < 0) i = 4096;  
  String charset = getContentCharSet(entity);  
  if(charset == null) charset = defaultCharset;  
  if(charset == null) charset = "ISO-8859-1";  
  
  reader = new InputStreamReader(instream, charset);  
  buffer = new CharArrayBuffer(i);  
  char tmp[] = new char[1024];  
  int l;  
  while((l = reader.read(tmp)) != -1)   
    buffer.append(tmp, 0, l);  
  ...  
  return buffer.toString();  
}  
  
public static String   
getContentCharSet(HttpEntity entity)  
  throws ParseException  
{  
  ...  
  String charset = null;  
  if(entity.getContentType() != null) {  
    HeaderElement values[] = entity.getContentType().getElements();  
    if(values.length > 0) {  
      NameValuePair param = values[0].getParameterByName("charset");  
      if(param != null)  
        charset = param.getValue();  
    }  
  }  
  return charset;  
} 

看到了嗎?第 11 行會呼叫 getContentCharSet(),來取得這回傳資料的編碼資訊。而這 getContentCharSet() 就是從伺服器回應的 header 中,找出 charset ,並回傳伺服器端所設定的編碼資訊。因此,當你發現 EntityUtils.toString() 還是不能正確解碼時,那你要先看看伺服器的回應檔頭中,是否有像這樣 "Content-Type: text/plain; charset=UTF-8",描述 charset 的資訊。

你可能感兴趣的:(Android 上的 HTTP 服務相關函式 (III))