Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> AsyncTask異步任務與LruCache緩存策略實現圖片加載(一)

AsyncTask異步任務與LruCache緩存策略實現圖片加載(一)

編輯:關於Android編程

AsyncTask異步任務

以下內容節選自官方文檔:
AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
異步任務使我們能簡單正確的使用UI線程,這個類允許我們做後台操作並將結果展示到UI線程中去,而不需要進行復雜的線程和handler操作
AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.)
異步任務這個類是作為線程和Handler的輔助類,而不該被看做通用的線程框架,異步任務用在短時操作(幾秒鐘)上十分理想。
If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.
如果需要保持線程持續長時間運行,那麼建議你使用Java並發庫提供的APIs,比如Executor,ThreadPoolExcutor和FutureTask。
An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread.
異步的任務是指運行在後台的線程執行的種種計算,這個計算結果將返回到UI線程。
An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute
異步任務有3個泛型參數,分別是Params,Progress和Result,同事有四個步驟,onPreExecute,doInBackground,onProgressUpdate,和onPostExecute。

注意以下文字:
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.
最開始引入的時候,異步任務是嚴格在一個後台單線程中被執行的,從DONUT開始(通過Build.VERSION_CODES.DONUT查看到改版本是 September 2009發布屬於Android 1.6,API 4)改成允許多任務並行執行線程池的方式。從HONEYCOMB(February 2011: Android 3.0. API 11)開始,任務有變回單線程以避免很多由於並行執行造成的應用程序出錯。所以目前的異步任務是單線程執行的。

簡介,AsyncTask是一個輕量級的異步任務框架,一般由於Android單線程的模型,導致網絡訪問請求需要異步完成.相比於handler,AsyncTask更加方便靈活,只需要重寫幾個方法即可:

Result doInBackground(Params… params) 這裡的返回值Result就是上面官方文檔中說的第三個泛型參數。params參數列表,這是AsyncTask.execute(Params… params)方法中傳過去的參數列表

void onPreExecute(),這個方法在doInBackground之前執行,可以做些初始化操作。

void onProgressUpdate(Void… value),在 publishProgress(Progress…)之後調用,這些參數是publishProgress傳過來的。

void publishProgress (Progress… values) 這個方法可以在doInBackground方法中調用,用來在後台進行耗時操作的同時通知UI線程做一些更新,該方法觸發onProgressUpdate,當後台任務被取消時,則不會回調onProgressUpdate方法。

使用

AsyncTask must be subclassed to be used. The subclass will override at least one method (doInBackground(Params…)), and most often will override a second one (onPostExecute(Result).)
異步任務需要作為子類被使用,子類必須覆寫doInBackground方法,通常還要覆寫onPostExecute方法,官方例子:

private class DownloadFilesTask extends AsyncTask {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);//這裡可以顯示進度條等
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

然後執行下面代碼即可執行異步任務

new DownloadFilesTask().execute(url1, url2, url3);

自定義AsyncTask例子:

 /**
     * 實現網絡訪問的異步操作
     */

    class NewsAsyncTask extends AsyncTask> {

        //程序先運行到這裡
        @Override
        protected List doInBackground(String... params) {
            Build.VERSION_CODES.HONEYCOMB
            //prams[0]是闖入的URL
            Log.e("test", "Task中" + Thread.currentThread().getName().toString());
            publishProgress();
            return getJsonData(params[0]);
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        /**
         * 我們已經有了數據源這時候要通過是配置器來刷新數據
         *
         * @param newsBeans
         */
        @Override
        protected void onPostExecute(List newsBeans) {
            super.onPostExecute(newsBeans);
            NewsAdapter adapter = new NewsAdapter(MainActivity.this, newsBeans);
            mListView.setAdapter(adapter);
            adapter.notifyDataSetChanged();
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
        }

    }

在適當的地方執行異步任務
MainActivity.java

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView) findViewById(R.id.lv_main);

        //創建異步任務
        mNewsAsyncTask = new NewsAsyncTask();
        mNewsAsyncTask.execute(URL);
        Log.e("test", "onCreate中---->" + Thread.currentThread().getName().toString());

    }

完整的代碼如下:

public class MainActivity extends Activity {

    private ListView mListView;
    private String URL = "http://www.imooc.com/api/teacher?type=4&num=30";

    private NewsAsyncTask mNewsAsyncTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mListView = (ListView) findViewById(R.id.lv_main);

        //創建異步任務
        mNewsAsyncTask = new NewsAsyncTask();
        mNewsAsyncTask.execute(URL);
        Log.e("test", "onCreate中---->" + Thread.currentThread().getName().toString());

    }

    @Override
    protected void onResume() {
        super.onResume();
    }

    /**
     * 實現網絡訪問的異步操作
     */

    class NewsAsyncTask extends AsyncTask> {

        //程序先運行到這裡
        @Override
        protected List doInBackground(String... params) {
            Build.VERSION_CODES.HONEYCOMB
            //prams[0]是闖入的URL
            Log.e("test", "Task中" + Thread.currentThread().getName().toString());
            publishProgress();
            return getJsonData(params[0]);
        }

        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        /**
         * 我們已經有了數據源這時候要通過是配置器來刷新數據
         *
         * @param newsBeans
         */
        @Override
        protected void onPostExecute(List newsBeans) {
            super.onPostExecute(newsBeans);
            NewsAdapter adapter = new NewsAdapter(MainActivity.this, newsBeans);
            mListView.setAdapter(adapter);
            adapter.notifyDataSetChanged();
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
        }

    }

    //Stream是字節流,java.io.Reader和java.io.InputStream組成了Java輸入類,是IO庫提供的平行獨立的等級結構
    //InputStream和Output處理8位元,byte是8bit,inputstreamreader將inputstream適配到reader
    //Reader和Writer處理16位元,可以處理中文,char是16bit,FIleReader重文件輸入,CharArrayReader重程序字符數組輸入,PipedReader從另一個線程的PipedWriter寫的字符
    private String readStream(InputStream in) {
        InputStreamReader isr = null;
        String result = "";
        try {
            String line = "";//每一行數據
            //使用BufferedReader對InputStreamReader進行包裝,Reader是字符流
            isr = new InputStreamReader(in, "utf-8");
            //
            BufferedReader br = new BufferedReader(isr);
            //br有許多方法,如read,readLine
            while ((line = br.readLine()) != null) {
                result = line + result;
            }

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }

    private List getJsonData(String weburl) {
        List newsBeanList = new ArrayList<>();
        try {
            URL url = new URL(weburl);
            HttpURLConnection httpUrlConnection = (HttpURLConnection) url.openConnection();
            InputStream is = httpUrlConnection.getInputStream();//從網絡中獲取輸入流
            String jsonStr = readStream(is);//將輸入流裝換成string字符串

            //使用內置的Json解析工具
            JSONObject jsonObject;
            NewsBean newsBean;
            jsonObject = new JSONObject(jsonStr);
            JSONArray jsonArray = (JSONArray) jsonObject.getJSONArray("data");
            for (int i = 0; i < jsonArray.length(); i++) {
                jsonObject = jsonArray.getJSONObject(i);
                newsBean = new NewsBean();
                newsBean.newsIconUrl = jsonObject.getString("picSmall");
                newsBean.newsTitle = jsonObject.getString("name");
                newsBean.newsContent = jsonObject.getString("description");
                newsBeanList.add(newsBean);
            }

            Log.e("test", jsonStr);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        Log.e("test", newsBeanList.toString());
        return newsBeanList;
    }

}

NewsAdapter.java

public class NewsAdapter extends BaseAdapter {
    List mList;
    LayoutInflater mInflater;
    ImageLoader mImageLoader;

    public NewsAdapter(Context context, List mList) {
        this.mList = mList;
        mInflater = LayoutInflater.from(context);
        mImageLoader = new ImageLoader();
    }

    @Override
    public int getCount() {
        return mList.size();
    }

    @Override
    public Object getItem(int position) {
        return mList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.item_layout, null);//無父控件
            viewHolder = new ViewHolder();
            viewHolder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_Icon);
            viewHolder.tvContent = (TextView) convertView.findViewById(R.id.tv_content);
            viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();//從緩存中得到convertView的控件
        }
        String imgUrl = mList.get(position).newsIconUrl;
        viewHolder.ivIcon.setTag(imgUrl);
//        new ImageLoader().showImageByThread(viewHolder.ivIcon, mList.get(position).newsIconUrl);


    }

    class ViewHolder {
        public TextView tvTitle, tvContent;
        public ImageView ivIcon;
    }
}

實體類NewsBean.java

public class NewsBean {
    public String newsIconUrl;
    public String newsTitle;
    public String newsContent;
}

涉及到LruCache緩存的類ImageLoader.java

public class ImageLoader {


    //    Lru算法
    private LruCache mCache;


    public ImageLoader() {
        int maxCache = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxCache / 4;
        //設置最大緩存
        mCache = new LruCache(cacheSize) {
            //在每次存入的時候調用
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }


    public void addBitmapToCache(String url, Bitmap bitmap) {
        if (getBitMapFromUrl(url) == null) {
            mCache.put(url, bitmap);
        }
    }


    public Bitmap getBitmapFromCache(String url) {
        return mCache.get(url);
    }

    private ImageView mImageView;

    private String imgUrl = "";

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mImageView.getTag().equals(imgUrl))
                mImageView.setImageBitmap((Bitmap) msg.obj);
            //mImageView.setImageResource();//在UI線程中執行
        }
    };

//    public void showImageByThread(ImageView imageView, final String url) {
//        mImageView = imageView;
//        imgUrl = url;
//        new Thread() {
//            @Override
//            public void run() {
//                Bitmap bitmap = getBitMapFromUrl(url);
//                Message message = Message.obtain();
//                message.obj = bitmap;
//                handler.sendMessage(message);
//            }
//        }.start();
//    }

    public Bitmap getBitMapFromUrl(String imageurl) {
        Bitmap bitmap;
        InputStream is = null;
        try {
            URL url = new URL(imageurl);
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            is = httpURLConnection.getInputStream();
            bitmap = BitmapFactory.decodeStream(is);
            httpURLConnection.disconnect();
            //Thread.sleep(1000);異步加載不用這個
            return bitmap;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return null;

    }


    public void showImageByAnsyncTask(ImageView imageView, String url) {
        //先從緩存中讀取
        Bitmap bitmap = getBitmapFromCache(url);
        if (bitmap == null) {
            new NewsAsyncTask(imageView, url).execute(url);//execute要將參數傳過去
        } else {
            mImageView.setImageBitmap(bitmap);
        }
    }


    /**
     * 使用異步任務加載圖片
     */

    private class NewsAsyncTask extends AsyncTask {

        private ImageView mImageView;
        private String mUrl;

        public NewsAsyncTask(ImageView imageView, String url) {
            mImageView = imageView;
            mUrl = url;
        }


        /**
         * 獲取圖片在這裡操作
         *
         * @param params
         * @return
         */
        @Override
        protected Bitmap doInBackground(String... params) {
            Bitmap bitmap = getBitMapFromUrl(params[0]);
            if (bitmap != null) {
                addBitmapToCache(params[0], bitmap);
            }
            return bitmap;
        }


        @Override
        protected void onPreExecute() {
            super.onPreExecute();
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            if (mUrl.equals(mImageView.getTag()))
                mImageView.setImageBitmap(bitmap);
        }

        @Override
        protected void onProgressUpdate(Void... values) {
            super.onProgressUpdate(values);
        }
    }

}

LruCache緩存

下面解釋下ImageLoader中使用到的LruCache,以下內容截取自官方文檔,可能翻譯的不好,有錯誤請大家指正:

A cache that holds strong references to a limited number of values. Each time a value is accessed, it is moved to the head of a queue
LruCache持有有限個數的“值”的強引用,每一次一個“值”獲取到,它就被移到隊列的頭部。
When a value is added to a full cache, the value at the end of that queue is evicted and may become eligible for garbage collection.
當某個值加到緩存已滿的lrucache中時,在隊尾的值會被趕走,並且可能會合法地被GC垃圾回收掉。
If your cached values hold resources that need to be explicitly released,override entryRemoved(boolean, K, V, V).
如果你的緩存值持有需要顯式被釋放的資源時,覆寫entryRemoved方法。
If a cache miss should be computed on demand for the corresponding keys, override create(K)
如果緩存無法計算一些“鍵”(keys)對應的值(value),覆寫create方法去實現計算key對應的value。默認實現是返回null。
This simplifies the calling code, allowing it to assume a value will always be returned, even when there’s a cache miss.
這簡化了我們調用代碼,允許 假定某個“值”始終會得到返回,即使某個緩存已經丟失。
By default, the cache size is measured in the number of entries. Override sizeOf(K, V) to size the cache in different units
默認情況下,緩存的尺寸由entries(緩存bitmap的話尺寸就是bitmap的個數)的數量所衡量,如果需要用不同的單位衡量緩存,覆寫sizeOf方法。sizeof默認的返回是1(緩存bitmap的話就是指一個bitmap的大小)。
官方API提供的例子如下:

 int cacheSize = 4 * 1024 * 1024; // 4MiB
   LruCache  bitmapCache = new LruCache (cacheSize) {
       protected int sizeOf(String key, Bitmap value) {
           return value.getByteCount();

   }}

注意這裡cacheSize和sizeOf中返回值的單位需要一致,不然有可能會出錯。

下面再提供官方Training-Building Apps with Graphics & Animation-Displaying Bitmaps Efficiently-Caching Bitmaps文章中提供的緩存Bitmap圖片的例子:

private LruCache mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

上面緩存的大小的計量單位統一為KB。

Note: In this example, one eighth of the application memory is allocated for our cache. On a normal/hdpi device this is a minimum of around 4MB (32/8). A full screen GridView filled with images on a device with 800x480 resolution would use around 1.5MB (800*480*4 bytes), so this would cache a minimum of around 2.5 pages of images in memory.
官方文檔說了,在這個例子中,開辟了1/8的應用內存空間作為緩存最大值,一個hdpi分辨率的設備最少大概有32M內存的樣子,這時候一個應弄能得到的大概4M,全屏的GridView在hdpi(800*480)的手機上將會使用800*400*4=1.5M的內存空間,所以達到4M最大值需要2.5頁滿屏的圖片。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved