สำหรับ App บน Android นั้น ส่วนใหญ่ มักจะมีส่วนของการเชื่อมต่อกับฝั่ง Server เพื่อรับ-ส่งข้อมูลกัน ซึ่งการเชื่อมต่อกับฝั่ง Server นั้น เรามักจะใช้ Class ที่ Implement Interface HttpClient
ซึ่งอยู่ใน Package org.apache.http.client
นั่นเอง
ถ้าใครที่เคยเขียนมาบ้าง จะรู้ว่า การใช้งานมันยุ่งยากมาก ทั้งในด้านของการเขียนคำสั่ง การจัดการกับ Error และ Exception ต่าง ๆ รวมถึงการเขียนคำสั่งไม่ให้อยู่บน Main Thread ตามกฎที่ถูกกำหนดไว้ เพื่อป้องกันการเกิด ANR (Application Not Responding) จากการเชื่อมต่อที่ใช้เวลานาน
และที่สำคัญ Interface HttpClient
นั้น ทาง Google ได้ประกาศให้เลิกใช้แล้ว ใน API level 22 (Android 5.1) ดังรูปด้านล่าง

และแนะนำให้ใช้งาน openConnection()
จาก Class URL
ที่ Package java.net
แทน
ก็แค่ Deprecated เอง มันก็ยังใช้ได้อยู่ไม่ใช่เหรอ
ประโยคข้างบน คงมีหลาย ๆ คนคิดแบบนี้ แต่ความเป็นจริงแล้ว เมื่อทาง Google ประกาศเลิกใช้แล้ว นั่นหมายความว่า ในอีกไม่กี่เวอร์ชั่นต่อจากนี้ไป มันจะถูกถอดออกไป…
และการมาของ Android M ที่เพิ่งเปิดตัวไปในงาน Google I/O 2015 ที่เพิ่งจบไปไม่กี่วันมานี้ แน่นอนว่าจะต้องมี API Level ใหม่ออกมา (ณ ตอนนี้เป็น Developer Preview กำหนด API Level เป็น 22+ หรือ mnc-preview-1) ซึ่งใน Developer Documentation ก็พบว่า Package org.apache.http.client
นั้น ได้ถูกถอดออกไปแล้ว

การที่ Package org.apache.http.client
ถูกถอดออกนั้น เป็นผลให้ Interface HttpClient
ไม่สามารถใช้งานได้อีกต่อไป
สำหรับใครที่คิดว่า ตัวเองไม่ได้ใช้ HttpClient
แต่ใช้ Library นั่น อย่าเพิ่งดีใจไปครับ ลองตรวจสอบดูก่อนนะ เพราะเท่าที่รู้มา หลาย ๆ Library ใช้ HttpClient
ทำงานอยู่เบื้องหลัง แปลว่าจะไม่สามารถใช้งานได้แล้ว…
สำหรับบทความนี้ ผมจึงขอแนะนำ OkHttp Library ที่พัฒนาโดย Square, Inc. ใช่แล้วครับ Square ที่ทำเกมส์ซีรีส์ชื่อดังอย่าง Final Fantasy นั่นแหล่ะ โดยจุดเด่นของ Library นี้อยู่ที่ ใช้ Class HttpURLConnection
ที่ Package java.net
นั่นเอง
อันที่จริง จุดเด่นอื่น ๆ มีอีกเยอะแยะมากมาย เช่นการใช้งานได้กับหลากหลาย Protocol แต่ว่า นาทีนี้ คงหนีไม่พ้นเรื่อง Deprecated นี่ล่ะ
โดยผมจะแนะนำถึงการใช้งานเบื้องต้นเท่านั้น ซึ่งก็น่าจะเพียงพอต่อการใช้งานแล้วล่ะ
List of contents
How to use OkHttp
ก่อนอื่น ต้องทำการเพิ่ม Library ให้กับ Project ของเราซะก่อน สำหรับ Android Studio นั้น สามารถเพิ่ม Dependency ใน build.gradle ของ Module app เรา ดังนี้
1 |
compile 'com.squareup.okhttp:okhttp:2.4.0' |
แล้วทำการ Sync ให้เรียบร้อย ซึ่ง OkHttp จะมีการใช้งาน Okio Library อยู่เบื้องหลัง ซึ่งมันจะถูก Sync มาด้วยแล้ว ไม่จำเป็นต้องเพิ่ม Dependency ของ Okio ลงไปด้วย
ถ้าไม่ได้ใช้ Android Studio ก็สามารถหา .jar มาใช้ได้จากเว็บ OkHttp
ตรวจสอบ Version ล่าสุดของ OkHttp Library ได้ที่ http://square.github.io/okhttp/
ก่อนจะเริ่มทดลองใช้งานกัน ต้องทำความเข้าใจกันก่อนว่า OkHttp Library นั้น ไม่ได้ทำงานบน Thread ใหม่ กรณีใช้งานแบบ Synchronous และจะทำงานบน Thread ใหม่ให้ในกรณีการใช้งานแบบ Asynchronous แต่ว่าไม่ได้ส่งผลการทำงานกลับมายัง Main Thread ให้ด้วย เพราะฉะนั้น เป็นหน้าที่ของเราที่ยังจะต้อง Handle เรื่องเหล่านี้เอง ว่าแล้วไปลองกันเลยดีกว่า
Synchronous Get
การใช้งานแบบ Synchronous นั้น อย่างที่เกริ่นไปแล้วว่า ไม่ได้ทำงานบน Thread ใหม่ให้ ดังนั้นเราจำเป็นต้องสร้าง Thread ใหม่เพื่อทำงาน และส่งผลการทำงานกลับไปยัง Main Thread เอง ในตัวอย่างต่อไปนี้จะใช้ AsyncTask
ในการทำงาน โดยผมจะทำการ Implement จาก Anonymous Class และใช้งานเลย ดังนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 |
new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... voids) { // TODO : ... } @Override protected void onPostExecute(String string) { super.onPostExecute(string); // TODO : ... } }.execute(); |
ในบรรทัดที่ 1 ทำการสร้าง Instance จาก Anonymous Class โดยจะ Implement 2 Method คือ doInBackground()
และ onPostExecute()
และทำการเรียกใช้ Method execute()
เพื่อทำงานเลย ในบรรทัดที่ 13
ทีนี้เราก็ใช้งาน OkHttp ในการเชื่อมไปยัง Server ได้แล้ว โดยการเขียนคำสั่งใน Method doInBackground()
ดังนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
OkHttpClient okHttpClient = new OkHttpClient(); Request.Builder builder = new Request.Builder(); Request request = builder.url("http://date.jsontest.com/").build(); try { Response response = okHttpClient.newCall(request).execute(); if (response.isSuccessful()) { return response.body().string(); } else { return "Not Success - code : " + response.code(); } } catch (IOException e) { e.printStackTrace(); return "Error - " + e.getMessage(); } |
ก่อนอื่นต้องสร้าง Instance จาก Class OkHttpClient
ขึ้นมาก่อน และใช้ Class Request.Builder
ช่วยสร้าง Request
ให้ (ในตัวอย่างนี้ จะเชื่อมต่อไปที่ http://date.jsontest.com/ ลองเปลี่ยนไปเป็น URL อื่นก็ได้นะ) แล้วจึงนำไปเชื่อมต่อกับ Server โดยนำ OkHttpClient
เรียกใช้งาน Method newCall()
จะได้ Return มาเป็นชนิด Call
แล้วเรียกใช้งาน Method execute()
อีกทีหนึ่ง
ตรง Method
execute()
นี่ล่ะ ที่จะทำงานเป็นแบบ Synchronous
Method execute()
จะ Return มาเป็นชนิด Response
เมื่อการรับ-ส่งข้อมูลกับ Server เสร็จสิ้น โดยสามารถตรวจสอบผลการทำงานได้จาก Method isSuccessful()
และหากทำงานสำเร็จ สามารถอ่านข้อมูลได้จาก Method body()
ของ Response
ซึ่งจะ Return มาเป็น ResponseBody
ซึ่งจะมี Method string()
ในการอ่านข้อมูลออกมา ดังในตัวอย่างบรรทัดที่ 9 ด้านบน
เมื่อเรา Return ผลการทำงานออกไปจาก Method doInBackground()
ของ AsyncTask
แล้ว ก็ถึงเวลาที่จะนำผลการทำงานไปใช้ โดยเขียน Code ไว้ใน Method onPostExecute()
ซึ่ง Method นี้ จะทำงานบน Main Thread แล้วล่ะ ดังนี้
1 2 3 4 5 6 |
@Override protected void onPostExecute(String string) { super.onPostExecute(string); textResult.setText(string); } |
ก็ไม่ได้มีอะไรซับซ้อนเลย เพราะ AsyncTask
ในตัวอย่างนี้ เราเป็นคนกำหนดให้ Return เป็นชนิด String อยู่แล้ว นำไปใช้งานกับ View ได้เลย
เมื่อรวม ๆ Code แล้ว ก็จะได้เป็น…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
new AsyncTask<Void, Void, String>() { @Override protected String doInBackground(Void... voids) { OkHttpClient okHttpClient = new OkHttpClient(); Request.Builder builder = new Request.Builder(); Request request = builder.url("http://date.jsontest.com/").build(); try { Response response = okHttpClient.newCall(request).execute(); if (response.isSuccessful()) { return response.body().string(); } else { return "Not Success - code : " + response.code(); } } catch (IOException e) { e.printStackTrace(); return "Error - " + e.getMessage(); } } @Override protected void onPostExecute(String string) { super.onPostExecute(string); textResult.setText(string); } }.execute(); |
อีกนิด…
ถ้าใครที่เขียนเว็บมานานพอสมควร จะทราบว่า ยังมีส่วนหัวของข้อมูลอีกส่วนนึง ที่เรียกว่า Header ซึ่งจะอยู่ก่อนส่วนของข้อมูลที่เราอ่านมาได้แล้วจากตัวอย่างข้างบน โดยสามารถอ่านข้อมูลออกมาได้จาก Response
ดังนี้
1 2 3 4 5 |
Headers headers = response.headers(); for (String header : headers.names()) { String headerName = header; String headerValue = headers.get(header); } |
และเนื่องจากจะต้องใช้งานผ่าน Internet ก็อย่าลืมเพิ่ม Use Permission ในไฟล์ AndroidManifest.xml ด้วย ดังนี้
1 |
<uses-permission android:name="android.permission.INTERNET" /> |
Asynchronous Get
มาดูวิธีใช้งานแบบ Asynchronous กันบ้าง ใน Library ได้เตรียม Method enqueue()
ของ Class Call
ไว้ให้ใช้งาน โดยจะทำงานในลักษณะ Asynchronous โดยจะแยกการทำงานไปอยู่บน Thread ใหม่ให้ ส่งผลการทำงานกลับมาในลักษณะของ Listener ซึ่งเราจะต้อง Implement Interface Callback
เพื่อรอรับผลการทำงาน เพียงแต่ว่า ผลการทำงานที่ส่งกลับมาให้ จะไม่ได้อยู่บน Main Thread ซึ่งตรงนี้ เราจะต้องจัดการเอาเอง แต่ก็ไม่ได้ยุ่งยากอะไร มาดู Code กันเลยดีกว่า…
เริ่มด้วยการสร้าง OkHttpClient
และ Request
แบบเดียวกับ Synchronous เลย (ไม่ต้องอธิบายซ้ำเน๊อะ) แล้วนำ OkHttpClient
มาเรียกใช้งาน Method newCall()
จะได้ Return มาเป็นชนิด Call
แล้วเรียกใช้งาน Method enqueue()
อีกทีหนึ่ง
ตรง Method
enqueue()
นี่ล่ะ ที่จะทำงานเป็นแบบ Asynchronous
Method enqueue()
นั้น ต้องการ Parameter 1 ตัว เป็นชนิด Interface Callback
ซึ่งในตัวอย่างด้านล่างนี้ จะ Implement ลงไปแบบ Inline เลย ซึ่ง Interface Callback
จะให้เรา Implement 2 Methods คือ onFailure()
และ onResponse()
คงเดาออกกันเน๊อะว่า Method ไหน จะเกิด Callback กลับมาเมื่อไหร่…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
OkHttpClient okHttpClient = new OkHttpClient(); Request.Builder builder = new Request.Builder(); Request request = builder.url("http://md5.jsontest.com/?text=https://github.com/first087/Android-OkHttp-Example").build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { // TODO : ... } @Override public void onResponse(Response response) throws IOException { // TODO : ... } }); |
ทั้ง
onFailure()
และonResponse()
จะยังไม่ได้กลับมาทำงานอยู่บน Main Thread
ถ้าอย่างนั้น ผมขอเพิ่มในส่วนของการกลับไปยัง Main Thread ไว้ด้วยเลย โดยการเพิ่ม Method updateView()
ดังนี้
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { // TODO : ... } @Override public void onResponse(Response response) { // TODO : ... } public void updateView(final String strResult) { runOnUiThread(new Runnable() { @Override public void run() { textResult.setText(strResult); } }); } }); |
และในบรรทัดที่ 8 นั้น ผมจะลบ throws IOException
ออกไป เนื่องจากผมต้องการจัดการกับ Exception เองภายใน Method onResponse()
กลับมาที่ Method onFailure()
จะถูก Callback มาเมื่อเกิดความผิดพลาดเกิดขึ้น โดยเราสามารถตรวจสอบ Request
ที่เกิดความผิดพลาดได้จาก Parameter ตัวแรก และ IOException
ที่เกิดขึ้นจาก Parameter ตัวที่สอง
Code ด้านล่างนี้ จะนำ Exception Message ส่งไปให้ Method updateView()
ดังนี้
1 2 3 4 |
@Override public void onFailure(Request request, IOException e) { updateView("Error - " + e.getMessage()); } |
ส่วน Method onResponse()
จะถูก Callback เมื่อการรับ-ส่งข้อมูลกับ Server เสร็จสิ้น โดยจะมี Response
ส่งกลับมาให้ด้วยใน Parameter ซึ่งก็จะเหมือนกับแบบ Synchronous แล้วล่ะ
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@Override public void onResponse(Response response) { if (response.isSuccessful()) { try { updateView(response.body().string()); } catch (IOException e) { e.printStackTrace(); updateView("Error - " + e.getMessage()); } } else { updateView("Not Success - code : " + response.code()); } } |
รวม ๆ Code ก็จะได้เป็น…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
OkHttpClient okHttpClient = new OkHttpClient(); Request.Builder builder = new Request.Builder(); Request request = builder.url("http://md5.jsontest.com/?text=https://github.com/first087/Android-OkHttp-Example").build(); okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Request request, IOException e) { updateView("Error - " + e.getMessage()); } @Override public void onResponse(Response response) { if (response.isSuccessful()) { try { updateView(response.body().string()); } catch (IOException e) { e.printStackTrace(); updateView("Error - " + e.getMessage()); } } else { updateView("Not Success - code : " + response.code()); } } public void updateView(final String strResult) { runOnUiThread(new Runnable() { @Override public void run() { textResult.setText(strResult); } }); } }); |
อย่าลืม Use Permission เน้ออออ
Accessing Headers
สำหรับกรณีที่ต้องการ Custom Header สำหรับการ Request นั้น จะมี Method ใน Class Request.Builder
ให้เราใส่ข้อมูลลงไปได้ ตัวอย่างเช่น
1 2 3 4 5 6 7 |
Request.Builder builder = new Request.Builder(); Request request = builder .url("https://api.github.com/repos/square/okhttp/issues") .header("User-Agent", "OkHttp Headers.java") .addHeader("Accept", "application/json; q=0.5") .addHeader("Accept", "application/vnd.github.v3+json") .build(); |
ใน Code ด้านบน จะใช้ Method header()
ในการกำหนดข้อมูล Header และจะใช้ Method addHeader()
ในการเพิ่มข้อมูลลงไปอีกนั่นเอง
เห็น API หลาย ๆ ตัว ชอบให้ส่งข้อมูลไปกับ Header เพราะฉะนั้น OkHttp Library ก็ตอบโจทย์ตรงนี้ด้วยนะ
หลังจากนั้น จะ Call แบบ Synchronous หรือ Asynchronous ก็เลือกตามใจชอบเลย…
Posting a String
มาดูการโพสข้อมูลไปยัง Server กันบ้าง เราสามารถใช้ Method post()
ของ Class Request.Builder
ในการโพสข้อมูลได้ ซึ่งมันต้องการ Parameter เป็นชนิด Class RequestBody
แต่ใน Class RequestBody
นั้น มี Static Method ชื่อว่า create()
ที่จะ Return เป็น RequestBody
มาให้ ตัวอย่างด้านล่างนี้ จะเป็นการโพส String ไปยัง Server นั่นเอง
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
MediaType mediaType = MediaType.parse("text/x-markdown; charset=utf-8"); String postBody = "" + "Releases\n" + "--------\n" + "\n" + " * _1.0_ May 6, 2013\n" + " * _1.1_ June 15, 2013\n" + " * _1.2_ August 11, 2013\n"; Request.Builder builder = new Request.Builder(); Request request = builder .url("https://api.github.com/markdown/raw") .post(RequestBody.create(mediaType, postBody)) .build(); |
สำหรับ Method create()
ของ Class RequestBody
นั้น ยังมีอีกหลาย Overload ให้เลือกใช้ สามารถ Post ข้อมูลชนิดอื่น ๆ ได้อีกด้วย
เป็นไงกันบ้างครับ OkHttp Library น่าใช้งานไม๊ครับ
สำหรับบทความแนะนำ OkHttp Library ก็ขอจบแค่นี้ละกันนะ แค่นี้ก็คงเพียงพอต่อการนำไปประยุกต์ใช้กันแล้วล่ะ ถ้าอยากลงลึกไปมากกว่านี้ แนะนำให้อ่าน wiki ของ OkHttp Library บน GitHub ดูครับ ละเอียดยิบเลยทีเดียว
หรือจะศึกษารายละเอียด Code ทั้งหมด ที่ผมนำมาเขียนบทความนี้ ก็ดูได้ที่ Project บน GitHub ของผมได้เลย
Permalink
สอบถามหน่อยครับ
ตรง Asynchronous Get onResponse นี่คือเซิฟเวอร์ส่งกลับมาใช่ไหมครับ แล้วถ้าเกิดว่าทางเซิฟเราส่งกลับมาเป็นรูป เราต้องทำ bitmap มั้ยครับ หรือต้องทำยังไง ใช้เมธอดรับรูปยังไงครับ
Permalink
ถ้า URL ที่ร้องขอ เป็น API แนะนำให้นำรูปแปลงเป็น base64 อยู่ใน JSON อีกที แล้วนำมา Convert ที่ App ครับ
แต่ถ้า URL ที่ร้องขอ เป็นรูปเลย แนะนำให้ใช้ Lib อื่นครับ