Sunday, June 23, 2013

תכנות לאנדרואיד בקלות פרק 12 – מיקבול תהליכים - Threads

בכל פעם שאנו יוצרים אפליקציית אנדרואיד, Thread שנקרא "main" נוצר באופן אוטומטי. Thread זה המכונה גם UI THREAD, הוא חשוב מאוד משום שהוא אחראי על שיגור האירועים ליישומונים המתאימים וזה כולל את אירועי ה draw. זה גם Thread שמנהל אינטראקציה עם פקדים באנדרואיד. לדוגמה, אם אתה נוגע בכפתור על מסך, Thread UI משגר אירוע שבעצם נותן פקודה למערכת ההפעלה לפעול בהתאם.
מודל Thread זה יכול להניב ביצועים גרועים ביישומי אנדרואיד מכיוון שכל הפעולות מתבצעות על אותו ה Thread, כגון עבודות חישוב ארוכות, גישה לשרת, הורדה של קבצים. אלה תהליכים שחייבים לפעול באופן מקביל ולא על ה UI thread , זאת על מנת לשמר מצב שבו האפליקציה מגיבה ולא קורסת. במידה וה UI THREAD תפוס למשך 5 שניות, אנו נקבל תוצאת ANR- Application not responding שיתבטא בקריסה של האפליקציה.
מכאן מגיעה החשיבות הגדולה בעבודה Multi threaded , ותיכף נדגים כיצד זה מתבצע.
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork();
mImageView.setImageBitmap(b);
}
}).start();
}
בואו נראה דוגמא קצרה:
מה הקוד מבצע?
בעת קליק של כפתור, מייצר thread חדש new Runnable אשר מוריד תמונה מן הרשת וצובע imageview עם התוכן שהתקבל, אבל יש פה בעיה נוספת, מהי?
נכון, ב click אנו מפעילים thread חדש ומריצים אותו, אבל אותו worker thread כמו ה thread הראשי, מצב זה יכול להוות בעיה בביצועים, ולעתים קשה מאוד לעשות debugging במצבים כאלה, אז מה עושים?
public void onClick(View v) {
new Thread(new Runnable() {
public void run() {
final Bitmap b = loadImageFromNetwork();
mImageView.post(new Runnable() {
public void run() {
mImageView.setImageBitmap(b);
}
});
}
}).start();
}
יש שינוי קטן בקוד הבא:
ה ImageView מתעדכן בעצם תחת runnable חדש, ולא תחת אותו אחד כמו ה UI THREAD, הקוד הנ"ל עובד, ועושה בדיוק מה שצריך לעשות, ולא מפר שום חוקי thread safety כפי שרצינו, אבל הוא נראה רע מאוד, ובעת יצירה של אופרציות מורכבות וארוכות, הדבר יכול לייצר קוד לא קריא, ומהווה פתח לטעויות.
הפתרון שהביא עימו ה Android SDK הינו Asynctask, למעשה Asynctask הינו מנגנון אשר מנהל עבורנו את כל נושא ה Threading ונותן לנו את המקום להתעסק בלוגיקה ולא לנהל Threading, אלא להשאיר אותו לAsynctask.
public void onClick(View v) {
new DownloadImageTask().execute("http://example.com/image.png");
}
private class DownloadImageTask extends AsyncTask {
protected Bitmap doInBackground(String... urls) {
return loadImageFromNetwork(urls[0]);
}
protected void onPostExecute(Bitmap result) {
mImageView.setImageBitmap(result);
}
}
בואו נראה כיצד הדבר מתבצע עם AsyncTask:
המחלקה DownloadImageTask מרחיבה את AsyncTask וכתוצאה מכך מחוייבת לממש 2 מתודות :
doInBackGround()
onPostExecute()
doInBackground() 
מקבלת מערך strings כאשר האיבר הראשון הוא ה URL של התמונה, לאחר שהתמונה מגיעה.
onPostExecute()
נכנסת לעבודה ומציירת את התמונה על אותו ImageView.