วิธีใช้งาน AsyncTask บน Android

Published on
Android
2014/05/android-asynctask-tutorial
Discord

เชื่อว่าหลายๆคนที่เขียน Android Application ต้องมีความคุ้นเคยกับ AsyncTask กันแน่นอน โดยเฉพาะเมื่อต้องการเชื่อมต่อ Internet ผ่าน HTTP ต่างๆ ล้วนต้องใช้ AsyncTask มาช่วยทั้งสิ้น หรือเวลาต้องการประมวลผลอะไรไว้ที่ background thread เพื่อไม่ให้กระทบกับตัว Main UI วันนี้ก็เลยทำเป็นบทความ อธิบาย AsyncTask วิธีการใช้งาน AsyncTask หลีกเลี่ยงการเกิด Exception เมื่อติดต่อผ่าน HTTP โดยใช้ AsyncTask แทน โดยบทความนี้จะโฟกัสไปที่การเชื่อมต่อ Internet โดยใช้ AsyncTask เพื่อหลีกเลี่ยง OnMainThreadException ครับ

AsyncTask คืออะไร?

AsyncTask นั้นเป็น abstract class ที่ทาง Android นั้นจัดเตรียมมาให้เราเพื่อทำการประมวลผล หรือทำงานเป็น background โดยไม่ต้องไปยุ่งกับตัว UI หลัก อย่างเช่น กรณีต้องการดึง content จากเว็บไซต์เว็บหนึ่ง เพื่อมาแสดงบนแอพพลิเคชันของเรา เราก็จะใช้ AsyncTask ช่วยในการคำนวณ ประมวลผล โดยเมื่อ AsyncTask ทำงานอยู่ มันจะไม่กระทบกับหน้าจอ UI ของเรา

การใช้งาน AsyncTask

สำหรับคลาส AsyncTask ดู Reference ได้ที่นี่ จะมีการใช้งานดังนี้ (ตัวอย่าง ผมไม่ได้ทำการ override เมธอดมาทั้งหมดนะครับ)

private class HttpTask extends AsyncTask<String, Integer, String>  {

    protected String doInBackground(String... params)   {
        return "result";
    }

    protected void onProgressUpdate(Integer... values) {

    }

    protected void onPostExecute(String result)  {

    }
}

เวลาเรียกใช้การก็แค่

new HttpTask().execute("https://devahoy.com");

จากโครงสร้าง AsyncTask ด้านบน เรามาดู arguments ของแต่ละตำแหน่งว่ามันคืออะไรกันดีกว่า จาก

AsyncTask<String, Integer, String>
  • String : ตัวแรกนั้นคือ parameter ที่ส่งไปครับ สมมติตัวอย่างนี้จะใช้ดึง content จากเว็บ params นี้ก็จะเป็น URI ของเว็บไซต์
  • Integer: ตัวนี้คือ ค่า Progress ไว้สำหรับแสดง ค่า Progressbar หรือทำพวก Loading ต่างๆ
  • String: ตัวที่สาม คือ ค่าผลลัพท์ ที่ต้องการ return กลับไป

เมื่อรู้จักทั้ง 3 parameters แล้ว ต่อไปก็มารู้จัก เมธอดของมัน ว่าจะเรียกใช้ตอนไหน บ้าง

protected String doInBackground(String... params)   {

}

เมธอดนี้ จะเป็นเมธอดที่ คอยทำงานเป็น Background ให้เรา เช่น เชื่อมต่อ HTTP ไปยังเว็บไซต์ โดยรับ ที่อยู่ URI เว็บจากค่า String ตัวแรกของ AsyncTask มา จากนั้นก็ทำการประมวลผล

protected void onProgressUpdate(Integer... values) {

}

เมธอดนี้จะเอาไว้แสดงค่า ProgressBar ว่าทำงานไปเท่าไหร่แล้ว โดยมากก็จะเป็นการนับ 0 ถึง 100 โดยค่า 0 - 100 มันก็ได้มาจาก Integer ซึ่งเป็น parameter ตัวที่สองของ AsyncTask แต่ในบทความนี้ ผมไม่พูดถึงครับ เพราะว่าโหลดเนื้อหาจากเว็บไซต์ คิดว่าไม่จำเป็นต้องใช้ ส่วนมากเอาไว้ใช้เวลาโหลดรูปภาพขนาดใหญ่ จะเห็นผลดีกว่า

protected void onPostExecute(String result)  {

}

เมธอดนี้จะถูกเรียกเมื่อการทำงานที่ Background นั้นจบลงแล้ว โดยรับ parameter เป็น String ที่ได้ผลลัพธ์มาจากเมธอด doInBackground ส่วนมากเมธอดนี้จะเอาไว้อัพเดท set ค่าต่างๆ ครับ เช่น setText ให้กับ EditText หรือ set ค่าให้กับ WebView เป็นต้น

เอาละ เมื่อรู้ถึงการใช้งาน เบื้องต้นของ AsyncTask ไปแล้ว คราวนี้มา Learning by Doing กันครับ เรียนรู้จากการลงมือทำ เรียนรู้จากตัวอย่างครับ

สร้างโปรเจ็ค

ทำการสร้างโปรเจ็ค จะด้วย Android Studio หรือ Eclipse ก็แล้วแต่ หากสร้างไม่เป็น อ่านได้จากบทความข้างล่างนี้ หากทำเป็นแล้ว ข้ามไปเลย

เมื่อสร้างโปรเจ็คได้แล้ว จะโฟกัสแค่ 2 ไฟล์ คือ MainActivity.java สำหรับเขียนโค๊ด และ activity_main.xml สำหรับหน้า Layout

สำหรับหน้าเลเอาท์ จะมีแค่ EditText ให้ใส่ URI ของเว็บไซต์ จากนั้นก็มีปุ่ม Button ไว้สำหรับ Submit ค่า สุดท้ายก็เป็น WebView เอาไว้แสดงเนื้อหาของเว็บไซต์ หน้าตาออกแบบคร่าวๆ ก็เป็นดังนี้

Layout

ตัวไฟล์ activity_main.xml ได้ดังนี้

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:weightSum="1">
        <EditText
            android:id="@+id/text_url"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.8"/>

        <Button
            android:id="@+id/button_ok"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="0.2"
            android:text="OK"/>

        </LinearLayout>

    <WebView
        android:id="@+id/webView_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    </WebView>

</LinearLayout>

มาดูที่ MainActivity.java เริ่มแรก ทำการเชื่อม View ในคลาสกับใน Layout ซะด้วย findViewById()

public class MainActivity extends ActionBarActivity {
    EditText mUrl;
    Button mButtonOK;
    WebView mWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mUrl = (EditText) findViewById(R.id.text_url);
        mButtonOK = (Button) findViewById(R.id.button_ok);
        mWebView = (WebView) findViewById(R.id.webView_content);

    }
....
}

จริงๆ จะ extends ActionBarActivity, FragmentActivity หรือ Activity ก็ได้เหมือนกัน คลาสพวกนี้ทาง Android นั้นทำมาเพื่อ support API ต่ำๆครับ สำหรับรายละเอียด ความแตกต่าง บทความนี้ไม่ขอพูดถึงครับ

ต่อมา ทำการสร้าง inner class ชื่อว่า SimpleTask ครับ และทำการ extends AsyncTask ตามรูปแบบของมันเลย

private class SimpleTask extends AsyncTask<String, Void, String> {

    @Override
    protected void onPreExecute() {
        // Create Show ProgressBar
    }

    protected String doInBackground(String... urls)   {
        String result = "";

        // blah blah

        return result;
    }

    protected void onPostExecute(String result)  {
        // Dismiss ProgressBar
        // updateWebView(result);
    }
}

จากด้านบน ผมให้ AsyncTask มันรับ parameter เป็น URI ของเว็บไซต์ ส่วนใน doInBackground จะเป็นการดึง content จากเว็บไซต์ โดยใช้ DefaultHttpClient ทำการ execute ๊URI ที่ใส่ไป จากนั้นเมื่อได้ผลลัพธืแล้ว ก็ส่งค่าผลลัพธ์ไปยังเมธอด onPostExecute ส่วนในโค๊ดด้านบน ผมยกตัวอย่าง ส่วนมากเค้าจะนิยม ให้ ProgressBar มันแสดงในเมธอด onPreExecute() แล้วก็ dismiss มันในเมธอด onPostExecute() แต่บทความนี้ ขี้เกียจเพิ่มครับ พอดีจัด Layout แบบ Linear ไปแล้ว ขี้เกียจแก้ :D

ส่วนขั้นตอนการ get content ใน เมธอด doInBackground() มีดังนี้

protected String doInBackground(String... urls)   {
    String result = "";
    try {

        HttpGet httpGet = new HttpGet(urls[0]);
        HttpClient client = new DefaultHttpClient();

        HttpResponse response = client.execute(httpGet);

        int statusCode = response.getStatusLine().getStatusCode();

        if (statusCode == 200) {
            InputStream inputStream = response.getEntity().getContent();
            BufferedReader reader = new BufferedReader
                    (new InputStreamReader(inputStream));
            String line;
            while ((line = reader.readLine()) != null) {
                result += line;
            }
        }

    } catch (ClientProtocolException e) {

    } catch (IOException e) {

    }
    return result;
}

เริ่มแรก สร้าง new instance ของ HttpGet โดยรับเอา URI ที่ได้จาก EditText จากนั้นใช้ HttpClient.execute() เพื่อทำการส่ง request ไปที่เว็บไซต์ จากนั้นเว็บไซต์จะส่ง response กลับมา เราก็ใช้ HttpResponse ในการรับค่าที่ส่งกลับมา โดยผมใช้ statusCode เพื่อเช็คว่าส่ง response กลับมาเป็น 200 หรือไม่ ตัวเลขนี้คือ HTTP Response Code หากใช่แสดงว่า ปกติ ก็ให้ทำการอ่านค่าจาก response.getEntity().getContent() ด้วย BufferedReader ตามตัวอย่างเลยครับ หากไม่เข้าใจ ไปอ่านพื้นฐาน File IO ของ Java ได้ครับ

ส่วนเมธอด onPostExecute ผมให้มันไปเรียก private เมธอด ที่ชื่อ updateWebView() โดยประกาศไว้ดังนี้

private void updateWebView(String result) {
    mWebView.getSettings().setJavaScriptEnabled(true);
    mWebView.loadData(result, "text/html; charset=utf-8", "utf-8");
}

สุดท้าย ก็ setLisneter ให้กับ Button ครับ

mButtonOK.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        new SimpleTask().execute(mUrl.getText().toString().trim());
    }
});

โดยเมื่อคลิกที่ปุ่ม ก็ทำการ new SimpleTask ขึ้นใหม่และทำการ execute โดยรับค่าจาก EditText ที่เรากรอก ส่งไปทำงานเป็น Background ใน SimpleTask กระบวนการทำงานทั้งหมดก็มีเท่านี้แล :)

อ้อสุดท้ายจริงๆ อย่าลืม เพิ่ม permission Internet ในไฟล์ AndroidManifest.xml ด้วยครับ

<uses-permission android:name="android.permission.INTERNET" />

Result

ใช้ Android-Async-HTTP แทน

ตัวอย่างนี้ต่อกันเลยนะครับ เพียงแค่เปลี่ยนวิธีการ มาใช้ Library กันบ้าง ตัวอย่างนี้คือใช้ Library ที่ชื่อว่า android-async-http สำหรับ Library ตัวนี้มีประโยชน์มากๆครับ เวลาเราใช้งานพวก HTTP ต่างๆ Features หลักๆของมัน ก็อ่านได้จากเว็บไซต์เลยครับ หลักๆทั่วไปก็พวก

  • asynchronous HTTP request สามารถ handle callback ได้
  • มี GET/POST ให้ใช้
  • Parsing JSON ก็ทำได้ง่ายๆ
  • สามารถดาวน์โหลด Binary File พวกรูปภาพ

การใช้งาน

การใช้งาน android-async-http นั้นง่ายมากๆครับ Add Library ซะก่อน วิธี Add Library ใน Eclipse ไม่ขอพูดถึงนะครับ หาอ่านได้ทั่วไป หรือไม่รู้ ก็ google ครับ

สำหรับ Android Studio เปิด build.gradle แล้วเพิ่มนี้ลงไป ในแท็ก dependencies

dependencies {
    compile 'com.loopj.android:android-async-http:1.4.4'

    compile 'com.android.support:appcompat-v7:+'
    compile fileTree(include: ['*.jar'], dir: 'libs')
}

Trick สำหรับการเพิ่ม Library ใน Gradle จริงๆ ถ้าเป็น Library ของ Maven ก็สามารถเพิ่มได้หมดนะครับเพราะมันจะไปดาวน์โหลดไฟล์จาก Maven อีกที คือ `compile 'GroupId:ArtifactId:version'

จากนั้น เวลาใช้งานก็เพียงแค่

import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;


AsyncHttpClient client = new AsyncHttpClient();
client.get("url", new AsyncHttpResponseHandler() {
    @Override
    public void onSuccess(String response) {
        //respond is a result.
    }
});

ทำการสร้าง instance ของ AsyncHttpClient ขึ้นมา จากนั้นก็เรียกเมธอด get() โดยมี parameter เป็น URI เว็บ และ callback สำหรับส่งผลลัพธ์ด้วย onSuccess()

นำไปประยุกต์ใช้แทน AsyncTask เมื่อกี้แบบง่ายๆก็คือ ตรง onClickListener ของ Button ก็ให้เปลี่ยนมาใช้ AsyncHttpClient แทน AsyncTask เดิม

จากเดิม เมื่อคลิกปุ่ม Button OK โค๊ดเป็นดังนี้

mButtonOK.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        new SimpleTask().execute(mUrl.getText().toString().trim());
    }
});

ก็เปลี่ยนใหม่เป็น

mButtonOK.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        AsyncHttpClient client = new AsyncHttpClient();
        client.get(mUrl.getText().toString(), new AsyncHttpResponseHandler() {
            @Override
            public void onSuccess(String response) {
               updateWebView(response);
            }
        });
    }
});

ทดสอบ ลองกรอก ๊URI แล้วก็กดปุ่ม OK แล้วลองเล่นดูครับ ผลลัพธ์ที่ได้ เหมือนกันเลย จากบทความนี้ก็ได้รู้ว่าเราสามารถเชื่อมต่อ HTTP โดยการใช้ AsyncTask มาช่วยแล้ว เรายังมี Library ที่ชื่อว่า android-async-http ได้อีกด้วย แถมรู้สึกจะดีกว่าตัว AsyncTask ของ Android เองอีก สำหรับ Library สำหรับการ connect HTTP จริงๆ ก็ยังมีอีกหลายตัวครับ ที่ผมเคยใช้ก็เป็น ion ตัวนี้สามารถใช้กับการโหลด ImageView ได้อีกด้วย (เหมือนๆพวก Volley, Picasso, Universal Image Loader) แต่ตัวนี้พิเศษกว่า คือช่วยในการเชื่อมต่อ Networking แถมมี GSON ด้วย หากได้ลองใช้ รับรองติดใจครับ ^^

โค๊ดทั้งหมด

โค๊ดทั้งหมดต่อจากนี้ ผมรวมไปเขียนไว้ใน gist ดีกว่าครับ จะได้สะดวก หากมีการแก้ไข หรือมีการอัพเดทเพิ่มเติม แต่ปกติทุกบทความผมก็เขียนด้วย Markdown แล้วใ้ช้ git อยู่แล้ว เวลาแก้บทความ ก็รู้ว่าตรงไหนมีการแก้ไข แต่ว่าแยกโค๊ดออกไปเลย ก็ยิ่งง่ายขึ้น ประหยัดหน้าเว็บด้วย จะได้ไม่ยาวมาก :)

ดูโค๊ดทั้งหมดบน Gist

Buy Me A Coffee
Authors
Discord