2008年9月29日 星期一

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

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

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

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

上傳的資料內有中文

首先,我們常常要上傳一個表單內的資料給伺服器。如果用 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")。

下載的資料內有中文

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

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

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

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

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

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

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

12 則留言:

匿名 提到...

你好,想請問,我們已經用以下這個方法,完成了顯示中文的動作,可是,手機上會出現空白的正方形小框框,不知道為什麼??你有遇到這個問題嗎????
謝謝你的教學~超感謝的!!!
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);
}

samlu 提到...

我自己寫的程式,沒這個問題。中文都正常顯示。

匿名 提到...

你好 若我要將手機中的sensor改變的值不斷即時傳回電腦可以嗎?
在android中要如何有個實作概念?謝謝!

samlu 提到...

當然可以,透過網路傳是其中一種方法。

匿名 提到...

非常感謝您~~~我可以試成功!!!!謝謝

匿名 提到...

InputStream is = rp.getEntity().getContent();
//An input stream is a means of reading data from a source in a byte-wise manner.

BufferedReader buf = new BufferedReader(new InputStreamReader(is, "UTF-8"));
//Wraps an existing Reader and buffers the input.
String line;
StringBuilder sb = new StringBuilder();
while ((line = buf.readLine()) != null)
{sb.append(line);}

大大你好,我參考了你的範例寫成一個讀取中文資料的程式,以上是能夠成功抓中文資料的程式碼。我想請問一下,為了能成功抓取中文,所以才會使用到InputStream這個以byte來讀取資料(記得中文字好像是幾個byte存取),再利用buffer解讀成UTF-8的編碼,我這樣的解釋是對的嗎? 再請問一下,程式的最後再以StringBuilder來讀出是為了什麼? 問題有點多麻煩大大了^^"

samlu 提到...

1. 你的解釋是對的。
2. 關於 StringBuilder 的問題,建議你先複習一下 Java 的書

匿名 提到...

請問這個範例中有辦法從HttpResponse這個物件中取到伺服器的IP嗎?謝謝

samlu 提到...

你不知道伺服器的 IP 那你怎麼連上的?

匿名 提到...

請問sam大~我依您的方法透過php取MySQL的一筆資料,結果從得一整頁網頁的程式碼,想要取的資料夾於BODY中,請問有辦法replace掉不要的部分嗎~或是有更好的做法呢? 謝謝!

猴子 提到...

你好

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);
}


可以將收到的資料不要轉成文字嘛
我想將收到的值再計算

黃小龍 提到...

我和樓上猴子兄有一樣的問題 我是將回傳字串再轉回int 但似乎無法成功.
請求版主指導
我是在做會員登入的部分要回傳Uid 有其他辦法也麻煩提點一下 謝謝

張貼留言