前言
若您開發的App有保存資料的需求,多半希望將資料存集至遠端伺服器,比較建議的作法是透過PHP, JSP, AST.NET等技術,以Web方式實作資料傳輸的媒介,存取資料即使用POST或GET的方式來進行。
在實作前有件事情需要注意,Android 4.0規定了存取網路的操作必須執行在另一Thread(執行緒)中,為了因應此規定,目前有兩大方向可供選擇:
- 使用Runnable與Handler:這種方法比較複雜,但彈性高,可依自己的需求規劃,同時在Handler的使用上需注意Memory leak之情況。
- 使用非同步任務(AsyncTask):此為相對簡單的方法,依規定好的格式即可以輕易完成網路存取。
接下來,就開始以第一種方式實作(Runnable + Handler),簡單一點的AsyncTask預計寫在下一篇(part2)。以下以重點說明為主,完整的範例已放在GitHub上,有需要的參考一下即可。
Step 0. 理解執行架構與流程
這邊用張簡圖表示,描述自步驟1發出請求起,至將遠端資料顯示至TextView的第驟6。Handler, Thread與Activity的互動關係大略是這樣。
Step 1. 開啟網際網路存取權限
為專案加入網際網路存取權限,找到AndroidManifest.xml,加入 android.permission.INTERNET 。
Step 2. HTTP請求與回應
以下程式摘錄自SendDataRunnable.java,可以直接套用,範圍程式中已把它包成method,若您想要自己另外處理,參數對照一下即可。這段程式碼有幾個重點:
- 第n列,有些教學範例會使用HttpClient這個類別來操作,但其已被Android棄用,因此這邊改以HttpURLConnection實作。
- 第n列,URL物件需要傳入Server上某個php檔案 (或其他Web程式)的路徑。
- 第n列,HTTP_OK這個常數值為200,表示正常狀態,確認正常了才開始讀取Response,你也可以再該處加上else,處理一些異常狀態。
- 第n列,composePostString( )負責組合參數,需傳入HashMap作為數個參數的集合(HashMap有Tag與Value對應的概念),詳見範例程式。
private String sendData(String target, HashMap params){
String responseData = "";
try {
URL url = new URL(target);
//建立與目標之間的連線
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
OutputStream stream = connection.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stream, "UTF-8"));
String c = composePostString(params);
writer.write(c);
writer.flush();
writer.close();
stream.close();
int httpResponseCode = connection.getResponseCode();
if(httpResponseCode == HttpsURLConnection.HTTP_OK) {
String oneLine = "";
BufferedReader reader = new BufferedReader(
new InputStreamReader(connection.getInputStream()));
while ((oneLine = reader.readLine()) != null) {
responseData += oneLine;
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return responseData;
}
Step 3. 實作Run()
run( )是Runnable介面中唯一的方法,當其被執行時會自動呼叫,在範例程式中拿來呼叫Step 2.所寫的sendData( )。
此段程式相當簡單,sendData( )會傳回Server回應的資料,故需檢查資料長度是否正常,並針對各情況進行處理。傳回來的資料會放在response變數中,至此所有動作仍不在Android的主執行緒上執行,若你要在這邊把資料更新到UI上是無法存取的,所以這邊有個handler.obtainMessage( ),用它把資料傳至主執行緒上的Handler。
但有一點請注意,obtainMessage傳入兩個參數,第一個是int型別的值,這是讓你識別目前的情況用的,姑且稱它為「識別碼」,你可以自己決定該如何實作,例如:1代表新增成功、2代表刪除成功、3代表取得清單資料等。為了方便管理,也可以宣告常數來處理,讓程式更易維護。
@Override
public void run() {
String response = sendData(this.path, this.params);
if(response.length() > 0) {
//TODO
handler.obtainMessage(0, response).sendToTarget();
}else{
//TODO
}
}
Step 4. Handler
在上一步驟中,我們已經取得Server回應的字串資料,為了在主執行者中接收這個字串資料,需要準備Handler來承接,礙於Memory Leak的問題(有機會再談),建議以內部靜態類別來實作,完整程式請參考範例中的main.java。
這邊定義了HttpHandler類別,其繼承Handler (來自android.os.Handler,別import錯了)。
請特別注意幾個地方:
- 第n列,將WeakReference強迫轉型為Main,這個Main需繼承當初建立WeakReference傳入的泛型 (本例是Activity),白話的說Main是Activity,但其具有Activity沒有的成員與方法(因為繼承),因此我們要把它強迫轉型為Main,以方便進行後續處理。
- 第n列,這邊用if判斷了msg.what值,該值就是在Step 3所傳入的int參數(本例為0),所以這邊檢查等於0時把Server傳回的response字串顯示在TextView上。
- 承上,response字串也是是在Step 3傳入,但其型別為Object,所以這邊取出時要再強迫轉型為字串。
static class HttpHandler extends Handler {
WeakReference weakReference;
public HttpHandler(Activity activity) {
weakReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Main main = (Main)weakReference.get();
if(msg.what == 0) {
//TODO 依what判斷後續處理
main.txvResponse.setText((String)msg.obj);
}
}
}
Step 5. 串接一切
各細節寫好了,現在要把他們串起來!實際上該封裝的東西也都裝好了,所以真正在呼叫時動作不會太多。
首先,在呼叫前確保handler與params兩個物件有新建了,兩者的作用在前面都有提過。
handler = new HttpHandler(this);
HashMap<String, String> params = new HashMap<>();
接著填入要送至Server的參數,每個參數都會有Tag (或稱為Name)與Value的對應。
params.put("name", "tom");
最後要將整個HTTP的請求與回應動作放在另一個執行緒中執行,因此我們這麼處理。記得網址的部分自己換掉。
new Thread(
new SendDataRunnable(handler, "http://www.example.com", params)
).start();
小結
上述的程式還可以再封裝的更細緻,但有時封過頭會綁手綁腳的,大家若要直接使用範例程式,請注意裡面標示//TODO的地方。
這邊有小建議,請盡可能的將識別碼、網址或ip之類的資源統一管理,看是要宣告成常數,還是把它放在string.xml中。
還有!安全議題也需注意,本篇沒有特針對安全部分進行討論,若你要傳輸的資料是有安全顧慮的,請以https的方式進行,亦需留意Server端的安全措施。