異步加載(AsyncTask異步任務、Handler、Json解析、Lrucache緩存、ListView滑動優化等來實現ListView圖文混排)

本文是學習筆記,原視頻地址:異步加載。主要是從慕課網提供提供的接口(www.imooc.com/api/teacher?type=4&num=30)獲取數據進行Json解析,將解析得到的數據放到ListView中,觀察Json數據,我們主要用到的是name 作爲標題,description作爲內容,picSmall作爲ImageView獲取圖片的網址。其中用到的網絡請求,AsyncTask,Json解析,將在本文的末尾貼出詳細地址(如果還沒貼就是我還沒更完)

一、AsyncTask請求網絡獲取Jason數據解析後獲取數據源,建立適配器顯示在ListView中

(注:由於返回Json數據中picSmall爲圖片地址,所以第一步驟,先將ImageView中的圖片設置爲ic_launcher,在第二步驟中將聽過兩種方式來處理)

先新建佈局文件item.xml作爲ListView的Item

<?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="wrap_content"
    android:padding="4dp"
    android:orientation="horizontal">
    <ImageView
        android:id="@+id/item_pic"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:src="@mipmap/ic_launcher"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingLeft="4dp"
        android:layout_gravity="center">
       <TextView
           android:id="@+id/item_title"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:textSize="20sp"
           android:text="This is a Title"
           android:gravity="center"
           android:ellipsize="end"
           android:maxLines="1"/>
        <TextView
            android:id="@+id/item_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:maxLines="3"
            android:textSize="15sp"
            android:text="This is Content"
            />

    </LinearLayout>
    

</LinearLayout>

也就是左邊一個顯示圖片的ImageView,右邊有兩行,放兩個TextView分別顯示標題和內容


在MainActivity中:

 1.先在MainActivity的佈局文件中加一個ListView,比較簡單,略去不寫

2.在MainActivity中,我們需要考慮到由於是網絡請求屬於費時操作,此處需要使用多線程和Handelr或者使用AsyncTask來處理,此處我們使用AsyncTask來處理

。開始之前,我們先搞清楚需要傳進去的參數,以及最終向要的結果是什麼?本程序中,我們傳進去一個url,想最終返回的是包含圖片地址的url(還要對圖片地址做進一步處理纔會達到最終想要的圖片)、題目、內容的多個對象,所以我們需要新建一個類來封裝這些屬性

2.1新建NewsBean:

package com.example.administrator.asyncapplication;

public class NewsBean {
    public  String newsPicUrl;
    public String newsTitle;
    public String newsContent;



}

2.2使用AsyncTask:

class NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
        @Override
        protected List<NewsBean> doInBackground(String... params) {
            return getJsonData (params[0]);
        }

        @Override
        protected void onPostExecute(List<NewsBean> newsBeans) {
            super.onPostExecute(newsBeans);
            
        }

    }
此時,我們需要一個getJsonData來返回我們所需要的List<NewsBean> 對象

2.2 獲取從網絡上返回的數據之後進行Json解析:

 private List<NewsBean> getJasonData(String url){
         List<NewsBean> news = new ArrayList<NewsBean>();


        try {
          String data = getDataFromInputStream(new URL(url).openStream());
           // Log.i("xyz", data);
            //Json解析
            NewsBean bean;
            JSONObject jsonObject;
            try {
                jsonObject = new JSONObject(data);
                JSONArray jsonArray = jsonObject.getJSONArray("data");
                for(int i=0;i<jsonArray.length();i++){
                    jsonObject = jsonArray.getJSONObject(i);
                    bean = new NewsBean();
                    bean.newsPicUrl=jsonObject.getString("picSmall");
                    bean.newsTitle=jsonObject.getString("name");
                    bean.newsContent=jsonObject.getString("description");
                    news.add(bean);
                }


            } catch (JSONException e) {
                e.printStackTrace();
            }
            ;
        } catch (IOException e) {
            e.printStackTrace();
        }
        //System.out.println(news.size());
        return news;

    }

2.3讀取服務器返回的InputStream數據 getDataFromInputStream

    //讀取輸入流,返回字符
    private String getDataFromInputStream(InputStream is){
        InputStreamReader isr;
        String result = "";//獲取讀取每一行之後的所有數據

        try {
            isr = new InputStreamReader(is, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(isr);
            String line = "";
            while ((line = bufferedReader.readLine()) != null) {
                result += line;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }


        return result;
    }

以上幾個步驟,我們得到數據源List<NewsBean>,之後

3.1建立適配器NewAdapter(用BaseAdapter):

public class NewAdapter extends BaseAdapter {
    List<NewsBean> list;
    LayoutInflater inflater;
 
    public NewAdapter(Context context,List<NewsBean> list){
        this.list=list;
        inflater = LayoutInflater.from(context);
     
    }
    @Override
    public int getCount() {
        return list.size();
    }

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

    @Override
    public long getItemId(int position) {

        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder=null;
        NewsBean bean = list.get(position);
        if(convertView==null){
            viewHolder = new ViewHolder();
            convertView=inflater.inflate(R.layout.item,null);
            viewHolder.pic = (ImageView)convertView.findViewById(R.id.item_pic);
            viewHolder.title =(TextView)convertView.findViewById(R.id.item_title);
            viewHolder.content=(TextView)convertView.findViewById(R.id.item_content);
            convertView.setTag(viewHolder);
        }
        else{
            viewHolder =(ViewHolder)convertView.getTag();
        }
        //先將ImageView的圖片設爲固定的,後面會更改
        viewHolder.pic.setImageResource(R.mipmap.ic_launcher);
        viewHolder.title.setText(bean.newsTitle);
        viewHolder.content.setText(bean.newsContent);

        return convertView;
    }
    class ViewHolder{
        public TextView title,content;
        public ImageView pic;
    }
}

3.2需要在MainActivity中創建異步任務的onPostExecute方法中設置適配器:

  protected void onPostExecute(List<NewsBean> newsBeans) {
            super.onPostExecute(newsBeans);
            listView.setAdapter(new NewAdapter(MainActivity.this,newsBeans));
        }

3.3在MainActivity中的onCreate()方法中執行異步任務:

public class MainActivity extends ActionBarActivity {

    ListView listView;
    private static String URL ="http://www.imooc.com/api/teacher?type=4&num=30" ;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView)findViewById(R.id.lv_main);

        //請求網絡,異步加載
        new NewsAsyncTask().execute(URL);

    }



之後運行程序。

二、將得到的圖片url解析成圖片並設置給ImageView,並且做規避ListView的View複用的處理以及用Lrucache對圖片進行緩存

然後,我們來處理圖片,由於Jason數據中解析出來的是圖片地址,所以們需要連接網絡,獲取所需要的圖片。可以用多線程和異步加載:

1.採用多線程的方式給ImageView設置圖片:

1.1新建類ImageLoader,之後新建兩個成員變量。ImageView mImageView; String mUrl;之後:

  //從內部類中訪問url需要被聲明爲最終類型
    public void showImageByThread(ImageView imageView,final String url){
        mUrl = url;
        mImageView = imageView;
        new Thread(){
            @Override
            public void run() {
                super.run();
                Bitmap bitmap = getbitMapFromUrl(url);
                Message message = Message.obtain();
                message.obj=bitmap;
                //用Handler機制,將獲得的圖片通過消息發送出去
                mHandler.sendMessage(message);
            }
        }.start();
    }

1.2 getbitMapFromUrl :

//通過一個網絡url地址返回一個圖片
    //得到拂去其返回的輸入流
    //通過BitmapFactory的decodeStream()方法來獲取圖片
    public Bitmap getbitMapFromUrl(String url){
        InputStream in=null;
        Bitmap bitMap;
        URL url1 = null;

        try {
            url1 = new URL(url);
            HttpURLConnection connection= (HttpURLConnection) url1.openConnection();
            in = new BufferedInputStream(connection.getInputStream());//對InputStream進行封裝
            bitMap = BitmapFactory.decodeStream(in);//根據InputStream得到BitMap
            connection.disconnect();
            return bitMap;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }



        return null;

1.3 在Handler中進行消息處理 handleMessage
 private Handler mHandler = new Handler(){
        //收到並處理消息
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
           
                mImageView.setImageBitmap((Bitmap) msg.obj);
            

        }
    };
1.4在NewAdapter中在設置完默認圖片之後,加上一行

new ImageLoader(viewHolder.pic,uri);

(String url = list.get(position).newsPicUrl; url即爲newsBean的屬性之一)

1.5之後運行程序,可以看到現在已經完全能接收到圖片了,是不是已經完美了呢?遠遠不是!在快速滑動的過程中,可以看到圖片出現閃動的狀態,原因是刷新了多次,第一次是

ListView中的緩存,先從緩存池中拿出圖片,然後再從網絡上下載,等於顯示了兩次,有時候會造成圖片錯位等。如何解決,將ViewHolder.pic和url聯繫起來,只有是當前的url時,才設置圖片。在NewAdapter中 viewHolder.pic.setTag(url);在ImageLoader中的Handler處理中加上;if(mImageView.getTag().equals(mUrl)){mImageView.setBitmap((Bitmap)mas.obj)},之後再快速滑動,可以看到不會出現剛纔的情況。

2.用AsyncTask的方式加載圖片,不再詳解

2.1

//異步任務
    class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
        //前期在設置ImageView和後期在避免ListView的緩存機制設置Tag時,需要用到url
        private ImageView mImageView;
        private String mUrl;
        public NewsAsyncTask(ImageView imageView,String url){
            mImageView = imageView;
            mUrl = url;
        }

        
        @Override
        protected Bitmap doInBackground(String... params) {
           
            return  <span style="font-family: Arial, Helvetica, sans-serif;">getbitMapFromUrl(url);</span>
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            if (mImageView.getTag().equals(mUrl)) {
                mImageView.setImageBitmap(bitmap);
            }
        }
2.2
 //通過異步任務將圖片設置給ImageView
    public void showImageByAsyncTask(ImageView imageView,String url){
       
            new NewsAsyncTask(imageView, url).execute(url);
        

    }
之後再NewAdapter做相應的更改即可。現在程序上下滑動可以看到先出現默認圖標,然後才顯示了圖片,還不夠完美,因爲圖片沒有緩存,導致每次都得重新加載下面通過AsyncTask來介紹使用LruCache緩存

三、使用LruCache緩存

注意lruche對象的底層是用map來是實現的,所以很多map的方法,我們在這裏也可以使用,存儲也是通過鍵值對的方式,key在此處是圖像的url,value是Bitmap

1.創建cache對象

我們在ImageLoader的構造函數中來初始化LruCache

這樣當ImageLoader對象被創建時,也就初始化了cache

public class ImageLoader {
     
    private LruCache<String,Bitmap> caches;
    public ImageLoader(){
        //獲得最大內存,從而設置緩存的空間大小,當款村的圖騙超過設置的空間大小時
        //lru最近未被使用的圖片就會被拋出緩存
        int maxMermory = (int)Runtime.getRuntime().maxMemory();
        int cacheSize = maxMermory/8;
        //匿名內部類 獲得cache實例
        caches = new LruCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };

    }
2.兩個方法,一個時從緩存中獲取圖片,另一個是添加圖片到緩存中:

 //先看緩存中是否有圖片,沒有的話就加到緩存中去
    public void addBitmapToCache(String url,Bitmap bitmap){
        if(getBitmapFromCache(url)==null){
            caches.put(url,bitmap);
        }

    }
    //根據url(key)從緩存中取出Bitmao(value)
    public Bitmap getBitmapFromCache(String url){
        return caches.get(url);
    }
3.之後,還需要對showImageByAsyncTask進行修改,給ImageView設置圖片之前,先看緩存中是否有對應圖片,沒有的話,從網絡上進行加載,有的話,直接設置

,但是,還有一點,從網絡上加載完了,應該添加到緩存中,以便下次備用

 //通過異步任務將圖片設置給ImageView
    public void showImageByAsyncTask(ImageView imageView,String url){
        Bitmap bitMap = getBitmapFromCache(url);
        //如果緩存中沒有圖片,就從網絡上下載
        //如果緩存中有圖片了,直接設置
        if(bitMap==null) {
            new NewsAsyncTask(imageView, url).execute(url);
        }else{
            imageView.setImageBitmap(bitMap);
        }

    }

 //異步任務
    class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
        //前期在設置ImageView和後期在避免ListView的緩存機制設置Tag時,需要用到url
        private ImageView mImageView;
        private String mUrl;
        public NewsAsyncTask(ImageView imageView,String url){
            mImageView = imageView;
            mUrl = url;
        }

        //先從網絡上下載圖片,下載下來之後,不需要重複下載,需要加入緩存
        @Override
        protected Bitmap doInBackground(String... params) {
            String url = params[0];
            Bitmap bitmap = getbitMapFromUrl(url);
            if(bitmap!=null){
                addBitmapToCache(url,bitmap);
            }
            return  bitmap;
        }

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


經過這幾個步驟,緩存已經差不多了,爲什麼說差不多呢?因爲我們在NewAdapter中還沒有修改的,回想一下我們如何調用的

new ImageLoader().showImageByAsyncTask(viewHolder.pic,url);
這樣,每次調用都重新生成了ImageLoader對象,哪裏有緩存了?解決辦法,將生成的ImageLoader對象保存起來
在NewAdapter中
private ImageLoader mImageLoader;
之後再NewAdapter的構造函數中,
mImageLoader = new ImageLoader();
之後在getView方法中更改:
mImageLoader.showImageByAsyncTask(viewHolder.pic,url);
至此,緩存也已經完成,運行程序,可以看到當加載完圖片之後,再往上滑動,將會使用緩存,不會重複加載,這就省了流量
(待續。。。。)





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