第十一章-Android線程和線程池(AsyncTask、HandlerThread、IntentService)

Android中的線程形態

本節將對Android中的線程形態做一個全面的介紹,除了傳統的Thread以外,還包含了AsyncTask,HandlerThread以及IntentService,這三者的底層實現也是線程,但是他們具有特殊的表現形式,同時在使用上也各有優缺點,下面從源碼的角度來分析AsyncTask的執行過程。

一、AsyncTask

AsyncTask是一種輕量級的異步任務類,它可以在線程池中執行後臺任務,然後把執行的進度和最終結果傳遞給主線程並在主線程中更新UI。從實現上來說,AsyncTask封裝了Thread和Handler,通過AsyncTask我們更加方便的執行後臺任務以及在主線程中訪問UI,但是AsyncTask並不適合進行特別耗時的後臺任務,對於特別耗時的任務來說,建議使用線程池。

AsyncTask是一個抽象的泛型類,他提供了Params,Progress和Result這三個泛型參數,其中Params表示參數的類型,Progress表示後臺任務的執行進度,Result表示後臺任務的返回結果的類型,如果AsyncTask確實不需要傳遞具體的參數,那麼這三個泛型可以用Void來代替。AsyncTask這個類的聲明如下所示。

public abstract class AsyncTask<Params, Progress, Result> 

AsyncTask提供了4個核心方法,它們的含義如下所示。

  • onPreExecute()

在主線程執行,在異步任務執行之前被調用,一般可以用於做一些準備工作。

  • doInBackground(Params… params)

在線程池中執行,此方法用於執行異步任務,params參數表示異步任務的輸入參數。在此方法中可以通過publishProgress方法來更新任務的進度,publishProgress會調用onProgressUpdate方法。此外此方法需要返回計算結果給onPostExecute方法。

  • onProgressUpdate(Progress… values)

在主線程執行,當後臺任務的執行進度發生改變時此方法會被調用。

  • onPostExecute(Result result)

在主線程中執行,在異步任務結束之後,此方法被調用,其中result參數是後臺任務的返回值,即DoInBackground的返回值。

上面幾個方法,onPreExecute先執行,接着是doInBackground,最後纔是onPostExecute。除了上述四個方法以外,AsyncTask還提供了一個onCancelled()方法,它同樣在主線程中執行,當異步任務被取消時,onCancelled就會被調用,這個時候onPostExecute就不會被調用。下面提供一個典型的示例(這個示例就是源碼中的)

   class DownloadTask extends AsyncTask<URL,Integer,Long>{

        @Override
        protected Long doInBackground(URL... urls) {
            int count = urls.length;
            long totalSize = 0;
            for (int i = 0; i < totalSize; i++) {
                totalSize += DownLoad.downloadFile(urls[i]);
                publicProgress((int)((i / (float)count)*100));
                if(isCancelled()){
                    break;
                }
            }
            return totalSize;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
           setProgressPercent(values[0]);
        }

        @Override
        protected void onPostExecute(Long aLong) {
            showDialog(aLong);
        }
    }

AsyncTask在使用的過程中也有一些條件限制的,主要有如下幾點:

  • 1、AsyncTask的類必須在主線程加載,也就意味着第一次訪問AsyncTask必須發生在主線程,當然這個過程在Android4.1之後已經被系統自動完成了,在Android5.0的源碼中,可以看到ActivityThread的main方法,它會調用AsyncTask的init方法,這就滿足了AsyncTask的類必須在主線程中加載這個條件了。
  • 2、AsyncTask的對象必須在主線程創建。
  • 3、execute方法必須在UI線程調用。
  • 4、不要再程序中直接使用onPreExecute、onPostExecute、DoInBackground和onProgressUpdate方法。
  • 5、一個AsyncTask對象只能執行一次,即只能調用一次execute方法,否則會運行時異常。
  • 6、在Android1.6之前,AsyncTask是串行執行任務的,Android1.6之後開始採用線程池並行處理,但是從Android3.0開始,爲了避免AsyncTask所帶來的併發錯誤,AsyncTask又採用一個線程來串行任務。儘管如此,在Android3.0以及以後的版本中,我們也可以通過AsyncTask的executteOnExecutor方法來並行的執行任務。

二、AsyncTask的工作原理
爲了分析AsyncTask的工作原理,我們從它的execute方法開始分析

	android.os.AsyncTask#execute
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
		//private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
        return executeOnExecutor(sDefaultExecutor, params);
    }

	android.os.AsyncTask#executeOnExecutor
	public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
        if (mStatus != Status.PENDING) {
            switch (mStatus) {
                case RUNNING:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task is already running.");
                case FINISHED:
                    throw new IllegalStateException("Cannot execute task:"
                            + " the task has already been executed "
                            + "(a task can be executed only once)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

在上面的代碼中,sDefaultExecutor實際上是一個串行的線程池,一個進程中所有的AsyncTask全部在這個串行的的線程池中排隊執行,這個排隊過程後面再進行分析。在executeOnExecutor方法中,AsyncTask的onPreExecute方法最先執行,然後線程池開始執行。下面分析下線程池的執行過程,如下所示。

	public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
	private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;

	private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

在這裏插入圖片描述

	mWorker = new WorkerRunnable<Params, Result>() {
		public Result call() throws Exception {
			mTaskInvoked.set(true);

			Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
			//noinspection unchecked
			return postResult(doInBackground(mParams));
		}
	};

在mWorker的call方法裏,首先將mTaskInvoked設置爲true,表示當前任務已經被調用過了,然後執行AsyncTask的doInBackgroud方法,接着將其返回值傳遞給postResult方法,它的實現如下所示。

    private Result postResult(Result result) {
        @SuppressWarnings("unchecked")
        Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
                new AsyncTaskResult<Result>(this, result));
        message.sendToTarget();
        return result;
    }
	
	private Handler getHandler() {
        return mHandler;
    }
	
	private final Handler mHandler;
	在下面代碼賦值
	mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
	? getMainHandler()
	: new Handler(callbackLooper);
			
	private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());//創建實例
            }
            return sHandler;
        }
    }
	

postResult方法會通過sHandler發送給一個MESSAGE_POST_RESULT的消息,這個sHandler的定義如下所示。

    private static class InternalHandler extends Handler {
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;
            switch (msg.what) {
                case MESSAGE_POST_RESULT:
                    // There is only one result
                    result.mTask.finish(result.mData[0]);
                    break;
                case MESSAGE_POST_PROGRESS:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

可以發現,sHandler是一個靜態的Handler對象,爲了能夠將執行環境切換到主線程,這就是要求sHandler對象必須在主線程中創建。由於靜態成員會在加載類的時候進行初始化,因此就變相要求AsyncTask的類必須在主線程加載,否則同一個進程中的AsyncTask都將無法正常工作。sHandler收到MESSAGE_POST_RESULT這個消息後會調用AsyncTask的finish方法,如下所示。

    private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

AsyncTask的finish方法的邏輯比較簡單,如果AsyncTask被取消執行了,那麼就會調用onCancelled方法,否則就會調用onPostExecute方法,可以看到doInBackground的返回結果會傳遞給onPostExecute方法,到這裏AsyncTack的整個工作過程就完畢了。

三、HandlerThread
HandlerThread繼承了Thread,它是一種可以使用Handler的Thread,它的實現也很簡單,就是在run方法中通過Looper.prepare()來創建消息隊列,並通過Looper.loop()來開啓消息循環,這樣在實際的使用中就允許在HandlerThread中創建Handler了,HandlerThread的run如下:

比如我們可以使用一個HandlerThread,將我們的業務切換到子線程執行(和主線程的邏輯剛好相反)。

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

在這裏插入圖片描述

四、IntentService

在這裏插入圖片描述

    @Override
    public void onCreate() {
        // TODO: It would be nice to have an option to hold a partial wakelock
        // during processing, and to have a static startService(Context, Intent)
        // method that would launch the service & hand off a wakelock.

        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

當IntentService被第一次啓動時,它的onCreate會被調用,onCreate方法會創建一個HandlerThread,然後使用它的Looper來構造一個Handler對象mServiceHandler,這樣通過mServiceHandler發送的消息最終都會在HandlerThread中執行,從這個角度來看,IntentService也可以用於執行後臺任務。每次啓動onstartCommon就會調用一次,IntentService在onStartCommon中處理每個後臺任務的intent。下面看一下onStartCommon方法是如何處理外界的intent的,onStartCommon調用了onStart,onStart方法實現如下所示。

    @Override
    public void onStart(Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }

在這裏插入圖片描述

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

這裏是引用
在這裏插入圖片描述

public class LocalIntentService extends IntentService {

    private static final String TAG = "LocalIntentService";

    public LocalIntentService() {
        super(TAG);
    }

    @Override
    protected void onHandleIntent( Intent intent) {
        String action = intent.getStringExtra("task_action");
        Log.i(TAG,"receiver action:" + action);
        SystemClock.sleep(3000);
        if("com.lgl.test.ACTION".equals(action)){
            Log.i(TAG,"handler action:" + action);
        }
    }

    @Override
    public void onDestroy() {
        Log.i(TAG,"onDestroy");
        super.onDestroy();
    }
}

五、Android中的線程池
線程池有如下優點。

  • 1、重用線程池中的線程,避免因爲線程的創建和銷燬所帶來的性能開銷。
  • 2、能有效控制線程池的最大併發數,避免大量的線程之間因互相搶佔系統資源而導致的阻塞現象。
  • 3、能夠對線程進行簡單的管理,並提供定時執行以及指定間隔循環執行等功能。

Android中的線程池的概念來源於Java中的Executor,Executor是一個接口,真正的線程池實現爲ThreadPoolExecutor。ThreadPoolExecutor提供了一系列參數來配置線程池,通過不同的參數可以創建不同的線程池,從線程池的功能特性上來說,Android的線程池主要分爲4類,可以通過Executors所提供的工廠方法得到的。

1、ThreadPoolExecutor
ThreadPoolExecutor是線程池的真正實現,它的構造方法提供了一系列的參數來配置,構造如下所示。

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue
                              ThreadFactory threadFactory) {
  • corePoolSize

線程池的核心線程數,默認情況下,核心線程會在線程池中一直存活,即使它們處於閒置狀態。如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置爲true,那麼閒置的核心線程在等待新任務到來時會有超時策略,這個間隔由keepAliveTime指定,當等待時間超過keepAliveTime所指的時長後,核心線程就會被終止。

  • maximumPoolSize

線程池所容納最大的線程數,當活動線程數達到這個數值後,後續的新任務將會被阻塞。

  • keepAliveTime

非核心線程閒置時的超時時長,超過這個時長,非核心線程就會被回收。當ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置爲true時,keepAliveTime同樣會作用於核心線程。

  • unit

用於指定keepAliveTime參數的時間單位,這是一個枚舉,常用的有TimeUnit,毫秒,秒以及分鐘等。

  • workQueue

線程池的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個參數中。

  • threadFactory

線程工廠,爲線程池提供創建新線程的功能,ThreadFactory是一個接口,它只有一個方法,Thread new
Thread(Runnable r)。

這裏是引用
在這裏插入圖片描述

    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;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {

        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable runnable) {
            return new Thread(runnable, "AsyncTask #" + mCount.getAndIncrement());
        }
    };
    private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingDeque<Runnable>(128);
    public static final Executor THREAD_POOL_EXECUTOR =
            new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

在這裏插入圖片描述

2、線程池的分類
常見的4中不同功能特性的線程池

  • FixedThreadPool

在這裏插入圖片描述

    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
  • CachedThreadPool

這裏是引用

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • ScheduleThreadPool

這裏是引用

    public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }
  • SingleTheardExecutor

這裏是引用

    public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1, threadFactory));
    }

下面演示下系統預置的4中線程池的典型用法。

Runnable common = new Runnable() {
	@Override
	public void run() {
		SystemClock.sleep(2000);
	}
};

ExecutorService fixThreadPool = Executors.newFixedThreadPool(4);
fixThreadPool.execute(common);

ExecutorService cacheThreadPool = Executors.newCachedThreadPool();
cacheThreadPool.execute(common);

ScheduledExecutorService scheduleThreadPool = Executors.newScheduledThreadPool(4);
scheduleThreadPool.schedule(common,2000,TimeUnit.MICROSECONDS);
scheduleThreadPool.scheduleAtFixedRate(common,10,1000,TimeUnit.MICROSECONDS);

ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
singleThreadPool.execute(common);

總結:線程和線程池都介紹完成了。平時開發中可以封裝一個線程池工具類。這樣避免隨意的new Thread導致一些性能開銷或內存泄漏等問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章