天天看點

Android多線程任務優化1:探讨AsyncTask的缺陷

 AsyncTask還有别的缺陷,在生成listview的時候,如果adapter裡面的count動态改變的話,不能使用AsyncTask,隻能使用Thread+Handler,否則會出現如下錯誤

java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only

from the UI thread. [in ListView(2130968590, class com.ryantang.rtimageloader.CustomListView) with Adapter(class android.widget.HeaderViewListAdapter)]

在重寫ListView的onScroll方法時,想動态改變listview的項目數,用了AsyncTask發生了這個FC。。

經過親測

executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)

這麼調用才會fc

直接execute()不會fc,順序逐一加載。

解決辦法:可以自己寫線程池,

private static final BlockingQueue<Runnable> sPoolWorkQueue = 

new LinkedBlockingQueue<Runnable>(10); 

可以改成final LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();無大小限制的

其實AsyncTask的使用時有技巧的,應該采用分項和預讀來控制,不可能一段程式裡進行不休止的運作,而且應該控制好異常處理,應該由一個控制線程來處理AsyncTask的調用判斷線程池是否滿了,如果滿了則線程睡眠否則請求AsyncTask繼續處理。

導語:在開發Android應用的過程中,我們需要時刻注意保障應用的穩定性和界面響應性,因為不穩定或者響應速度慢的應用将會給使用者帶來非常差的互動體驗。在越來越講究使用者體驗的大環境下,使用者也許會因為應用的一次Force Close(簡稱FC)或者延遲嚴重的動畫效果而解除安裝你的應用。由于現在的應用大多需要異步連接配接網絡,本系列文章就以建構網絡應用為例,從穩定性和響應性兩個角度分析多線程網絡任務的性能優化方法。

AsyncTask是Android為我們提供的友善編寫異步任務的工具類,但是,在了解AsyncTask的實作原理之後,發現AsyncTask并不能滿足我們所有的需求,使用不當還有可能導緻應用FC。

本文主要通過分析AsyncTask送出任務的政策和一個具體的例子,說明AsyncTask的不足之處,至于解決辦法,我們将在下篇再講解。

分析:

AsyncTask類包含一個全局靜态的線程池,線程池的配置參數如下:

private static final int CORE_POOL_SIZE =5;//5個核心工作線程  

   private static final int MAXIMUM_POOL_SIZE = 128;//最多128個工作線程  

   private static final int KEEP_ALIVE = 1;//空閑線程的逾時時間為1秒  

   private static final BlockingQueue<Runnable> sWorkQueue =  

           new LinkedBlockingQueue<Runnable>(10);//等待隊列  

   private static final ThreadPoolExecutorsExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,  

           MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue,sThreadFactory);//線程池是靜态變量,所有的異步任務都會放到這個線程池的工作線程内執行。  

我們這裡不詳細講解ThreadPoolExecutor的原理,但将會講解一個異步任務送出到AsyncTask的線程池時可能會出現的4種情況,并會提出在Android硬體配置普遍較低這個客觀條件下,每個情況可能會出現的問題。

1、線程池中的工作線程少于5個時,将會建立新的工作線程執行異步任務(紅色表示新任務,下同)

Android多線程任務優化1:探讨AsyncTask的缺陷

2、線程池中已經有5個線程,緩沖隊列未滿,異步任務将會放到緩沖隊列中等待

Android多線程任務優化1:探讨AsyncTask的缺陷

3、線程池中已經有5個線程,緩沖隊列已滿,那麼線程池将新開工作線程執行異步任務

Android多線程任務優化1:探讨AsyncTask的缺陷

問題:Android的裝置一般不超過2個cpu核心,過多的線程會造成線程間切換頻繁,消耗系統資源。

4、線程池中已經有128個線程,緩沖隊列已滿,如果此時向線程送出任務,将會抛出RejectedExecutionException

Android多線程任務優化1:探讨AsyncTask的缺陷

問題:抛出的錯誤不catch的話會導緻程式FC。

好吧,理論分析之後還是要結合實際例子,我們通過實作一個模拟異步擷取網絡圖檔的例子,看看會不會出現上面提到的問題。

例子:使用GridView模拟異步加載大量圖檔

ActivityA.java

package com.zhuozhuo;  

import java.util.ArrayList;  

import java.util.Collection;  

import java.util.HashMap;  

import java.util.Iterator;  

import java.util.List;  

import java.util.ListIterator;  

import java.util.Map;  

import android.app.Activity;  

import android.app.AlertDialog;  

import android.app.Dialog;  

import android.app.ListActivity;  

import android.app.ProgressDialog;  

import android.content.Context;  

import android.content.DialogInterface;  

import android.content.Intent;  

import android.database.Cursor;  

import android.graphics.Bitmap;  

import android.os.AsyncTask;  

import android.os.Bundle;  

import android.provider.ContactsContract;  

import android.util.Log;  

import android.view.LayoutInflater;  

import android.view.View;  

import android.view.ViewGroup;  

import android.widget.AbsListView;  

import android.widget.AbsListView.OnScrollListener;  

import android.widget.Adapter;  

import android.widget.AdapterView;  

import android.widget.AdapterView.OnItemClickListener;  

import android.widget.BaseAdapter;  

import android.widget.GridView;  

import android.widget.ImageView;  

import android.widget.ListAdapter;  

import android.widget.SimpleAdapter;  

import android.widget.TextView;  

import android.widget.Toast;  

public class ActivityA extends Activity {  

    private GridView mGridView;  

    private List<HashMap<String, Object>> mData;  

    private BaseAdapter mAdapter;  

    private ProgressDialog mProgressDialog;  

    private static final int DIALOG_PROGRESS = 0;  

    @Override  

    public void onCreate(Bundle savedInstanceState) {  

        super.onCreate(savedInstanceState);  

        setContentView(R.layout.main);  

        mGridView = (GridView) findViewById(R.id.gridview);  

        mData = new ArrayList<HashMap<String,Object>>();  

        mAdapter = new CustomAdapter();  

        mGridView.setAdapter(mAdapter);  

    }  

    protected void onStart () {  

        super.onStart();  

        new GetGridDataTask().execute(null);//執行擷取資料的任務  

    protected Dialog onCreateDialog(int id) {  

        switch (id) {  

        case DIALOG_PROGRESS:  

            mProgressDialog = new ProgressDialog(ActivityA.this);  

            mProgressDialog.setMessage("正在擷取資料");  

            mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);  

            return mProgressDialog;  

        }  

        return null;  

    class CustomAdapter extends BaseAdapter {  

        CustomAdapter() {  

        @Override  

        public int getCount() {  

            return mData.size();  

        public Object getItem(int position) {  

            return mData.get(position);  

        public long getItemId(int position) {  

            return 0;  

        public View getView(int position, View convertView, ViewGroup parent) {  

            View view = convertView;  

            ViewHolder vh;  

            if(view == null) {  

                view = LayoutInflater.from(ActivityA.this).inflate(R.layout.list_item, null);  

                vh = new ViewHolder();  

                vh.tv = (TextView) view.findViewById(R.id.textView);  

                vh.iv = (ImageView) view.findViewById(R.id.imageView);  

                view.setTag(vh);  

            }  

            vh = (ViewHolder) view.getTag();  

            vh.tv.setText((String) mData.get(position).get("title"));  

            Integer id = (Integer) mData.get(position).get("pic");  

            if(id != null) {  

                vh.iv.setImageResource(id);  

            else {  

                vh.iv.setImageBitmap(null);  

            FifoAsyncTask task = (FifoAsyncTask) mData.get(position).get("task");  

            if(task == null || task.isCancelled()) {  

                Log.d("Test", "" + position);  

                mData.get(position).put("task", new GetItemImageTask(position).execute(null));//執行擷取圖檔的任務  

            return view;  

    static class ViewHolder {  

        TextView tv;  

        ImageView iv;  

    class GetGridDataTask extends FifoAsyncTask<Void, Void, Void> {  

        protected void onPreExecute () {  

            mData.clear();  

            mAdapter.notifyDataSetChanged();  

            showDialog(DIALOG_PROGRESS);//打開等待對話框  

        protected Void doInBackground(Void... params) {  

            try {  

                Thread.sleep(500);//模拟耗時的網絡操作  

            } catch (InterruptedException e) {  

                e.printStackTrace();  

            for(int i = 0; i < 200; i++) {  

                HashMap<String, Object> hm = new HashMap<String, Object>();  

                hm.put("title", "Title");  

                mData.add(hm);  

            return null;  

        protected void onPostExecute (Void result) {  

            mAdapter.notifyDataSetChanged();//通知ui界面更新  

            dismissDialog(DIALOG_PROGRESS);//關閉等待對話框  

    class GetItemImageTask extends FifoAsyncTask<Void, Void, Void> {  

        int pos;  

        GetItemImageTask(int pos) {  

            this.pos = pos;  

                Thread.sleep(2000); //模拟耗時的網絡操作  

            mData.get(pos).put("pic", R.drawable.icon);  

}  

Android多線程任務優化1:探讨AsyncTask的缺陷

由運作圖可見

當網絡情況較差,異步任務不能盡快完成執行的情況下,新開的線程會造成listview滑動不流暢。當開啟的工作線程過多時,還有出現FC的可能。

至此,你還相信萬能的AsyncTask嗎?至于你信不信,反正我不信。

總結:

AsyncTask可能存在新開大量線程消耗系統資源和導緻應用FC的風險,是以,我們需要根據自己的需求自定義不同的線程池,由于篇幅問題,将留到下篇再講。

為了給使用者帶來良好的互動體驗,在Android應用的開發過程中需要把繁重的任務(IO,網絡連接配接等)放到其他線程中異步執行,達到不阻塞UI的效果。

本系列文章由淺入深介紹Android進行異步處理的實作方法和系統底層的實作原理。

提供資料:

介紹如何使用Thread+Handler的方式從非UI線程發送界面更新消息到UI線程

介紹如何使用AsyncTask異步更新UI界面

追蹤系統代碼,介紹Thread+Handler的實作原理

追蹤系統代碼,介紹系統底層AsyncTask的實作原理 

繼續閱讀