יום שבת, 20 בדצמבר 2014

Get off my main thread - חלק 3

לאחר שהתחלנו לפני שני מאמרים לסקור את AsyncTask ובמאמר הקודם לסקור את ThreadPoolExecutor, נחבר בין שניהם ונמשיך לצלול לקוד של AsyncTask ולהבין איך ThreadPoolExecutor שייך לשם.


הפעם נחבר קוד טיפה שונה ל-AsyncTask שלנו. בפעם הקודמת שהשתמשנו בו, הרצנו AsyncTask אחד, ב-Thread חיצוני אחד, שביצע 100 פעולות ברקע. כעת נבקש להריץ מספר AsyncTask במקביל. נתחיל בקוד הבא:

public void onStartProgressButtonClicked(View view) {
    AtomicInteger numOfRunTimes = new AtomicInteger(0);
    for (int i = 1; i <= 100; i++) {
        DummyWorkAsyncTask dummyWorkAsyncTask = new DummyWorkAsyncTask();
        dummyWorkAsyncTask.execute(numOfRunTimes);
    }
}  
 
private class DummyWorkAsyncTask extends AsyncTask<AtomicInteger, Integer, Void> {

    @Override
    protected Void doInBackground(AtomicInteger... params) {
        doDummyWork();
        AtomicInteger numOfRunTimes = params[0];
        int myProgress = numOfRunTimes.incrementAndGet();
        publishProgress(myProgress);
        return null;
    }
    
    @Override
    protected void onProgressUpdate(Integer... values) {
        setProgressPercent(values[0]);
    }
}
רק שהפעם נקבל משהו מוזר, אם נריץ את הקוד הנ״ל ב-Gingerbread נקבל קריאות במקביל למספר AsyncTask והעבודה שלנו תתבצע מהר כפי שהייתה לו היינו משתמשים ב-ThreadPoolExecutor, אך אם נריץ את הקוד ב-KitKat למשל (או כל Honeycomb ומעלה) נגלה שהקוד רץ פחות או יותר באותה המהירות של הקוד מלפני שני מאמרים, זה שרץ ב-Thread אחד.

אז מה קורה פה? נפנה לקוד המקור לבדוק.
הקטע הבא לקוח מתוך של AsyncTask ב-KitKat.

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;

/**
 * An {@link Executor} that can be used to execute tasks in parallel.
 */
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

/**
 * An {@link Executor} that executes tasks one at a time in serial
 * order.  This serialization is global to a particular process.
 */
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

public final AsyncTask<Params, Progress, Result> execute(Params... params) {
    return executeOnExecutor(sDefaultExecutor, params);
}
ב-AsyncTask יש לנו שני משתנים סטטים, האחד TREAD_POOL_EXECUTOR שנבנה עם כמות Threads מותאמת למספר הליבות במכשיר ויכול להריץ משימות במקביל, והשני SERIAL_EXECUTOR המריץ פעולות בטור. ניתן גם לראות שהברירת מחדל כאשר אנו קוראים ל-execute היא להריץ בטור. הברירת מחדל היא ההבדל לשוני בין הפצות אנדרואיד שונות, בשלב מסויים רצו בגוגל להמנע מבעיות שיכולות לנבוע מהרצת קוד במקביל והפכו את ברירת המחדל של AsyncTask מהרצה במקביל להרצה בטור.

דבר נוסף שאני לוקח ממבט בקוד המקור הוא לא לקרוא יותר ל-execute, אלא רק ל-exeuteOnExecutor, כך הקוד יהיה ברור יותר ותינתן לי שליטה מלאה על הקוד שלי. 
אם ארצה להריץ בטור אקרא ל-

dummyWorkAsyncTask.executeOnExecutor( 
                 AsyncTask.SERIAL_EXECUTOR, numOfRunTimes);

ואם ארצה במקביל אוכל לקרוא ל-

dummyWorkAsyncTask.executeOnExecutor(
                AsyncTask.THREAD_POOL_EXECUTOR, numOfRunTimes);

או במקרה שלנו, כפי שגילינו במאמר הקודם עדיף יהיה לקרוא ל-

ExecutorService ex = Executors.newCachedThreadPool();
for (int i = 1; i <= 100; i++) {
    DummyWorkAsyncTask dummyWorkAsyncTask = new DummyWorkAsyncTask();
    dummyWorkAsyncTask.executeOnExecutor(ex, numOfRunTimes);
}

עד עכשיו דיברנו על Thread חיצוני, AsyncTask ו-ThreadPoolExecutor, והזכרנו שכולם סובלים מאותה נקודת התורפה שעשויה להתרחש במקרה הבא - המשתמש יסובב את המסך, ה-Activity יווצר מחדש, Threads חיצוניים חדשים יוקמו בעוד הקודמים ממשיכים ומנסים לקרוא ל-
setProgressPercent של ה-Activity הלא נכון והאפליקציה תהיה במצב בלתי צפוי.
 
ישנן מספר דרכים להתגבר על הבעיה, אפרט את חלקם:
 
1) שליטה על סיבובי מסך על ידי הוספת configChanges=orientation ל-manifest, בשיטה זה המערכת תתן למפתח את כל השליטה על הקורה בסיבוב המסך, ולא יופעל ה-LifeCycle הטבעי של -Activity, ההורס את ה-Activity הישן, משנה את ה-Configuration ויוצר Activity חדש. 
למי זה טוב? אם ה-Activity שלכם לא משתמשת ב-Resources שונים בשינוי אוריינטציה, אם לא אכפת לנו מקוד שעשוי להכשל בעתיד, כאשר נוסיף קוד ל-Activity או אם אנחנו רוצים להיות מפוטרים. אצטט את הדוקמנטציה של אנדרואיד - ״הטכניקה הזו צריכה להיות בשימוש רק כמוצא אחרון ואינה מתאימה למרבית האפליקציות״ 

2) שימוש ב-onSaveInstanceState ו-onRestoreInstacneState על מנת לשמור את המצב האחרון בו ה-Activity היה לפני שינוי האוריינטציה ולעלות אותו מחדש לאחר השינוי.
למי זה טוב? במקרים בהם נרצה לשמור מידע על המצב הנוכחי של ה-Activity, למשל - איזה Views חבואים ואיזה נראים, באיזה מיקום ב-ListView או RecyclerView נמצא המשתמש, על מנת להחזיר אותו לאותו מיקום (זה למעשה קורה אוטומטי על ידי הטכניקה הזו), ובמקרים רבים אחרים בו נרצה מידע גולמי על ה-Activity או ה-Views בו, פתרון זה יהיה מספיק טוב ופשוט. במידה ונרצה לשמור אובייקטים שלמים, כמו תמונות (על מנת להמנע מעלייה איטית שלהם מחדש) או כמו במקרה שלנו, בו נרצה לשמור את ה-AsyncTask, זה לא מספיק. 

3) שמירה של האובייקטים הרצויים כמשתנים סטטים או סינגלטון - כאן יש פתח לבעיות רבות הכוללות ניהול של האובייקטים הרצויים, שחרור שלהם שלא צריך אותם ודאגה שיוכלו לשנות Views ב-Activity הנכון.    

4) שימוש ב-Loaders, יורחב במאמר הבא.

5) אחד הפתרונות הטובים ביותר למצב שלנו ולמצבים רבים אחרים יהיה שימוש ב-Fragments -
בסיבוב מסך, כאשר ה-Activity נהרס ומאותחל, fragments אשר יסומנו עם (setRetainInstance(true לא יהרסו וישמרו על האובייקטים שלהם, אתן פה את הדוגמא מ-Handling Runtime Changes של http://developer.android.com.

נבנה את הפרגמנט הבא:
public class RetainedFragment extends Fragment {

    // data object we want to retain
    private MyDataObject data;

    // this method is only called once for this fragment
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // retain this fragment
        setRetainInstance(true);
    }

    public void setData(MyDataObject data) {
        this.data = data;
    }

    public MyDataObject getData() {
        return data;
    }
}
ונשתמש בו כך:
public class MyActivity extends Activity {

    private RetainedFragment dataFragment;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // find the retained fragment on activity restarts
        FragmentManager fm = getFragmentManager();
        dataFragment = (DataFragment) fm.findFragmentByTag(data);

        // create the fragment and data the first time
        if (dataFragment == null) {
            // add the fragment
            dataFragment = new DataFragment();
            fm.beginTransaction().add(dataFragment, data).commit();
            // load the data from the web
            dataFragment.setData(loadMyData());
        }

        // the data is available in dataFragment.getData()
        ...
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // store the data in the fragment
        dataFragment.setData(collectMyLoadedData());
    }
}


הערה:
איך פרגמנטים שומרים על עצמם בסיבובי מסך? ה-ActivityThread שומר מצביע לאובייקטים שישמרו לפני הריסת ה-Activity והבנייה שלה מחדש, בעבר יכלנו להוסיף אובייקטים לתהליך הזה על ידי getLastNonConfigurationInstance, מרגע הוספת הפרגמנטים פונקצייה זו ב-Deprecated, מלבד פרגמנטים גם LoaderManager יודע לשמור את ה-Loaders שלו בסיבובי מסך ועליו נרחיב במאמר הבא.

לקריאה נוספת:

יום שבת, 13 בדצמבר 2014

Get off my main thread - חלק 2

במאמר הקודם התחלנו לסקור שיטות לביצוע פעולות ברגע, דיברנו על Thread חיצוני ועל AsyncTask, המשותף לשניהם הוא הרצה ב-Worker Thread חיצוני אחד. במאמר הזה ובמאמר הבא נדבר על השיטות להריץ עבודה ברקע במספר Threads במקביל.

ThreadPoolExecutor (פתרון של ג׳אווה - נמצא תחת java.util.concurrent): הזכרתי כבר שג׳אווה מעודדת מחזור Threads, כחלק מהעידוד גם מסופק לנו מחוץ לקופסא מחלקה שעושה את מרבית העבודה עבורנו. אנסה לעשות סדר לגבי איך עובד ThreadPoolExecutor בעזרת ציורים משובבי נפש שהכנתי - פחות או יותר ננסה לדמיין את המחלקה כך:


ל-ThreadPoolExecutor שלושה רכיבים עיקריים, שני מאגרי Threads ותור של משימות. משימות נכנסות בצורת Runnable על ידי execute או submit, ועוברות לטיפול ב-Core Poll. במידה וה-Core Poll לא מלא הוא יצור Thread חדש לטובת כל משימה שתגיע, כברירת מחדל, ה-Threads אשר נוצרים ב-Core Poll ישארו שם ולא ישוחררו עד לקריאת shutdown.


באיור העיגולים יהיו המשימות הנכנסות, הריבועים הנוצרים יהיו ה-Threads, והמצב ב-ThreadPoolExecutor שלנו הוא כזה שכבר נכנסו מספר משימות (בדיוק 10), ולכן 10 Threads נוצרו. מבין עשרת ה-Threads שניים כבר סיימו את עבודתם. הכלל של Core Poll עדיין תקף - לכל משימה שתיכנס יווצר Thread חדש עבורה, על אף שחלק מה-Threads סיימו את עבודתם. כאשר מספר ה-Threads ב-Core Poll יגיע למקסימום, משימות חדשות יכנסו ל-Threads פנויים, אלא אם אין כאלה.

אם כל ה-Threads ב-Core Poll נוצרו וכולם עסוקים, המשימות החדשות ימתינו בתור עד שיתפנה Thread ב-Core Poll שיוכל לטפל בהם. במצב כזה יכולה להווצר לנו בעייה - אם המשימות מה-Core Poll יצרו משימות חדשות שיעזרו להם לסיים את העבודה (למשל בחישובים מסובכים) והמשימות החדשות יגיעו לתור להמתנה, הן לא יתחילו וכך המשימות הישנות לעולם לא יסיימו - מצב של deadlock.



כאשר כל התכולה של התור מלאה גם היא, משימות יתחילו להשלח אל ה-Backup Poll, ההבדל בינו לבין ה-Core Poll הוא ש-Threads אשר סיימו את עבודתם ב-Backup Poll ימתינו x זמן ואז ישוחררו - ולכן השארתי שם פתח ניקוז קטן. הזמן אשר יעבור בין סיום המשימה להריסת ה-Thread נתון אף הוא בידי המפתח.


במצב בו כל ה-Threads מלאים ועסוקים, התור מלא אף הוא ומשימה חדשה נכנסת, יוקפץ Rejected Execution Exception.

מבחינה תיאורטית ThreadPoolExecutor יעבוד כפי שמתואר כאן, בפועל המצב בו גדלי התור ושני ה-Polls אינם 0 ואינם אינסופיים נדיר, שני שימושים נפוצים מאוד, אשר בשניהם לא נמצא RejectedExecutionException הם:

1) FixedThreadPool -
  Core Poll size = n 
  Queue Capacity = infinite
  Backup Poll size = 0


ב-FixedThreadPool מספר ה-Threads הפעיל קבוע והתור הוא אינסופי, כך שיש לנו מספר סופי של פועלים המנסים לסיים את המשימות.
2) CachedThreadPool -
  Core Poll size = 0
  Queue Capacity = 0
  Backup Poll size = infinite



ב-CachedThreadPool יכולות הקיבול של התור ושל ה-Core Poll הם אפס וה-Backup Poll size הוא אינסופי, כך שכל משימה שתיכנס תקבל מיידית Thread שיעבוד עליה, ה-Threads אשר סיימו את עבודתם ולא קיבלו עבודה חדשה למשך דקה, ישוחררו, בשיטה זו לא יכול להווצר deadlock.
בשביל כל סגנון אחר של ThreadPoolExecutor המפתח יגדיר בעצמו את הנתונים.

הערה:
ב-Android Lollipop התווסף סוג נוסף של ThreadPoolExecutor - כזה המתאים בעיקר לפעולות חישוביות רקורסיביות - ForkJoinPool. סוג זה יודע לפצל כל משימה למשימות נוספות אשר יעזרו לה ולהמנע מ-deadlock, הוא בשימוש מגא׳ווה 1.8. סוג זה עדיין לא בשימוש ב-Android, על אף שהקוד נמצא, הוא נסתר תחת hide@
 
/** 
* @param parallelism the targeted parallelism level
* @return the newly created thread pool
* @since 1.8
* @hide
*/
public static ExecutorService newWorkStealingPool(int parallelism) {
    return new ForkJoinPool(parallelism, 
        ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
}
 
מתי נשתמש בכל אחד מהשיטות?
בכללי נבחן כל מקרה לגופו, השימוש הטוב ביותר תלוי בחומרה שלנו ובמשימה שלנו, הדבר הנכון ביותר יהיה למדוד זכרון וזמני ריצה וכך לבדוק מה השיטה הנכונה ביותר. על פי הדוקמנטציה של גוגל - ב-CachedThreadPool נשתמש כאשר נרצה לבצע הרבה פעולות קצרות ובמקביל.
לקוח מתוך Abstractivate: Choosing an ExecutorService

כיצד נשתמש בפועל ב-ThreadPoolExecutor?
הבנאי של ThreadPoolExecutor נראה כך:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                                        TimeUnit unit, BlockingQueue<Runnable> workQueue,
                                        ThreadFactory threadFactory, RejectedExecutionHandler handler)
 
נגדיר בו את ה-corePoolSize, את ה-Backup Poll size שיהיה 
(maximumPoolSize - corePoolSize), נגדיר את הזמן בו Threads ב-Backup Poll יחיו ללא משימה, workQueue הוא התור שלנו, ThreadFactory אחראי לאתחולים של ה-Threads (שמות, Priority וכו׳) ו-handler שיטפל במצב של RefectedExecutionException. לשניים האחרונים יש אופציות ברירת מחדל אם לא נבחרו.
ברוב המקרים לא נקרא לבנאי הנ״ל בעצמנו אלא נשתמש במחלקת Executors שמספקת לנו בין היתר את הפונקציות הבאות:


public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
 
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
 
כפי שציינתי קודם, את כל הקריאות שנבצע במחלקה הזאת נסיים בקריאה ל-shutDown, פעולה זו תאסור על משימות חדשות להכנס לתור, תאפשר למשימות הקיימות להסתיים ותשחרר את כל ה-Threads בסיום.
נמשיך עם הבעיה שהצגנו במאמר הקודם והפעם נפתור אותה עם ThreadExecutorService
 
public void onStartProgressButtonClicked(View view) {
    final AtomicInteger progress = new AtomicInteger(0);
    final ThreadPoolExecutor ex = (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
    final Handler uiHandler = new Handler(Looper.getMainLooper()) {

        @Override
        public void dispatchMessage(Message msg) {
            setProgressPercent(msg.what);
        }
    };
    for (int i = 1; i <= 100; i++) {
        ex.execute(new Runnable() {

            @Override
            public void run() {
                doDummyWork();
                int myProgress = progress.incrementAndGet();
                uiHandler.obtainMessage(myProgress).sendToTarget();
            }
        });
    }
    ex.shutdown();
}
במקרה הזה בחרתי לפתור את הבעיה עם FixedThreadPool של 5 Threads, למה? סתם. מכיוון שבחרתי ללא סיבה ובנוסף אני מבצע הרבה פעולות קטנות אשר על פי הדוקמנטציה עדיף להשתמש ב-CachedThreadPool, אבדוק את עצמי. הרצתי את הקוד הנ״ל 100 פעמים כאשר בכל פעם בחרתי מספר Threads שונה (בין 1 ל 100) ומדדתי זמנים. 
התוצאות:
על פי התוצאות,  היה עדיף להתשמש ב-CachedThreadPool.

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

במאמר הבא נמשיך לעסוק ב-AsyncTask שהתחלנו במאמר הקודם והפעם בהפעלת מספר Threads במקביל באמצעות AsyncTask, ונתחיל לעסוק בפתרון הבעיות הטמונות בשיטות השונות.

לקריאה נוספת:

יום שבת, 6 בדצמבר 2014

Get off my main thread - חלק 1

בסדרת המאמרים הקודמים דיברנו על ה-Main Thread ועל החשיבות בלהשאיר אותו נקי ככל הניתן. אזכיר כאן את שני כללי הברזל המוכרים אשר צריכים ללוות כל מפתח אנדרואיד לגבי ה-Main Thread:
  1. אל תחסום אותו
  2. אל תנסה לגשת לקומפוננטות של ה-UI toolkit מחוץ ל-Main Thread
בסדרת המאמרים הבאה נבחן את הדרכים השונות אותם אנדרואיד מספק לנו על מנת לבצע תהליכים ארוכים ב-Worker Threads.

לשם ההדגמה נעבוד עם אפליקציה חוצת גבולות ועשירת תרבויות אשר לה ProgressBar, TextView ו-Button. 
האפליקציה שלנו תידרש לבצע 100 פעמים עבודה סתמית כלשהי ב-Worker Thread ולהציג למשתמש את ההתקדמות.
ה-Activity הראשי של האפליקציה -
public class BackgroundActivity extends Activity {
    private ProgressBar mProgress;
    private TextView mText;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.background_activity);
        mProgress = (ProgressBar) findViewById(R.id.progressBar);
        mText = (TextView) findViewById(R.id.textView);
    }

    public void onStartProgressButtonClicked(View view) { 
        // do 100 dummy works
    }

    private void doDummyWork() {
        Thread.sleep(100);
    }
 
    protected void setProgressPercent(int myProgress) {
        mText.setText(myProgress + "% percentage complete");
        mProgress.setProgress(myProgress);
    } 
}
 
ה-xml של ה-Activity -

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

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:indeterminate="false"
        android:max="100"
        android:padding="20dp" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="onStartProgressButtonClicked"
        android:text="Start Progress"
        tools:ignore="HardcodedText" />

</LinearLayout>

אנו נבדוק דרכים שונות למלא את public void onStartProgressButtonClicked

שימוש ב-Thread חיצוני פשוט (פתרון של ג׳אווה - נמצא תחת java.lang):
 בתור התחלה נפנה לפתרון הבסיסי ביותר, ניצור Thread חדש ונשאיר לו את העבודה -

public void onStartProgressButtonClicked(View view) {
    Thread backgroundThread = new Thread(new Runnable() {

        @Override
        public void run() {
            for (int i = 1; i <= 100; i++) {
                doDummyWork(); 
                setProgressPercent(i); 
            }
        }
    });
    backgroundThread.start();
}
בפתרון הזה יצרנו Thread חדש, הגדרנו לו את העבודה - להריץ 100 פעמים doDummyWork ולדווח על ההתקדמות, אמנם בהרצה שלו מיד נקרוס ונקבל - CalledFromWrongThreadException כי עברנו על הכלל השני וניסינו לשנות את ה-TextView וה-ProgressBar שלנו מ-Thread אחר מה-UI Thread.
גם אם לא אחדש לרוב מפתחי האנדרואיד מהם השיטות הנפוצות להריץ את הקוד אשר קשור ל-UI ב-UI Thread, אנסה לחדש על העומד מאחוריהם.
א. שימוש ב-Handler:

@Override
public void run() {
    Handler uiHandler = new Handler(Looper.getMainLooper()); 
    for (int i = 1; i <= 100; i++) {
        final int myProgress = i;
        doDummyWork(); 
        uiHandler.post(new Runnable() {
 
            @Override
            public void run() { 
                setProgressPercent(myProgress);              
            }
        });
    }
} 
זו אולי הדרך הנפוצה ביותר בה נתקלתי בשליחת משימות ל-UI Thread, אמנם ניתן לשפץ אותה עוד טיפה. בצורה הנוכחית אנו יוצרים 100 אובייקטים של Runnable, לעומת זאת אנו יודעים ש-Message ממחזר את עצמו ולכן הצורה היעילה יותר תראה כך:

@Override
public void run() {
    Handler uiHandler = new Handler(Looper.getMainLooper()) {

        @Override
        public void dispatchMessage(Message msg) {
            int myProgress = msg.what; 
            setProgressPercent(myProgress);
        };
    };
 
    for (int i = 1; i <= 100; i++) {
        doDummyWork();
        uiHandler.obtainMessage(i).sendToTarget();
    }
}
במידה ונתקל בסיטואציה בה נרצה להמנע מליצור Handler נוכל להשתמש ב-Handler שנמצא בכל View ומקושר ל-Main Thread, במקרה שלנו -
mText.post(new Runnable() {...});
או לקבל את ה-Handler ישירות - 
mText.getHandler();
ב. שימוש ב-Activity.runOnUiThread:

@Override
public void run() {
    for (int i = 1; i <= 100; i++) {
        final int myProgress = i;     
        doDummyWork();
        runOnUiThread(new Runnable() {

            @Override
            public void run() {
                setProgressPercent(myProgress);
            }
        });
    }
}
בכדי להשתמש בפתרון הזה נצטרך רפרנס ל-Activity, שהרי runOnUiThread היא פונקצייה של Activity, דבר אשר אינו מובטח לנו ב-custom objects. 
ההבדל בין שתי השיטות טמון ביישום של runOnUiThread, נסתכל על הקוד המקור -

public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}
runOnUiThread יבדוק האם ה-Thread הנוכחי הוא ה-UI Thread, במידה ולא יוציא את הפקודה ל-uiHandler וזו תכנס לסוף התור של ה-MessageQueue ב-Main Looper, במידה וכן - הפקודה תרוץ באופן מיידי.

2. שימוש ב-AsyncTask (פתרון של אנדרואיד - נמצא תחת android.os):
על מנת לחסוך את הטיפול ב-Thread חיצוני ובסנכרון שלו חזרה ל-UI Thread, אנרואיד מספק לנו את  AsyncTask, מחלקה מעט שנויה במחלוקת, ישנם מפתחים שימצאו אותה שימושית מאוד ואחרים שיזהירו מפני שימוש בה. דעתי - כל עוד המפתח מבין ויודע מה יקרה בשימוש שלו במחלקה, היא יכולה להיות יעילה. המחלקה מציעה מגוון אפשרויות שימוש בה - בטור או במקביל, בהווה או בעתיד ועוד. נתחיל לחקור את השימוש הכי בסיסי והכי נפוץ של המחלקה במאמר הזה ונסיים לחקור מעבר בעוד שני מאמרים, לאחר שנחקור את ThreadPoolExecutor.

חקירה בסיסית של AsyncTask -
השימוש הנפוץ ביותר והמוכר ביותר של המחלקה מתבסס על חלקי הקוד הבאים מתוך קוד המקור שלה -
public abstract class AsyncTask<Params, Progress, Result> {

    /**
     * Runs on the UI thread before doInBackground.
     */
    protected void onPreExecute() {}

    /**
     * Runs on a background thread
     */
    protected abstract Result doInBackground(Params... params);

    /**
     * Runs on the UI thread after doInBackground.
     * The specified result is the value returned by doInBackground
     * This method won't be invoked if the task was cancelled. 
     */
    protected void onPostExecute(Result result) {}

    /**
     * Runs on the UI thread after publishProgress is invoked.
     */
    protected void onProgressUpdate(Progress... values) {}

    // A lot of code here
}
AsyncTask היא מחלקה מסוג Abstract, אין לה מימוש קיים ואם נרצה להשתמש בה נהיה חייבים לרשת ממנה, 4 רכיבים עיקריים מרכיבים אותה - onPreExecute - יופעל ב-UI Thread לפני המשימה שנבקש לבצע ב-Thread חיצוני, doInBackground - יבוצע ב-Thread חיצוני, onPostExecute - יבוצע ב-UI Thread לאחר שהעבודה של doInBackground הסתיימה, הוא יקבל את האובייקט ש-doInBackground יחזיר ו-onProgressUpdate, אליו נקרא ידנית על ידי publishProgress, גם הוא יופעל ב-UI Thread.

אפשר לתאר את התהליך כך:
original posted at http://programmerguru.com/android-tutorial/what-is-asynctask-in-android/
את כל התהליך תתחיל קריאה ל-execute, הזזתי, פישטתי ומחקתי הרבה קוד, אפשר להשיג את השימוש הבסיסי ביותר על ידי הקטע קוד הבא:

private final Handler sHandler = new Handler() {
    public void dispatchMessage(Message msg) {
        Object resultOrProgress = (Object) msg.obj;
        switch (msg.what) {
            case MESSAGE_POST_RESULT:
                onPostExecute((Result) resultOrProgress);
                break;
            case MESSAGE_POST_PROGRESS:
                onProgressUpdate((Progress[]) resultOrProgress);
                break;
        }
    };
};
   
public final void execute(final Params... params) {
    onPreExecute();
    new Thread(new Runnable() {

        @Override
        public void run() {
            Result result = doInBackground(params);
            sHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget();
        }
    });
}   
   
protected final void publishProgress(Progress... values) {
    sHandler.obtainMessage(MESSAGE_POST_PROGRESS, values).sendToTarget();
}

בשביל הפעילות הבסיסית שרוב הפעמים ישתמשו ב-AsyncTask, הקוד לעיל יספיק. שלושה דברים אפשר לראות לפי הקוד -
א. execute חייב לרוץ ב-UI Thread, אחרת onPreExecute ירוץ ב-Thread הלא נכון.
ב. new AsyncTask חייב לרוץ ב-UI Thread, ה-Handler שלו מאותחל ללא Looper מוגדר.
ג. שלושת הפרמטרים הגנרים של המחלקה - Params, Progress and Result מקבלים משמעות פה, Params ישלח ל-doInBackground, בתצורת מערך, Progress ישלח ל-onProgressUpdate ו-Result ישלח ל-onPostExecute
בשביל להריץ את הקוד שלנו ב-AsyncTask נכתוב את הקוד הבא:

public void onStartProgressButtonClicked(View view) {
    Integer numOfRunTimes = 100;
    DummyWorkAsyncTask dummyWorkAsyncTask = new DummyWorkAsyncTask();
    dummyWorkAsyncTask.execute(numOfRunTimes);
}       
   
private class DummyWorkAsyncTask extends AsyncTask<Integer, Integer, Boolean> {

    @Override
    protected Boolean doInBackground(Integer... params) {
        int numOfRunTimes = params[0];
        boolean result = true;
        for (int i = 1; i <= numOfRunTimes; i++) {
            doDummyWork();
            publishProgress(i);
        }
        return result;
    }
       
    @Override
    protected void onProgressUpdate(Integer... values) {
        setProgressPercent(values[0]);
    }
       
    protected void onPostExecute(Boolean result) {
        if (result == true)
            Toast.makeText(getApplicationContext(), "Job done", 
               Toast.LENGTH_LONG).show();
    }
}

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

במאמר הבא נדון לעומק על ThreadPoolExecutor - ביצוע פעולות חיצוניות במספר Threads במקביל.