Thursday, June 14, 2012

[Note]Android Threads, Handlers and AsyncTask


先看過Processes and Threads會有比較清楚的概念
當Adnroid Application 啟動後, 系統會建一個主要的thread 稱 "main thread" or "UI thread", 所有的components 皆跑在這個UI thread, system calls 也是透過UI thread dispatched給各個component, ex: onKeyDown, touch event.
UI thread 如因大量運算或等待而blocked, 預設超過5秒ANR(Application Not Responding)就會發生.
且Android UI components 並非thread-safe, 使用上要特別小心.
所以:
1. long time computation使用另外的thread, 不要寫在 UI Thread.
2. 不要在UI thread 之外使用UI component method.
透過Thread, Handler and AsyncTask perform asynchronous processing, 避免UI thread block.

Android 提供以下的method, 可在其它的thread 下調用 UI thread.
Activity.runOnUiThread(Runnable)
View.post(Runnable) <-- used in example code.
View.postDelayed(Runnable, long)
或是使用Handler or AsyncTasks class 達到同樣的效果.

* Handler
有2個主要用途:
(1) message scheduling,  post action at specific point.
(2) 將其它thread發出來的action 放入message queue中, 避免race condition.
 
處理message 需要override handleMessage(), 透過sendMessage() or sendEmptyMessage() method.
執行Runnanble則是使用post() method.
一個Activity 只需有一個Handler instance.

* AsyncTask
使用AsyncTask必需繼承它, 且override doInBackground() method.

4 steps:
呼叫execute() 開始執行, 之後onPerExecute()接著自動被呼叫, 通常用來 initial status of task.
接著doInBackground() 被調用, 一般為long time computation時使用.
call publishProgress()後會調用onProgressUpdate(), onProgressUpdate()調用於UI thread, 一般用來update progress bar, UI animate.
publishProgress() trigger時間點是無法預期的.
doInBackground() 結束後會trigger onPostExecute()調用於UI thread, 用來返回結果.
 
* Thread, Handler 在Avtivity 結束後 thread就結束, 但AsyncTack則否.
Ans: 
So, even if AsyncTask finished, thread does not die. But thread in thread pool can be killed.
 
Examples
Layout XML file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/run" />

    <ProgressBar
        android:id="@+id/progressBar1"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="60dp" />

</LinearLayout>
 
Thread example code:
package com.samchen.samdemos.threads;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;

public class DemoThreads extends Activity {
    final boolean isThread = true;
    final String TAG = "DemoThreads";
    private ProgressBar mProgress;
    private int mProgressStatus = 0;
    boolean isRunning = false;
   
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.demothreads);

        Button btnRun = (Button) findViewById(R.id.button1);
        mProgress = (ProgressBar) findViewById(R.id.progressBar1);

        btnRun.setOnClickListener(new Button.OnClickListener() {

            @Override
            public void onClick(View arg0) {
                if(isRunning){
                    Log.d(TAG,"thread is runing");
                    Toast.makeText(getApplicationContext(),
                            getString(R.string.running),
                            Toast.LENGTH_SHORT).show();
                    return;
                }else
                    isRunning = true;
               
                if (isThread) {
                    // thread example
                    new Thread(new Runnable() {
                        public void run() {
                            while (mProgressStatus < 100) {
                                // update the bar status
                                mProgress.post(new Runnable() {
                                    public void run() {
                                        mProgress.setProgress(mProgressStatus);
                                        Log.d(TAG, "update mProgressStatus : "
                                                + mProgressStatus);
                                    }
                                });

                                // do something long
                                SystemClock.sleep(500);
                                mProgressStatus++;
                            }
                        }

                    }).start();

                } else {
                    // non-thread example
                    while (mProgressStatus < 100) {
                        // update the bar status
                        mProgress.post(new Runnable() {
                            public void run() {
                                mProgress.setProgress(mProgressStatus);
                            }
                        });

                        // do something long
                        //SystemClock.sleep(500);
                        //if use SystemClock.sleep, system will be crash.
                        try{
                            Thread.sleep(500);
                        }catch (InterruptedException e){
                            Log.d(TAG,"InterruptedException!!!!!!!");
                            e.printStackTrace();
                        }
                        mProgressStatus++;
                       
                    }
                }

            }

        });
    }

}

Handler example:
package com.samchen.samdemos.threads;

import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;

public class DemoHandler extends Activity {

    final String TAG = "DemoHandler";
    private ProgressBar mProgress;
    private int mProgressStatus = 0;
    private Handler mHandler;
    boolean isRunning = false;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.demothreads);

        Button btnRun = (Button) findViewById(R.id.button1);
        mProgress = (ProgressBar) findViewById(R.id.progressBar1);
        mHandler = new Handler();

        btnRun.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                if (!isRunning) {
                    startProgress(mProgress);
                    isRunning = true;
                } else{
                    Log.d(TAG, "thread is runing");
                    Toast.makeText(getApplicationContext(),
                            getString(R.string.running),
                            Toast.LENGTH_SHORT).show();
                }
            }
        });

    }

    public void startProgress(View view) {
        // Do something long
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (mProgressStatus = 0; mProgressStatus <= 100; mProgressStatus++) {
                    //final int value = i;
                    SystemClock.sleep(500);
                   
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            Log.d(TAG, "update mProgressStatus : "
                                    + mProgressStatus);
                            mProgress.setProgress(mProgressStatus);
                        }
                    });
                }
            }
        };
        new Thread(runnable).start();
    }
}

AsyncTask example:
package com.samchen.samdemos.threads;

import android.app.Activity;
import android.opengl.GLSurfaceView;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;

public class DemoAsyncTask extends Activity {

    final String TAG = "DemoAsyncTask";
    private ProgressBar mProgress;
    private int mProgressStatus = 0;
    boolean isRunning = false;
    private updateTask mTask;

    /** Called when the activity is first created. */

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

        mProgress = (ProgressBar) findViewById(R.id.progressBar1);

        Button btnRun = (Button) findViewById(R.id.button1);
        btnRun.setOnClickListener(new Button.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                if (!isRunning) {
                    mTask = new updateTask();
                    mTask.execute();
                    isRunning = true;
                } else {
                    Log.d(TAG, "thread is runing");
                    Toast.makeText(getApplicationContext(),
                            getString(R.string.running), Toast.LENGTH_SHORT)
                            .show();
                }
            }
        });

    }

    private class updateTask extends AsyncTask<Void, Integer, Void> {

        protected void onPostExecute(Void result) {
           
        }

        protected void onProgressUpdate(Integer... value) {
            Log.d(TAG, "onProgressUpdate : " + value);
            mProgress.setProgress(value[0]);
        }

        protected void onPreExecute() {
            // TODO Auto-generated method stub
            mProgressStatus = 0;
        }

        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            while (mProgressStatus < 100) {
                Log.d(TAG, "doInBackground mProgressStatus : "
                        + mProgressStatus);
                // do something long
                publishProgress(mProgressStatus);
                SystemClock.sleep(500);
                mProgressStatus++;
            }
            return null;
        }
    }

}