זה הפוסט השלישי בסדרה הדנה בנושא 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.
ארבעת המתודות הן:
doInBackground - מתודה זו היא החלק שרץ ב--Thread החדש. זו המתודה היחידה שחייבים לממש, והיחידה שלא רצה ב-UI Thread.
onProgressUpdate - מתודה זו רצה ב-UI Thread. היא מקבלת נתונים מ-
doInBackgroun בעזרת מנגנון אותו נראה כמובן בהמשך, ויכולה לעבד אותם ב-Thread הראשי.
onPreExecute - מתודה זו מופעלת לפני הקריאה ל-doInBackground, ומאפשרת לבצע כל מיני איתחולים.
onPostExecute - מתודה זו מופעלת אחרי ש-doInBackground סיים את עבודתו. היא מקבלת ממנו את התוצאות ויכולה לעבד אותן.
קיימות עוד מספר מתודות נוספות. נשתמש בשתיים:
cancel - על פי התעוד, היא מנסה לבטל את פעולת ה-Task, אבל הניסוח לא מדויק: היא מפעילה את הסימון isCancelled אבל לא עושה כלום מעבר לכך. הכוונה היא להמנע מהריגת ה-Task מבחוץ. בדוגמא הנ"ל, ה-Task בודק את isCancelled ומפסיק את ריצתו בהתאם.
isCancelled - כמו שתואר לעי"ל, נותן אינדיקציה על פקודת cancel שניתנה.
הפעלת ה-AsyncTask
ההפעלה נעשית ע"י המתודה execute.
הדיאגרמה הבאה מתארת את התהליכים ב-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
הנה התוכנית בשלמותה:
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;
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);editTextThreadOutput = (EditText) findViewById(R.id.edit_text2);
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();
}
} });- asyncTaskExample = new AsyncTaskExample();
- asyncTaskExample.execute(sleepTime, taskId);
- }
- private class AyncTaskExample extends AsyncTask <Integer, Integer, String>{
- protected String doInBackground(Integer... loopParams) {
- boolean running = true;
- taskId = loopParams[1];
- int counter = 0;
- while(running){
- publishProgress(counter++, taskId);
- SystemClock.sleep(loopParams[0]);
- if(isCancelled())
- running = false;
- }
- return("We are done!");
- }
- protected void onProgressUpdate(Integer... progress) {
- editTextThreadOutput.setText("Counter of Task No." + progress[1] + "= " +progress[0]);
- }
- @Override
- protected void onPostExecute(String result) {
- Toast.makeText(SyncTaskActivity.this, result, Toast.LENGTH_SHORT)
- .show();
- }
- @Override
- protected void onCancelled() {
- Toast.makeText(SyncTaskActivity.this, "cancelled", Toast.LENGTH_SHORT)
- .show();
- }
- }
- }
ה-Activity class בנוי משני חלקים עיקריים, כל אחד נצבע בצבע שונה:
בצהוב - המתודה onCreate של ה-class הראשי -Activity class. כאן מופעלים כל התהליכים, כולל איתחול והפעלת האובייקט של AsyncTask
באפור - ה-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 שני פרמטרים:
sleep time - הפרמטר שיקבע את זמן ההמתנה ב-loop האינסופי.
taskID- פרמטר שאין כ"כ מה לעשות איתו ונועד להדגים העברת פרמטרים.
- protected String doInBackground(Integer... loopParams) {
- boolean running = true;
- taskId = loopParams[1];
- int counter = 0;
- while(running){
- publishProgress(counter++, taskId);
- SystemClock.sleep(loopParams[0]);
- if(isCancelled())
- running = false;
- }
- return("We are done!");
שורה 5-10: המתודה כאן מבצעת loop אין סופי - . היא לעולם לא תגיע לשורה 11.
שורה 3 - שליפת הפרמטר השני שנמסר למתודה, ה-taskId.
בשורה 6 - (publishProgress(counter++,taskId. זוהי הפקודה שמעבירה פרמטרים ל-UI THread. הפרמטרים יתקבלו ע"י המתודה onProgressUpdate שרצה במסגרת ה-UI Thread. שני פרמטרים מסוג Integer מועברים.
שורה 7 - זמן ה-sleep (במילישניות), נקבע ע"י הפרמטר השני.
שורה 8 - בודקת אם ניתנה פקודת cancel. אם היא ניתנה, ה-Task יסתיים.
מתודה 2: onProgressUpdate
- protected void onProgressUpdate(Integer... progress) {
- editTextThreadOutput.setText("Counter of Task No." + progress[1] + "= " +progress[0]);
- }
זוהי כאמור המתודה שמטפלת בפרמטרים שנמסרו עם הפקודה publishProgress.
במקרה הנ"ל נמסרו שני פרמטרים מסוג Integer, והם מועברים לתצוגה בחלון ה-EditText.
מתודה 3: onPostExecute
- @Override
- protected void onPostExecute(String result) {
- Toast.makeText(SyncTaskActivity.this, result, Toast.LENGTH_SHORT)
- .show();
- }
מתודה זו לא תופעל במימוש שלנו, היות ש-doInBackground אינסופית. במקרה אחר, הערך שמוחזר ע"י doInBackground הוא הפרמטר ש-onPostExecute מקבלת.
מתודה 4: onCancel.
תופעל בעקיבות הפקודה cancel. היא תוציא הודעת TOAST למסך.
OnCreate
סיימנו עם ה-AsyncTask. ה-onCreate מספקת מעטפת בלבד.
היא יוצרת את הכפתור להפעלה והפסקה ואת חלון התצוגה בו נראה את ה-counter שנשלח מה-background.
השורות החשובות יותר מהחלק הצהוב הן אלה:
- asyncTaskExample = new AsyncTaskExample();
- asyncTaskExample.execute(sleepTime, taskId);
חבר, עשית עבודה גדולה בבלוג הזה!
השבמחקבאופן ספציפי, בפוסט הזה חלק מהפונטים נראים אצלי נורא.
הי אנונימי. תודה על המחמאה. אני רואה שיש בעיה בפונטים בפוסט הזה, לא ברורה לי הסיבה. בינתיים החלפתי את סוג הפונט אבל לא נראה שיש שיפור. אנסה לטפל בזה. רונן.
השבמחקישר כח על ההשקעה
השבמחקפוסט חשוב ומועיל מאוד, תודה!
השבמחקרק הערה קטנה, הקוד שעטוף בצבעים חזקים, ממש קשה לקרוא את זה.
אוי אפשר לסמן את הבלוקים במסגרת צבעונית עדינה כך שהטקסט ישאר נקי?