יום שלישי, דצמבר 21

Threads חלק 3: AsyncTask

זה הפוסט השלישי בסדרה הדנה בנושא Threads. שני הפוסטים הקודמים הציגו שתי שיטות לטיפול ב-Threads באמצעות Handler:

בחלק הראשון, Threads עם data messages האינטראקציה עם ה-Thread נעשתה ע"י שליחת הודעות data ל-Handler שרץ במסגרת ה-UI Thread.

בחלק השני, Threads עם runnable, האינטראקציה נעשתה ע"י העברת מודולים מה-Thread להרצה  ב-UI Thread.

כעת נעבור לשיטה השלישית - ה-AsyncTask. זהו class שקיים החל מ-API Level 3 כלומר v1.5. ה-AsyncTask מספק מנגנון שלם להרצת Background Thread, הרצת אלמנטים ב-UI Thread, והעברת פרמטרים בינהם. 

הטיפול בכל התהליכים הנ"ל נעשה בעזרת 4 מתודות של ה-AsyncTas.

ארבעת המתודות הן:

  1. doInBackground  - מתודה זו היא החלק שרץ ב--Thread החדש. זו המתודה היחידה שחייבים לממש, והיחידה שלא רצה ב-UI Thread.

  2. onProgressUpdate - מתודה זו רצה ב-UI Thread. היא מקבלת נתונים מ-

    doInBackgroun בעזרת מנגנון אותו נראה כמובן בהמשך, ויכולה לעבד אותם ב-Thread הראשי.

  3. onPreExecute - מתודה זו מופעלת לפני הקריאה ל-doInBackground, ומאפשרת לבצע כל מיני איתחולים.

  4. onPostExecute - מתודה זו מופעלת אחרי ש-doInBackground סיים את עבודתו. היא מקבלת ממנו את התוצאות ויכולה לעבד אותן.


קיימות עוד מספר מתודות נוספות. נשתמש  בשתיים:

cancel - על פי התעוד, היא מנסה לבטל את פעולת ה-Task, אבל הניסוח לא מדויק: היא מפעילה את הסימון isCancelled אבל לא עושה כלום מעבר לכך. הכוונה היא להמנע מהריגת ה-Task מבחוץ. בדוגמא הנ"ל, ה-Task בודק את isCancelled ומפסיק את ריצתו בהתאם.

isCancelled - כמו שתואר לעי"ל, נותן אינדיקציה על פקודת cancel שניתנה.


הפעלת ה-AsyncTask
ההפעלה נעשית ע"י המתודה execute.

  1. הדיאגרמה הבאה מתארת את התהליכים ב-AsyncTask


הסבר לדיאגרמה:

המספרים בתוך הצורות מתארים את סדר הפעולות.

הקווים המקווקוים מראים את מעבר הפרמטרים.

מעבר הפרמטרים ב-AsyncTask

במערכת שלושה סוגים של פרמטרים:

1. הפרמטרים שמוכנסים ל-doOnBackground.

פרמטרים אלה מועברים עם הרצת האובייקט- פקודת execute .דוגמא:

 

        asyncTaskExample.execute(sleepTime, taskId);


 

2. הפרמטרים שמועברים ל-onProgressUpdate מ-doOnBackground תוך כדי מהלך הריצה.

אלה מועברים עם הפקודה publishProgress. דוגמא:

                   publishProgress(counter++, taskId);
 


3. הפרמטרים שמועברים ל-onPostExecute מ-doOnBackground כשאהאחרון מסיים.

כאן מדובר ב-return של doOnBackground.

הכרזת סוג הפרמטרים שורת הכותרת של ה-AsyncTask class.

בשורת הכותרת מוכרז הסוג של כל אחד מ-3 הפרמטרים הנ"ל.  הסוג יכול להיות URL, String, Integer וכו. דוגמא לשורת הכותרת:

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

שורה זו קובעת כי:

הפרמטרים  שמוכנסים ל-doOnBackground יהיו מסוג Integer.

הפרמטרים שמועברים ל-onProgressUpdate יהיו מסוג Integer.

הפרמטרים שיועברו ל-onPostExecute מסוג String.

אם אין העברת פרמטרים מסוג כלשהו, יש סמן Void באותו מקום (V גדולה!). 

דוגמא: נניח שאין העברת פרמטרים באף אחד מהשלושה:

private class SyncTaskExample extends AsyncTask <Void, Void, Void>{

הערה חשובה: למרות שבמקרה שהפרמטר השלישי הוא Void ה-doOnBackground לא צריך להחזיר ערך כלשהו, חייבים לרשום שם ( return(null. (אני הפסדתי כאן מעט זמן חיים).


תאור הדוגמא

  • הדוגמא תציג מימוש של ה- AsyncTask.

  • ה-AsyncTask יהיה class פנימי בתוך Activity Task.

  • ההפעלה נעשית מ-onCreate של ה-Activity.

  • ה-AsyncTask ישלח counter מה-background לתצוגה על המסך.

  • בעזרת כפתור אפשר להפסיק ולהתחיל את ה-Task.


הנה תמונה של ה-UI


הנה התוכנית בשלמותה:



  1. import android.app.Activity;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.os.SystemClock;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.Toast;


  2. public class AsyncTaskActivity extends Activity {
        EditText editTextThreadOutput;
        Button buttonThreadOnOff;
        AsyncTaskExample asyncTaskExample;
           Integer sleepTime = 100;
           Integer taskId = 1;
        /** Called when the activity is first created. */
      
      @Override public void onCreate(Bundle state) {
            super.onCreate(state);
            setContentView(R.layout.main);

  3.        editTextThreadOutput = (EditText) findViewById(R.id.edit_text2);

  4.    buttonThreadOnOff = (Button)findViewById(R.id.button_thread_onoff);
            buttonThreadOnOff.setOnClickListener(
            new Button.OnClickListener() {public void onClick(View v) {
                if(asyncTaskExample.isCancelled()){
                    asyncTaskExample = new AsyncTaskExample();
                    asyncTaskExample.execute(sleepTime, taskId);
                 }
                else{
                    if(!asyncTaskExample.cancel(true))
                        Toast.makeText(AsyncTaskActivity.this,"Cancel Failed", Toast.LENGTH_LONG).show();
                 }
            } });

  5.         asyncTaskExample = new AsyncTaskExample();
  6.         asyncTaskExample.execute(sleepTime, taskId);
  7.     }
  8.  
  9.     private class AyncTaskExample extends AsyncTask <Integer, Integer, String>{
  10.         protected String doInBackground(Integer... loopParams) {
  11.             boolean running = true;
  12.             taskId = loopParams[1];
  13.             int counter = 0;
  14.             while(running){
  15.                    publishProgress(counter++, taskId);
  16.                 SystemClock.sleep(loopParams[0]);
  17.                 if(isCancelled())
  18.                     running = false;
  19.             }
  20.             return("We are done!");
  21.         }
  22.         protected void onProgressUpdate(Integer... progress) {
  23.             editTextThreadOutput.setText("Counter of Task No." + progress[1] + "= " +progress[0]);
  24.         }
  25.         @Override
  26.         protected void onPostExecute(String result) {
  27.             Toast.makeText(SyncTaskActivity.this, result, Toast.LENGTH_SHORT)
  28.                 .show();
  29.         }
  30.         @Override
  31.         protected void onCancelled() {
  32.             Toast.makeText(SyncTaskActivity.this, "cancelled", Toast.LENGTH_SHORT)
  33.                 .show();
  34.         }
  35.     }
  36. }


 

 ה-Activity class בנוי  משני חלקים עיקריים, כל אחד נצבע בצבע שונה:

 

  1. בצהוב - המתודה onCreate של ה-class הראשי -Activity class. כאן מופעלים כל התהליכים, כולל איתחול והפעלת האובייקט של AsyncTask

  2. באפור - ה-class הפנימי, ה-AsyncTask .

    (יתכן שהייתי צריך לשים קצת אדום כדי לא לקפח.)

     


נתחיל בעיקר: ה-AsyncTask. 

תאור הפעולה

  • ה-Task מקבל שני פרמטרים: Task Id ו- sleep.
  • הוא מבצע loop אינסופי.

  • כל מעבר של ה-loop כולל:

    • הגדלת counter

    • שליחת ה-counter ביחד עם ה-taskId לתצוגה ב-UI.

    • המתנה ב-sleep, כשאורך ההמתנה בהתאם לפרמטר sleep שהועבר ל-Task בהתחלה.

    • בדיקה האם ניתנה הודעת cancel. אם כן - מסיימים את ה-Task.

צלילה לתוכנית

בתוך ה-class ממומשות חמש מתודות, שהכבר הוצגו בהקדמה למעלה.

 

מתודה 1: .doInBackground.

זהו קטע הקוד שרץ ב-Thread חדש.

שורה 1: המתודה מקבלת כאן פרמטרים מסוג Integer. אפשר להחליף את Integer בכל סוג אחר. הפרמטרים הם VarArgs כך שניתן להכניס רשימת פרמטרים באורך משתנה. אבל רק מהסוג שנקבע (כאן למשל - Integer).

כפי שנראה בהמשך, ב-onCreate, נמסרו ל-doInBackground שני פרמטרים:

  1. sleep time - הפרמטר שיקבע את זמן ההמתנה ב-loop האינסופי.

  2. taskID- פרמטר שאין כ"כ מה לעשות איתו ונועד להדגים העברת פרמטרים.

 
  1.     protected String doInBackground(Integer... loopParams) {
  2.             boolean running = true;
  3.             taskId = loopParams[1];
  4.             int counter = 0;
  5.             while(running){
  6.                    publishProgress(counter++, taskId);
  7.                 SystemClock.sleep(loopParams[0]);
  8.                 if(isCancelled())
  9.                     running = false;
  10.             }
  11.             return("We are done!");
  12.        

שורה 5-10: המתודה כאן מבצעת loop אין סופי - . היא לעולם לא תגיע לשורה 11.

שורה 3 - שליפת הפרמטר השני שנמסר למתודה, ה-taskId.

בשורה 6 - (publishProgress(counter++,taskId. זוהי הפקודה שמעבירה פרמטרים ל-UI THread. הפרמטרים יתקבלו ע"י המתודה onProgressUpdate שרצה במסגרת ה-UI Thread. שני פרמטרים מסוג Integer מועברים.

שורה 7 - זמן ה-sleep (במילישניות), נקבע ע"י הפרמטר השני.

שורה 8 - בודקת אם ניתנה פקודת cancel. אם היא ניתנה, ה-Task יסתיים.


מתודה 2: onProgressUpdate

  1. protected void onProgressUpdate(Integer... progress) {
  2.             editTextThreadOutput.setText("Counter of Task No." + progress[1] + "= " +progress[0]);
  3.         }

זוהי כאמור המתודה שמטפלת בפרמטרים שנמסרו עם הפקודה publishProgress.

במקרה הנ"ל נמסרו שני פרמטרים מסוג Integer, והם מועברים לתצוגה בחלון ה-EditText.

 

מתודה 3: onPostExecute


  1. @Override
  2.         protected void onPostExecute(String result) {
  3.             Toast.makeText(SyncTaskActivity.this, result, Toast.LENGTH_SHORT)
  4.                 .show();
  5.         }


מתודה זו לא תופעל במימוש שלנו, היות ש-doInBackground אינסופית. במקרה אחר, הערך שמוחזר ע"י doInBackground הוא הפרמטר ש-onPostExecute מקבלת.



מתודה 4: onCancel.

תופעל בעקיבות הפקודה cancel. היא תוציא הודעת TOAST למסך.

 

OnCreate

סיימנו עם ה-AsyncTask. ה-onCreate מספקת מעטפת בלבד.

היא יוצרת את הכפתור להפעלה והפסקה ואת חלון התצוגה בו נראה את ה-counter שנשלח מה-background.

השורות החשובות יותר מהחלק הצהוב הן אלה:

  1.    asyncTaskExample = new AsyncTaskExample();
  2.         asyncTaskExample.execute(sleepTime, taskId);

יצירת האובייקט והרצתו. 2 הפרמטרים של execute בשורה 2, הם אלה שיועברו ל-doOnBackground. הם חייבים להיות מהסוג שהוכרז עליו בכותרת של ה-class, כלומר Integer.

 

הקטע הצהוב השני אליו נתייחב הוא ה-listener callback של הכפתור (שורה 4 בצהוב). הכפתור גורם להפעלה והפסקה של ה-Task לסרוגין. ההפסקה מתבצעת ע"י  cancel , שלמעשה רק נותן את הסימן להפסקה - ראה הסבר למעלה.

במקרה של הפעלה מחדש - יוצרים אובייקט חדש היות שאסור להפעיל פעמיים את  האובייקט.

קישור להורדת קבצי הדוגמא.


4 תגובות:

  1. חבר, עשית עבודה גדולה בבלוג הזה!
    באופן ספציפי, בפוסט הזה חלק מהפונטים נראים אצלי נורא.

    השבמחק
  2. הי אנונימי. תודה על המחמאה. אני רואה שיש בעיה בפונטים בפוסט הזה, לא ברורה לי הסיבה. בינתיים החלפתי את סוג הפונט אבל לא נראה שיש שיפור. אנסה לטפל בזה. רונן.

    השבמחק
  3. פוסט חשוב ומועיל מאוד, תודה!
    רק הערה קטנה, הקוד שעטוף בצבעים חזקים, ממש קשה לקרוא את זה.
    אוי אפשר לסמן את הבלוקים במסגרת צבעונית עדינה כך שהטקסט ישאר נקי?

    השבמחק