解決AsyncTask引起的RejectedExecutionException

AsyncTask是google爲易用和有效的異步操做UI線程的所開發的一個封裝類。使用者能夠很輕易的使用其進行後臺操做,而後將結果傳給UI線程,而不須要使用Thread和Handler。html

這樣好用的一個類,顯然能夠在ListView異步加載圖片時大顯身手,本着這樣的想法,筆者瞬間就完成了一段這樣的模擬代碼:java

Adapter的getView方法:android

複製代碼

 1         @Override 2         public View getView(int pos, View view, ViewGroup viewGroup) { 3             if (view == null) { 4                 view = getLayoutInflater().inflate(R.layout.test2_list_item, 5                         null); 6             } 7             ImageView imageView = (ImageView) view.findViewById(R.id.imageView); 8             //這裏每次都new出一個新的AsyncTask,進行執行。 9             new TestAsyncTask(imageView, pos).execute(new Object());10             TextView itemView = (TextView) view.findViewById(R.id.itemView);11             itemView.setText("測試數據" + pos);12             return view;13         }

複製代碼

TestAsyncTask:api

複製代碼

 1 private class TestAsyncTask extends AsyncTask { 2         private ImageView imageView; 3         private int index; 4  5         public TestAsyncTask(ImageView imageView, int index) { 6             this.imageView = imageView; 7             this.index = index; 8         } 9 10         @Override11         protected Object doInBackground(Object... arg0) {12             // 模擬長時間的運算;大多數狀況下是等待進行網絡請求。13             long sum = 0;14             for (int i = 0; i <= 10000 * 100; i++) {15                 sum += i * 1l;16             }17             return null;18         }19 20         @Override21         protected void onPostExecute(Object result) {22             //模擬已經得到了網絡請求的圖片23             imageView.setImageBitmap(BitmapFactory.decodeResource(24                     getResources(), R.drawable.image01, null));25         }26     }

複製代碼

運行調試,圖片一個一個全都加載出來,沒有問題,正當我瘋狂的向下翻動着,妄圖享受一下列表迅速滑動的快感的時候,一個無情的錯誤彈了出來。網絡

04-17 11:22:52.009: E/AndroidRuntime(22792): FATAL EXCEPTION: main
04-17 11:22:52.009: E/AndroidRuntime(22792): java.util.concurrent.RejectedExecutionException: pool=128/128, queue=10/10
04-17 11:22:52.009: E/AndroidRuntime(22792): at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:1961)
04-17 11:22:52.009: E/AndroidRuntime(22792): at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:794)
04-17 11:22:52.009: E/AndroidRuntime(22792): at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1315)多線程

好吧,如今我理解爲啥網絡上流傳的異步加載圖片是用更加繁瑣的Thread加Handler形式,而不用AsyncTask了。oracle

難道是AsyncTask有什麼用法上的問題,從出錯結果來看貌似是達到了某個上限被拒絕了。那咱們就從這個java.util.concurrent.RejectedExecutionException錯誤入手。app

翻看java的在線文檔RejectedExecutionException,咱們看到了這個錯誤的解釋異步

複製代碼

public class RejectedExecutionException extends RuntimeException
Exception thrown by an Executor when a task cannot be accepted for execution. Since: 1.5 See Also: Serialized Form

複製代碼

這個代表是Executor這個類不接受執行task時拋出的異常。而Executor是java1.5以後增長的java.util.concurrent包中操做多線程的主要類。能夠執行多個RunnableTask,看來google的AsyncTask就是用的這套API。ide

瞭解到這點,咱們就能夠打開AsyncTask的源碼查看它究竟是怎麼實現的:

打開源碼以後,找到execute方法: 

複製代碼

 1     /** 2      * Executes the task with the specified parameters. The task returns 3      * itself (this) so that the caller can keep a reference to it. 4      * 5      * This method must be invoked on the UI thread. 6      * 7      * @param params The parameters of the task. 8      * 9      * @return This instance of AsyncTask.10      *11      * @throws IllegalStateException If {@link #getStatus()} returns either12      *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.13      */14     public final AsyncTask<Params, Progress, Result> execute(Params... params) {15         if (mStatus != Status.PENDING) {16             switch (mStatus) {17                 case RUNNING:18                     throw new IllegalStateException("Cannot execute task:"19                             + " the task is already running.");20                 case FINISHED:21                     throw new IllegalStateException("Cannot execute task:"22                             + " the task has already been executed "23                             + "(a task can be executed only once)");24             }25         }26 27         mStatus = Status.RUNNING;28 29         onPreExecute();30 31         mWorker.mParams = params;32         sExecutor.execute(mFuture);33 34         return this;35     }

複製代碼

 找到這裏使用的ExecutorsExecutor這個屬性,這樣來到它賦值的地方

複製代碼

 1     private static final String LOG_TAG = "AsyncTask"; 2  3     private static final int CORE_POOL_SIZE = 5; 4     private static final int MAXIMUM_POOL_SIZE = 128; 5     private static final int KEEP_ALIVE = 10; 6  7     private static final BlockingQueue<Runnable> sWorkQueue = 8             new LinkedBlockingQueue<Runnable>(10); 9 10     private static final ThreadFactory sThreadFactory = new ThreadFactory() {11         private final AtomicInteger mCount = new AtomicInteger(1);12 13         public Thread newThread(Runnable r) {14             return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());15         }16     };17 18     private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,19             MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);

複製代碼

能夠看到他是構造了一個ThreadPoolExecutor常量,保證new出多個AsyncTask都是使用這一個Executor。異常應該是它拋出的,咱們看下這個類的文檔,其中有一段是這樣描述的:

複製代碼

Rejected tasks  New tasks submitted in method execute(Runnable) will be rejected when the Executor has been shut down, and also when the Executor uses finite bounds for both maximum threads and work queue capacity, and is saturated. In either case, the execute method invokes the rejectedExecution(Runnable, ThreadPoolExecutor) method of its         RejectedExecutionHandler. Four predefined handler policies are provided:
  1.In the default ThreadPoolExecutor.AbortPolicy, the handler throws a runtime RejectedExecutionException upon rejection.   2.In ThreadPoolExecutor.CallerRunsPolicy, the thread that invokes execute itself runs the task. This provides a simple feedback control mechanism that will slow    down the rate that new tasks are submitted.   3.In ThreadPoolExecutor.DiscardPolicy, a task that cannot be executed is simply dropped.   4.In ThreadPoolExecutor.DiscardOldestPolicy, if the executor is not shut down, the task at the head of the work queue is dropped, and then execution is retried     (which can fail again, causing this to be repeated.)
It is possible to define and use other kinds of RejectedExecutionHandler classes. Doing so requires some care especially when policies are designed to work only under particular capacity or queuing policies.

複製代碼

 原來在Executor的隊列和容量都達到最大時,再次增長新的task的時候將會進行拒絕行爲,而默認的拒絕行爲就是拋出這個RejectedExecutionException異常。

 看到這裏頓時恍然了,原來初始化ThreadPoolExecutor沒有指定拒絕行爲,致使了異常的發生。google,你能夠的!

 那解決方案也就誕生了,就是複製一份AsyncTask的源碼,本身重寫這個初始化方法,增長相應的拒絕策略,後面就有幾個可供選擇的策略。修改AsyncTask源碼以下

1     private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,2             MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory, new ThreadPoolExecutor.DiscardOldestPolicy());

再次運行,OK,不會再拋出那個異常了。

其實當你看AsyncTask的google官方文檔時,你會發現後面有這麼一段:

Order of execution

When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting with HONEYCOMB, tasks are executed on a single thread to avoid common application errors caused by parallel execution.

也就是說剛開始,AsyncTask是被限制在一個線程裏。從1.6開始,使用了線程池,容許運行多任務同時進行。而到了3.0以後,又被限制在一個線程裏爲了不多線程執行的錯誤。

變3次了,你能夠的,google。

爲了驗證這段話,瞅瞅3.0以後的AsyncTask的源碼:

複製代碼

    private static final String LOG_TAG = "AsyncTask";    private static final int CORE_POOL_SIZE = 5;    private static final int MAXIMUM_POOL_SIZE = 128;    private static final int KEEP_ALIVE = 1;    private static final ThreadFactory sThreadFactory = new ThreadFactory() {        private final AtomicInteger mCount = new AtomicInteger(1);        public Thread newThread(Runnable r) {            return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
        }
    };    private static final BlockingQueue<Runnable> sPoolWorkQueue =            new LinkedBlockingQueue<Runnable>(10);    /**
     * 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 final int MESSAGE_POST_RESULT = 0x1;    private static final int MESSAGE_POST_PROGRESS = 0x2;    private static final InternalHandler sHandler = new InternalHandler();    //這裏咱們發現到了這個默認的執行器,是下面實現的線性隊列。    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;    private final WorkerRunnable<Params, Result> mWorker;    private final FutureTask<Result> mFuture;    private volatile Status mStatus = Status.PENDING;    
    private final AtomicBoolean mCancelled = new AtomicBoolean();    private final AtomicBoolean mTaskInvoked = new AtomicBoolean();    
    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);
            }
        }
    }

複製代碼

 這樣咱們也就能夠根據3.0的源碼修改咱們的AsyncTask裏的默認執行器,一樣使用SerialExecutor保證只會啓用一個線程。另外3.0的AsyncTask多了一個executeOnExecutor(java.util.concurrent.Executor, Object[])方法,若是你但願有多個任務同時啓動,也可使用THREAD_POOL_EXECUTOR執行。

該篇文章只是淺淺的探討了一下使用AsyncTaskRejectedExecutionException問題解決,對於多線程理解多有不足之處,歡迎各位大牛拍磚。

相關文章
相關標籤/搜索