Android開發--異步加載


因為移動端軟件開發思維模式或者說是開發的架構其實是不分平台和編程語言的,就拿安卓和IOS來說,他們都是移動前端app開發展示數據和用戶交互數據的數據終端,移動架構的幾個大模塊:UI界面展示、本地數據可持續化存儲、網絡數據請求、性能優化等等,安卓和IOS開發都要考慮這些架構的模塊。所以,熟悉IOS的開發的人,再去學習一下安卓的開發以及安卓的開發模式,你會發現很多技術和思想安卓和IOS是一樣的,只是可能說法不一樣,由於編程語言比如OC和Java略微的差異性,編碼習慣和細節不一樣之外,其他都是一樣的。

 

本人開始對安卓略有興趣,開始對安卓粗淺的學習,一方面也會拿IOS和安卓進行對比闡述,如果你會IOS,再學習安卓的,閱讀本人的博客也許會對你有很大的幫助。

(但是對於安卓開發的語言基礎Java、以及android studio的使用,本人不會詳細闡述,作為有情懷有獨立能力的程序員,這些基礎應該不會難道你們的吧,更何況本人對android studio的使用一直通過google和百度來學習相關的setting和快捷鍵)

 

在Android中,異步加載最常用的兩種方式:

  1、多線程\線程池

  2、AsyncTask

當然,AsyncTask底層是基於線程池實現的。所以以上兩種方法是異曲同工。

 

一、首先,按照在IOS中用UITableView加載數據的思路一樣,我們先要創建出自定義的Cell以及Cell的布局:

新建item_layout.xml文件:

源碼:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="wrap_content"
 5     android:padding="4dp"
 6     android:orientation="horizontal"
 7     >
 8     <ImageView
 9         android:id="@+id/iv_icon"
10         android:layout_width="64dp"
11         android:layout_height="64dp"
12         android:src="@mipmap/ic_launcher"/>
13 
14 
15     <LinearLayout
16         android:layout_width="match_parent"
17         android:layout_height="match_parent"
18         android:padding="4dp"
19         android:gravity="center"
20         android:orientation="vertical">
21 
22         <TextView
23             android:id="@+id/tv_title"
24             android:layout_width="match_parent"
25             android:layout_height="wrap_content"
26             android:textSize="15sp"
27             android:maxLines="1"
28             android:text="標題標題標題"/>
29 
30 
31         <TextView
32             android:id="@+id/tv_content"
33             android:layout_width="match_parent"
34             android:layout_height="wrap_content"
35             android:textSize="10sp"
36             android:maxLines="3"
37             android:text="內容內容內容"/>
38 
39     </LinearLayout>
40 
41 
42 </LinearLayout>

就這樣,一個自定義的item就創建好了,就好比我們IOS中xib或者storyboard上的UITableViewCell創建好了。

然后接着在activity_main.xml中創建一個ListView,這個ListView就好比我們IOS的UITableView。

源碼:

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     xmlns:tools="http://schemas.android.com/tools"
 4     android:id="@+id/activity_main"
 5     android:layout_width="match_parent"
 6     android:layout_height="match_parent"
 7     android:paddingBottom="@dimen/activity_vertical_margin"
 8     android:paddingLeft="@dimen/activity_horizontal_margin"
 9     android:paddingRight="@dimen/activity_horizontal_margin"
10     android:paddingTop="@dimen/activity_vertical_margin"
11     tools:context="com.example.heyang.myapplication.MainActivity">
12 
13     <ListView
14         android:id="@+id/lv_main"
15         android:layout_width="match_parent"
16         android:layout_height="match_parent"
17         />
18 
19 
20 </RelativeLayout>

二、因為每一個item或者類比IOS的每一個Cell都需要一個圖片地址、標題Title、內容Content三個數據,所以我們就需要一個模型對象來一一映射對應到item,在安卓或者Java中的說法叫創建一個Bean對象,其實可以類比理解為IOS的model模型對象。

源碼:

 1 package com.example.heyang.myapplication;
 2 
 3 /**
 4  * Created by HeYang on 16/10/5.
 5  */
 6 
 7 public class NewsBean {
 8     // 包含三個屬性:1、標題2、內容3、圖片的網址
 9     public String newsIconURL;
10     public String newsTitle;
11     public String newsContent;
12 }

 

三、接着就是要數據源了,http://www.imooc.com/api/teacher?type=4&num=30,點擊這個URL打開網頁會看到一大堆數據,然后你可以通過json格式轉換工具就可以看到json的數據格式。

那么接下來就很容易明白了,模型Bean對象中的三個屬性就可以來自這個URL下的json數據中的name、picSmall、discription。

那么接下來就是IOS中所謂的字典轉模型的步驟。只不過在這之前,先要通過網絡請求獲取到這些數據才行,這里網絡請求獲取json數據的做法有點和IOS的不同了。

感覺IOS的網絡請求,不管是蘋果的NSURLSession還是第三方的AFN框架都已經是封裝好的網絡請求框架。而安卓的獲取json數據的做法好像更接近底層,采用了輸入輸出流來獲取URL的網頁數據:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.os.AsyncTask;
  4 import android.support.v7.app.AppCompatActivity;
  5 import android.os.Bundle;
  6 import android.util.Log;
  7 import android.widget.ListView;
  8 
  9 import org.json.JSONArray;
 10 import org.json.JSONException;
 11 import org.json.JSONObject;
 12 
 13 import java.io.BufferedReader;
 14 import java.io.IOException;
 15 import java.io.InputStream;
 16 import java.io.InputStreamReader;
 17 import java.io.UnsupportedEncodingException;
 18 import java.net.URL;
 19 import java.util.ArrayList;
 20 import java.util.List;
 21 
 22 public class MainActivity extends AppCompatActivity {
 23 
 24     private ListView mListView;
 25 
 26     private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30";
 27 
 28     @Override
 29     protected void onCreate(Bundle savedInstanceState) {
 30         super.onCreate(savedInstanceState);
 31         setContentView(R.layout.activity_main);
 32 
 33         // 獲取xml上的ListView對象
 34         mListView = (ListView) findViewById(R.id.lv_main);
 35 
 36         new NewsAsyncTask().execute(URL);
 37     }
 38 
 39     // 通過輸入輸出流獲取整個網頁格式的字符串數據
 40     private String readStream(InputStream is){
 41         InputStreamReader isr;
 42         String result = "";
 43 
 44         try {
 45             String line = "";
 46             // 1、輸入流對象 2、輸入流讀取對象 3、字節讀取對象
 47             isr = new InputStreamReader(is,"utf-8");
 48             BufferedReader br = new BufferedReader(isr);
 49             while ((line = br.readLine()) != null){
 50                 result += line;
 51             }
 52         } catch(UnsupportedEncodingException e){
 53             e.printStackTrace();
 54         } catch (IOException e) {
 55             e.printStackTrace();
 56         }
 57         return result;
 58     }
 59 
 60 
 61     private List<NewsBean> getJsonData(String url){
 62         // 創建存儲NewsBean的集合對象
 63         List<NewsBean> newsBeanList = new ArrayList<>();
 64         try {
 65             // 取出網絡的json字符串的格式之后
 66             String jsonStr = readStream(new URL(url).openStream());
 67             // 就要用JSONObject對象進行解析
 68             JSONObject jsonObject;
 69             // 然后需要NewsBean,其實相當於IOS的模型對象
 70             NewsBean newsBean;
 71             try {
 72                 // jsonObject的對象,創建該對象的同時傳入json字符串格式的對象
 73                 jsonObject = new JSONObject(jsonStr);
 74                 // 拿到jsonObject對象之后,就需要通過key值來拿到數組
 75                 JSONArray jsonArray = jsonObject.getJSONArray("data");
 76                 // 然后開始遍歷數組,獲取模型數組
 77                 for (int i = 0;i<jsonArray.length();i++){
 78                     // 數組里每一個元素又是jsonObject
 79                     jsonObject = jsonArray.getJSONObject(i);
 80 
 81                     // 開始創建模型對象
 82                     newsBean = new NewsBean();
 83                     newsBean.newsIconURL = jsonObject.getString("picSmall");
 84                     newsBean.newsTitle = jsonObject.getString("name");
 85                     newsBean.newsContent = jsonObject.getString("description");
 86                     
 87                     // 創建的一個模型對象,就要添加到集合當中去
 88                     newsBeanList.add(newsBean);
 89                 }
 90 
 91 
 92             } catch (JSONException e) {
 93                 e.printStackTrace();
 94             }
 95 
 96             Log.d("heyang",jsonStr);
 97         } catch (IOException e) {
 98             e.printStackTrace();
 99         }
100 
101         return newsBeanList;
102     }
103 
104     // 創建一個內部類來實現 ,在實現下面內部類之前,需要自定義的Bean對象來封裝處理Josn格式的數據
105     class  NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
106         @Override
107         protected List<NewsBean> doInBackground(String... strings) {
108             return getJsonData(strings[0]);
109         }
110     }
111 }

按照以上的源碼,就能順利的獲取到了ListView的數據源數組。

但是在網絡請求方面,別忘了要給項目增加Internet訪問權限:

<!--增加 網絡訪問權限 -->
<uses-permission android:name="android.permission.INTERNET"/>

 

四、創建繼承BaseAdapter自定義的Adapter,注意其中利用了匿名內部類來優化處理ListView的每一個view的循環利用:

這里要補充一下,安卓加載ListView用到了適配器模式,所以需要下面自定義的Adapter對象,這個和我們IOS開發的加載UITableView用到的一些列代理方法的代理模式還是有區別的。不過,如果讀者學習了適配器模式,將會對安卓底層用到的適配器模式就會有所理解了。

 1 package com.example.heyang.myapplication;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.BaseAdapter;
 8 import android.widget.ImageView;
 9 import android.widget.TextView;
10 
11 import java.util.List;
12 
13 /**
14  * Created by HeYang on 16/10/6.
15  */
16 
17 public class NewsAdapter extends BaseAdapter {
18 
19     // 適配器對象需要傳入Bean數據集合對象,類似IOS的模型數組集合
20     private List<NewsBean> beanList;
21     // 然后要傳入LayoutInflater對象,用來獲取xml文件的視圖控件
22     private LayoutInflater layoutInflater;
23 
24     // 創建構造方法
25     public NewsAdapter(MainActivity context, List<NewsBean> data){
26         beanList = data;
27         layoutInflater = LayoutInflater.from(context);// 這個context對象就是Activity對象
28     }
29 
30     @Override
31     public int getCount() {
32         return beanList.size();
33     }
34 
35     @Override
36     public Object getItem(int i) {
37         // 因為beanList是數組,通過get訪問對應index的元素
38         return beanList.get(i);
39     }
40 
41     @Override
42     public long getItemId(int i) {
43         return i;
44     }
45 
46     @Override
47     public View getView(int i, View view, ViewGroup viewGroup) {
48         ViewHolder viewHolder = null;
49         if (view == null){
50             viewHolder = new ViewHolder();
51             // 每一個View都要和layout關聯
52             view = layoutInflater.inflate(R.layout.item_layout,null);
53             // 在R.layout.item_layout中有三個控件對象
54             // 現在全部傳遞給view對象了
55             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
56             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
57             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
58             view.setTag(viewHolder);
59 
60         }else {
61 
62             // 重復利用,但是由於里面的View展示的數據顯然需要重新賦值
63             viewHolder = (ViewHolder) view.getTag();
64         }
65         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
66         viewHolder.tvContent.setText(beanList.get(i).newsContent);
67         // 先默認加載系統圖片
68         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);
69 
70         return view;
71     }
72 
73     // 最后需要一個匿名內部類來創建一個臨時緩存View的對象
74     class ViewHolder{
75         public TextView tvContent,tvTitle;
76         public ImageView ivIcon;
77     }
78 }

五、最后優化和完善MainActivity對象

  1 package com.example.heyang.myapplication;
  2 
  3 import android.os.AsyncTask;
  4 import android.support.v7.app.AppCompatActivity;
  5 import android.os.Bundle;
  6 import android.util.Log;
  7 import android.widget.ListView;
  8 
  9 import org.json.JSONArray;
 10 import org.json.JSONException;
 11 import org.json.JSONObject;
 12 
 13 import java.io.BufferedReader;
 14 import java.io.IOException;
 15 import java.io.InputStream;
 16 import java.io.InputStreamReader;
 17 import java.io.UnsupportedEncodingException;
 18 import java.net.URL;
 19 import java.util.ArrayList;
 20 import java.util.List;
 21 
 22 public class MainActivity extends AppCompatActivity {
 23 
 24     private ListView mListView;
 25 
 26     private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30";
 27 
 28     @Override
 29     protected void onCreate(Bundle savedInstanceState) {
 30         super.onCreate(savedInstanceState);
 31         setContentView(R.layout.activity_main);
 32 
 33         // 獲取xml上的ListView對象
 34         mListView = (ListView) findViewById(R.id.lv_main);
 35 
 36         new NewsAsyncTask().execute(URL);
 37     }
 38 
 39     // 通過輸入輸出流獲取整個網頁格式的字符串數據
 40     private String readStream(InputStream is){
 41         InputStreamReader isr;
 42         String result = "";
 43 
 44         try {
 45             String line = "";
 46             // 1、輸入流對象 2、輸入流讀取對象 3、字節讀取對象
 47             isr = new InputStreamReader(is,"utf-8");
 48             BufferedReader br = new BufferedReader(isr);
 49             while ((line = br.readLine()) != null){
 50                 result += line;
 51             }
 52         } catch(UnsupportedEncodingException e){
 53             e.printStackTrace();
 54         } catch (IOException e) {
 55             e.printStackTrace();
 56         }
 57         return result;
 58     }
 59 
 60 
 61     private List<NewsBean> getJsonData(String url){
 62         // 創建存儲NewsBean的集合對象
 63         List<NewsBean> newsBeanList = new ArrayList<>();
 64         try {
 65             // 取出網絡的json字符串的格式之后
 66             String jsonStr = readStream(new URL(url).openStream());
 67             // 就要用JSONObject對象進行解析
 68             JSONObject jsonObject;
 69             // 然后需要NewsBean,其實相當於IOS的模型對象
 70             NewsBean newsBean;
 71             try {
 72                 // jsonObject的對象,創建該對象的同時傳入json字符串格式的對象
 73                 jsonObject = new JSONObject(jsonStr);
 74                 // 拿到jsonObject對象之后,就需要通過key值來拿到數組
 75                 JSONArray jsonArray = jsonObject.getJSONArray("data");
 76                 // 然后開始遍歷數組,獲取模型數組
 77                 for (int i = 0;i<jsonArray.length();i++){
 78                     // 數組里每一個元素又是jsonObject
 79                     jsonObject = jsonArray.getJSONObject(i);
 80 
 81                     // 開始創建模型對象
 82                     newsBean = new NewsBean();
 83                     newsBean.newsIconURL = jsonObject.getString("picSmall");
 84                     newsBean.newsTitle = jsonObject.getString("name");
 85                     newsBean.newsContent = jsonObject.getString("description");
 86 
 87                     // 創建的一個模型對象,就要添加到集合當中去
 88                     newsBeanList.add(newsBean);
 89                 }
 90 
 91 
 92             } catch (JSONException e) {
 93                 e.printStackTrace();
 94             }
 95 
 96             Log.d("heyang",jsonStr);
 97         } catch (IOException e) {
 98             e.printStackTrace();
 99         }
100 
101         return newsBeanList;
102     }
103 
104     // 創建一個內部類來實現 ,在實現下面內部類之前,需要自定義的Bean對象來封裝處理Josn格式的數據
105     class  NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{
106         @Override
107         protected List<NewsBean> doInBackground(String... strings) {
108             return getJsonData(strings[0]);
109         }
110 
111         @Override
112         protected void onPostExecute(List<NewsBean> newsBeen) {
113             super.onPostExecute(newsBeen);
114             NewsAdapter newsAdapter = new NewsAdapter(MainActivity.this,newsBeen);
115             mListView.setAdapter(newsAdapter);
116 
117         }
118     }
119 }

 好,運行一下模擬器看看結果:

六、下面采用兩種方法進行加載圖片:①多線程加載圖片 ②AsyncTask

①多線程加載圖片:

創建一個普通的Class類:ImageLoader,在內部使用線程run運行執行ImageView加載網絡圖片的邏輯

 1 package com.example.heyang.myapplication;
 2 
 3 import android.graphics.Bitmap;
 4 import android.graphics.BitmapFactory;
 5 import android.os.Handler;
 6 import android.os.Message;
 7 import android.widget.ImageView;
 8 
 9 import java.io.BufferedInputStream;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.net.HttpURLConnection;
13 import java.net.MalformedURLException;
14 import java.net.URL;
15 
16 /**
17  * Created by HeYang on 16/10/6.
18  */
19 
20 public class ImageLoader {
21 
22     private ImageView mImageView;
23     private String mURLStr;
24 
25     private Handler handler = new Handler(){
26         @Override
27         public void handleMessage(Message msg) {
28             super.handleMessage(msg);
29 
30             if (mImageView.getTag().equals(mURLStr)){
31                 mImageView.setImageBitmap((Bitmap) msg.obj);
32             }
33 
34 
35         }
36     };
37 
38     public void showImageByThread(ImageView imageView, final String urlString){
39 
40         mImageView = imageView;
41         mURLStr = urlString;
42 
43         new Thread(){
44             @Override
45             public void run() {
46                 super.run();
47                 Bitmap bitmap = getBitmapFromURL(urlString);
48                 // 當前線程是子線程,並不是UI主線程
49                 // 不是Message message = new Message();
50                 Message message = Message.obtain();
51                 message.obj = bitmap;
52                 handler.sendMessage(message);
53 
54             }
55         }.start();
56     }
57 
58     public Bitmap getBitmapFromURL(String urlString){
59         Bitmap bitmap = null;
60         InputStream is = null;
61 
62         try {
63             URL url = new URL(urlString);
64             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
65             is = new BufferedInputStream(connection.getInputStream());
66             bitmap = BitmapFactory.decodeStream(is);
67             // 最后要關閉http連接
68             connection.disconnect();
69             Thread.sleep(1000);// 睡眠1秒
70         } catch (IOException e) {
71             e.printStackTrace();
72         } catch (InterruptedException e) {
73             e.printStackTrace();
74         } finally {
75 
76             try {
77                 is.close();
78             } catch (IOException e) {
79                 e.printStackTrace();
80             }
81         }
82 
83         return bitmap;
84     }
85 }

然后修改一下之前的NewsAdapter的代碼:

 1 package com.example.heyang.myapplication;
 2 
 3 import android.content.Context;
 4 import android.view.LayoutInflater;
 5 import android.view.View;
 6 import android.view.ViewGroup;
 7 import android.widget.BaseAdapter;
 8 import android.widget.ImageView;
 9 import android.widget.TextView;
10 
11 import java.util.List;
12 
13 /**
14  * Created by HeYang on 16/10/6.
15  */
16 
17 public class NewsAdapter extends BaseAdapter {
18 
19     // 適配器對象需要傳入Bean數據集合對象,類似IOS的模型數組集合
20     private List<NewsBean> beanList;
21     // 然后要傳入LayoutInflater對象,用來獲取xml文件的視圖控件
22     private LayoutInflater layoutInflater;
23 
24     // 創建構造方法
25     public NewsAdapter(MainActivity context, List<NewsBean> data){
26         beanList = data;
27         layoutInflater = LayoutInflater.from(context);// 這個context對象就是Activity對象
28     }
29 
30     @Override
31     public int getCount() {
32         return beanList.size();
33     }
34 
35     @Override
36     public Object getItem(int i) {
37         // 因為beanList是數組,通過get訪問對應index的元素
38         return beanList.get(i);
39     }
40 
41     @Override
42     public long getItemId(int i) {
43         return i;
44     }
45 
46     @Override
47     public View getView(int i, View view, ViewGroup viewGroup) {
48         ViewHolder viewHolder = null;
49         if (view == null){
50             viewHolder = new ViewHolder();
51             // 每一個View都要和layout關聯
52             view = layoutInflater.inflate(R.layout.item_layout,null);
53             // 在R.layout.item_layout中有三個控件對象
54             // 現在全部傳遞給view對象了
55             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
56             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
57             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
58 
59             view.setTag(viewHolder);
60 
61         }else {
62 
63             // 重復利用,但是由於里面的View展示的數據顯然需要重新賦值
64             viewHolder = (ViewHolder) view.getTag();
65         }
66         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
67         viewHolder.tvContent.setText(beanList.get(i).newsContent);
68         // 先默認加載系統圖片
69         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);  // 類似加載占位圖片
70         viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
71         // 將ImageView對象和URLSting對象傳入進去
72         new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
73 
74         return view;
75     }
76 
77     // 最后需要一個匿名內部類來創建一個臨時緩存View的對象
78     class ViewHolder{
79         public TextView tvContent,tvTitle;
80         public ImageView ivIcon;
81     }
82 }

注意,其中使用了viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);作為唯一標示傳遞給ImageLoader內部做判斷,這樣加載ListView不會出現圖片加載和緩存沖突了現象。(源碼中保留了Thread.sleep(1000)可以查看到效果)。

②AsyncTask

接着,使用AsyncTask異步加載,在imageLoader對象的基礎上繼續:

完整源碼:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.graphics.Bitmap;
  4 import android.graphics.BitmapFactory;
  5 import android.os.AsyncTask;
  6 import android.os.Handler;
  7 import android.os.Message;
  8 import android.widget.ImageView;
  9 
 10 import java.io.BufferedInputStream;
 11 import java.io.IOException;
 12 import java.io.InputStream;
 13 import java.net.HttpURLConnection;
 14 import java.net.MalformedURLException;
 15 import java.net.URL;
 16 
 17 /**
 18  * Created by HeYang on 16/10/6.
 19  */
 20 
 21 public class ImageLoader {
 22 
 23     private ImageView mImageView;
 24     private String mURLStr;
 25 
 26     private Handler handler = new Handler(){
 27         @Override
 28         public void handleMessage(Message msg) {
 29             super.handleMessage(msg);
 30 
 31             if (mImageView.getTag().equals(mURLStr)){
 32                 mImageView.setImageBitmap((Bitmap) msg.obj);
 33             }
 34 
 35 
 36         }
 37     };
 38 
 39     public void showImageByThread(ImageView imageView, final String urlString){
 40 
 41         mImageView = imageView;
 42         mURLStr = urlString;
 43 
 44         new Thread(){
 45             @Override
 46             public void run() {
 47                 super.run();
 48                 Bitmap bitmap = getBitmapFromURL(urlString);
 49                 // 當前線程是子線程,並不是UI主線程
 50                 // 不是Message message = new Message();
 51                 Message message = Message.obtain();
 52                 message.obj = bitmap;
 53                 handler.sendMessage(message);
 54 
 55             }
 56         }.start();
 57     }
 58 
 59     public Bitmap getBitmapFromURL(String urlString){
 60         Bitmap bitmap = null;
 61         InputStream is = null;
 62 
 63         try {
 64             URL url = new URL(urlString);
 65             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
 66             is = new BufferedInputStream(connection.getInputStream());
 67             bitmap = BitmapFactory.decodeStream(is);
 68             // 最后要關閉http連接
 69             connection.disconnect();
 70             Thread.sleep(1000);// 睡眠1秒
 71         } catch (IOException e) {
 72             e.printStackTrace();
 73         } catch (InterruptedException e) {
 74             e.printStackTrace();
 75         } finally {
 76 
 77             try {
 78                 is.close();
 79             } catch (IOException e) {
 80                 e.printStackTrace();
 81             }
 82         }
 83 
 84         return bitmap;
 85     }
 86 
 87 
 88     // ==================使用AsyncTask====================
 89     public void showImageByAsyncTask(ImageView imageView, final String urlString){
 90         new NewsAsyncTask(imageView, (String) imageView.getTag()).execute(urlString);// 這兩個參數分別傳遞的目的地可以理解一下
 91     }
 92     
 93     private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
 94 
 95         // 需要私有的ImageView對象和構造方法來傳遞ImageView對象
 96         private ImageView mImageView;
 97         private String urlString;
 98 
 99         public NewsAsyncTask(ImageView imageView,String urlString){
100             mImageView = imageView;
101             urlString = urlString;
102         }
103 
104         @Override
105         protected Bitmap doInBackground(String... strings) {
106             // 在這個方法中,完成異步下載的任務
107             getBitmapFromURL(strings[0]);
108             return null;
109         }
110 
111 
112         @Override
113         protected void onPostExecute(Bitmap bitmap) {
114             super.onPostExecute(bitmap);
115             // 然后在這個方法中設置imageViewd
116             if (mImageView.getTag().equals(urlString)){
117                 mImageView.setImageBitmap(bitmap);
118             }
119 
120         }
121     }
122 
123 }

然后在NewsAdapter里修改一下:

 然后運行效果和上面多線程的效果一樣,這里就不再重復展示了。

 

七、LruCache緩存處理

為了提高用戶體驗,所以需要使用緩存機制。這里安卓提供了Lru算法處理緩存。

Lru:Least Recently Used 近期最少使用算法。

所以,我們需要在ImageLoader中創建LruCache對象:

完整源碼:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.annotation.TargetApi;
  4 import android.graphics.Bitmap;
  5 import android.graphics.BitmapFactory;
  6 import android.os.AsyncTask;
  7 import android.os.Build;
  8 import android.os.Handler;
  9 import android.os.Message;
 10 import android.support.annotation.RequiresApi;
 11 import android.util.Log;
 12 import android.util.LruCache;
 13 import android.widget.ImageView;
 14 
 15 import java.io.BufferedInputStream;
 16 import java.io.IOException;
 17 import java.io.InputStream;
 18 import java.net.HttpURLConnection;
 19 import java.net.MalformedURLException;
 20 import java.net.URL;
 21 
 22 /**
 23  * Created by HeYang on 16/10/6.
 24  */
 25 
 26 public class ImageLoader {
 27 
 28     private ImageView mImageView;
 29     private String mURLStr;
 30     // 創建緩存對象
 31     // 第一個參數是需要緩存對象的名字或者ID,這里我們傳輸url作為唯一名字即可
 32     // 第二個參數是Bitmap對象
 33     private LruCache<String,Bitmap> lruCache;
 34 
 35     // 然后我們需要在構造方法中初始化這個緩存對象
 36     // 另外,我們不可能把所有的緩存空間拿來用
 37     @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
 38     public ImageLoader(){
 39         
 40         // 獲取最大可用內存
 41         int maxMemory = (int) Runtime.getRuntime().maxMemory();
 42         int cachaSize =  maxMemory / 4;
 43         // 創建LruCache對象,同時用匿名內部類的方式重寫方法
 44         lruCache = new LruCache<String,Bitmap>(cachaSize){
 45             @Override
 46             protected int sizeOf(String key, Bitmap value) {
 47                 // 我們需要直接返回Bitmap value的實際大小
 48                 //return super.sizeOf(key, value);
 49                 // 在每次存入緩存的時候調用
 50                 return value.getByteCount();
 51             }
 52         };
 53     }
 54 
 55     // 然后我們要寫倆個方法:1、將bitmap存入緩存中 2、從緩存中取出bitmap
 56 
 57     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 58     public void addBitmapToCache(String url, Bitmap bitmap){
 59         if (getBitMapFromCache(url) == null){
 60             lruCache.put(url,bitmap);
 61         }
 62     }
 63 
 64     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 65     public Bitmap getBitMapFromCache(String url){
 66         return lruCache.get(url);
 67     }
 68 
 69 
 70     private Handler handler = new Handler(){
 71         @Override
 72         public void handleMessage(Message msg) {
 73             super.handleMessage(msg);
 74 
 75             if (mImageView.getTag().equals(mURLStr)){
 76                 mImageView.setImageBitmap((Bitmap) msg.obj);
 77             }
 78 
 79 
 80         }
 81     };
 82 
 83     public void showImageByThread(ImageView imageView, final String urlString){
 84 
 85         mImageView = imageView;
 86         mURLStr = urlString;
 87 
 88         new Thread(){
 89             @Override
 90             public void run() {
 91                 super.run();
 92                 Bitmap bitmap = getBitmapFromURL(urlString);
 93                 // 當前線程是子線程,並不是UI主線程
 94                 // 不是Message message = new Message();
 95                 Message message = Message.obtain();
 96                 message.obj = bitmap;
 97                 handler.sendMessage(message);
 98 
 99             }
100         }.start();
101     }
102 
103     public Bitmap getBitmapFromURL(String urlString){
104         Bitmap bitmap = null;
105         InputStream is = null;
106 
107         try {
108             URL url = new URL(urlString);
109             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
110             is = new BufferedInputStream(connection.getInputStream());
111             bitmap = BitmapFactory.decodeStream(is);
112             // 最后要關閉http連接
113             connection.disconnect();
114 //            Thread.sleep(1000);// 睡眠1秒
115         } catch (IOException e) {
116             e.printStackTrace();
117         }
118 //        catch (InterruptedException e) {
119 //            e.printStackTrace();
120 //        }
121         finally {
122 
123             try {
124                 is.close();
125             } catch (IOException e) {
126                 e.printStackTrace();
127             }
128         }
129 
130         return bitmap;
131     }
132 
133 
134     // ==================使用AsyncTask====================
135     public void showImageByAsyncTask(ImageView imageView, final String urlString){
136         // 在異步請求之前,先判斷緩存中是否有,有的話就取出直接加載
137         Bitmap bitmap = getBitMapFromCache(urlString);
138         if (bitmap == null){
139             // 如果沒有,就異步任務加重
140             new NewsAsyncTask(imageView, (String) imageView.getTag()).execute(urlString);// 這兩個參數分別傳遞的目的地可以理解一下
141         }else{
142             imageView.setImageBitmap(bitmap);
143         }
144 
145     }
146 
147     private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
148 
149         // 需要私有的ImageView對象和構造方法來傳遞ImageView對象
150         private ImageView mImageView;
151         private String mURlString;
152 
153         public NewsAsyncTask(ImageView imageView,String urlString){
154             mImageView = imageView;
155             mURlString = urlString;
156         }
157 
158         @Override
159         protected Bitmap doInBackground(String... strings) {
160             // 在這個方法中,完成異步下載的任務
161             String url = strings[0];
162             // 從網絡中獲取圖片
163             Bitmap bitmap = getBitmapFromURL(strings[0]);
164             if (bitmap != null){
165                 // 將網絡加載出來的圖片存儲緩存
166                 addBitmapToCache(url,bitmap);
167             }
168             return bitmap;
169         }
170 
171 
172         @Override
173         protected void onPostExecute(Bitmap bitmap) {
174             super.onPostExecute(bitmap);
175             // 然后在這個方法中設置imageViewd
176 
177             if (mImageView.getTag().equals(mURlString)){
178                 mImageView.setImageBitmap(bitmap);
179             }
180 
181         }
182     }
183 }

還需要在NewsAdapter類里做一個小小的優化,因為適配器的getView是個不斷被調用的方法,就好比UITableView創建Cell的代理方法,會被不斷的被調用。而在getView方法中,ImageLoader的構造方法會被不斷的被調用,這樣的話,會造成ImageLoader構造方法中的LruCache對象不斷的被創建,這樣顯然是不好的。所以我們需要用一個引用,指向一開始就創建好的ImageLoader對象,然后多次使用。

然后

完整源碼:

 1 package com.example.heyang.myapplication;
 2 
 3 import android.annotation.TargetApi;
 4 import android.content.Context;
 5 import android.os.Build;
 6 import android.support.annotation.RequiresApi;
 7 import android.view.LayoutInflater;
 8 import android.view.View;
 9 import android.view.ViewGroup;
10 import android.widget.BaseAdapter;
11 import android.widget.ImageView;
12 import android.widget.TextView;
13 
14 import java.util.List;
15 
16 /**
17  * Created by HeYang on 16/10/6.
18  */
19 
20 public class NewsAdapter extends BaseAdapter {
21 
22     // 適配器對象需要傳入Bean數據集合對象,類似IOS的模型數組集合
23     private List<NewsBean> beanList;
24     // 然后要傳入LayoutInflater對象,用來獲取xml文件的視圖控件
25     private LayoutInflater layoutInflater;
26 
27     private ImageLoader imageLoader;
28 
29     // 創建構造方法
30 
31     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
32     public NewsAdapter(MainActivity context, List<NewsBean> data){
33         beanList = data;
34         layoutInflater = LayoutInflater.from(context);// 這個context對象就是Activity對象
35         imageLoader = new ImageLoader();
36     }
37 
38     @Override
39     public int getCount() {
40         return beanList.size();
41     }
42 
43     @Override
44     public Object getItem(int i) {
45         // 因為beanList是數組,通過get訪問對應index的元素
46         return beanList.get(i);
47     }
48 
49     @Override
50     public long getItemId(int i) {
51         return i;
52     }
53 
54 
55     @Override
56     public View getView(int i, View view, ViewGroup viewGroup) {
57         ViewHolder viewHolder = null;
58         if (view == null){
59             viewHolder = new ViewHolder();
60             // 每一個View都要和layout關聯
61             view = layoutInflater.inflate(R.layout.item_layout,null);
62             // 在R.layout.item_layout中有三個控件對象
63             // 現在全部傳遞給view對象了
64             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
65             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
66             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
67 
68             view.setTag(viewHolder);
69 
70         }else {
71 
72             // 重復利用,但是由於里面的View展示的數據顯然需要重新賦值
73             viewHolder = (ViewHolder) view.getTag();
74         }
75         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
76         viewHolder.tvContent.setText(beanList.get(i).newsContent);
77         // 先默認加載系統圖片
78         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);  // 類似加載占位圖片
79         viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
80         // 將ImageView對象和URLSting對象傳入進去
81 //        new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
82         imageLoader.showImageByAsyncTask(viewHolder.ivIcon,beanList.get(i).newsIconURL);
83         return view;
84     }
85 
86     // 最后需要一個匿名內部類來創建一個臨時緩存View的對象
87     class ViewHolder{
88         public TextView tvContent,tvTitle;
89         public ImageView ivIcon;
90     }
91 }

 

八、滾動時的高效優化

前面的代碼,雖然順利的實現了異步加載的過程,但是在實際項目開發中,ListView的每一個item項可能是很復雜的,如果按照前面的代碼實現,用戶在滾動的過程就可能會出現卡頓的現象。

雖然從網絡加載的數據是在子線程中完成,但是更新UI的過程只能在主線程中更新,如果item項很復雜的話,滾動ListView出現卡頓現象也是可以理解的。

但是我們可以優化這個卡頓的問題:

  ① ListView滑動停止后才加載可見項

  ② ListView滑動時,取消所有加載項

優化的原因是:用戶在滑動的時候,因為ListView在滑動,所以沒必要做更新主線程UI的操作,而更新主線程UI的操作可以放在滑動結束的時候執行,這樣也是符合用戶交互習慣的,同時也優化了卡頓的問題。

既然我們需要監聽滾動的事件,可以直接使用監聽滾動的接口:

並實現接口的方法:

因為我們要實現在結束滾動的時候加載可見項,說的再明白點就是比如有100項需要加載,手機屏幕最多顯示8項,用戶滾動到第20項范圍內結束,這時候就要加載這地20項附近的8項即可。

所以要實現這樣的邏輯,就需要滾動結束之前,就要獲取可見項的起止位置,還要獲取這起止位置之間對應的所有數據。

接下來在前面代碼進行修改的細節就比較多了,這里就直接上修改的源碼,總體思路就是之前的一邊滾動一邊執行適配器對象的getView對象去加載每一個item,而接下來就是改成

在滾動結束的時候,才加載可見項的圖片,當然之前的getView方法里加載String字符串的邏輯仍舊可以保留,因為這個和通過網絡加載圖片的業務邏輯本質是不同的,讀者自己領會,不難。

在NewsAdapter類中:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.annotation.TargetApi;
  4 import android.content.Context;
  5 import android.os.Build;
  6 import android.support.annotation.RequiresApi;
  7 import android.view.LayoutInflater;
  8 import android.view.View;
  9 import android.view.ViewGroup;
 10 import android.widget.AbsListView;
 11 import android.widget.BaseAdapter;
 12 import android.widget.ImageView;
 13 import android.widget.ListView;
 14 import android.widget.TextView;
 15 
 16 import java.util.List;
 17 
 18 /**
 19  * Created by HeYang on 16/10/6.
 20  */
 21 
 22 public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{
 23 
 24     // 適配器對象需要傳入Bean數據集合對象,類似IOS的模型數組集合
 25     private List<NewsBean> beanList;
 26     // 然后要傳入LayoutInflater對象,用來獲取xml文件的視圖控件
 27     private LayoutInflater layoutInflater;
 28 
 29     private ImageLoader imageLoader;
 30 
 31     // 可見項的起止index
 32     private int mStart,mEnd;
 33     // 因為我們需要存儲起止項所有的url地址
 34     public static String[] URLS;
 35 
 36     // 創建構造方法
 37     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 38     public NewsAdapter(MainActivity context, List<NewsBean> data, ListView listView){
 39         beanList = data;
 40         layoutInflater = LayoutInflater.from(context);// 這個context對象就是Activity對象
 41         imageLoader = new ImageLoader(listView);
 42 
 43         // 將模型數組中的url字符串單獨存儲在靜態數組中
 44         int dataSize = data.size();
 45         URLS = new String[dataSize];
 46         for (int i = 0; i < dataSize; i++) {
 47             URLS[i] = data.get(i).newsIconURL;
 48         }
 49 
 50         // 已經要記得注冊
 51         listView.setOnScrollListener(this);
 52     }
 53 
 54     @Override
 55     public int getCount() {
 56         return beanList.size();
 57     }
 58 
 59     @Override
 60     public Object getItem(int i) {
 61         // 因為beanList是數組,通過get訪問對應index的元素
 62         return beanList.get(i);
 63     }
 64 
 65     @Override
 66     public long getItemId(int i) {
 67         return i;
 68     }
 69 
 70 
 71     @Override
 72     public View getView(int i, View view, ViewGroup viewGroup) {
 73         ViewHolder viewHolder = null;
 74         if (view == null){
 75             viewHolder = new ViewHolder();
 76             // 每一個View都要和layout關聯
 77             view = layoutInflater.inflate(R.layout.item_layout,null);
 78             // 在R.layout.item_layout中有三個控件對象
 79             // 現在全部傳遞給view對象了
 80             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
 81             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
 82             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
 83 
 84             view.setTag(viewHolder);
 85 
 86         }else {
 87 
 88             // 重復利用,但是由於里面的View展示的數據顯然需要重新賦值
 89             viewHolder = (ViewHolder) view.getTag();
 90         }
 91         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
 92         viewHolder.tvContent.setText(beanList.get(i).newsContent);
 93         // 先默認加載系統圖片
 94         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);  // 類似加載占位圖片
 95         viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
 96         // 將ImageView對象和URLSting對象傳入進去
 97 //        new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
 98         imageLoader.showImageByAsyncTask(viewHolder.ivIcon,beanList.get(i).newsIconURL);
 99         return view;
100     }
101 
102     @Override
103     public void onScrollStateChanged(AbsListView absListView, int i) {
104         // SCROLL_STATE_IDLE :  滾動結束
105         if (i == SCROLL_STATE_IDLE){
106             // 加載可見項
107             imageLoader.loadImages(mStart,mEnd);
108         }else{
109             // 停止所有任務
110             imageLoader.cancelAllTask();
111         }
112     }
113 
114     @Override
115     public void onScroll(AbsListView absListView, int i, int i1, int i2) {
116         // i是第一個可見元素 i1是當前可見元素的長度
117         mStart = i;
118         mEnd = i + i1;
119     }
120 
121     // 最后需要一個匿名內部類來創建一個臨時緩存View的對象
122     class ViewHolder{
123         public TextView tvContent,tvTitle;
124         public ImageView ivIcon;
125     }
126 }

在ImageLoader類中:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.annotation.TargetApi;
  4 import android.graphics.Bitmap;
  5 import android.graphics.BitmapFactory;
  6 import android.os.AsyncTask;
  7 import android.os.Build;
  8 import android.os.Handler;
  9 import android.os.Message;
 10 import android.support.annotation.RequiresApi;
 11 import android.util.Log;
 12 import android.util.LruCache;
 13 import android.widget.ImageView;
 14 import android.widget.ListView;
 15 
 16 import java.io.BufferedInputStream;
 17 import java.io.IOException;
 18 import java.io.InputStream;
 19 import java.net.HttpURLConnection;
 20 import java.net.MalformedURLException;
 21 import java.net.URL;
 22 import java.util.HashSet;
 23 import java.util.Set;
 24 
 25 /**
 26  * Created by HeYang on 16/10/6.
 27  */
 28 
 29 public class ImageLoader {
 30 
 31     private ImageView mImageView;
 32     private String mURLStr;
 33     // 創建緩存對象
 34     // 第一個參數是需要緩存對象的名字或者ID,這里我們傳輸url作為唯一名字即可
 35     // 第二個參數是Bitmap對象
 36     private LruCache<String,Bitmap> lruCache;
 37 
 38     private ListView mListView;
 39     private Set<NewsAsyncTask> mTasks;
 40 
 41     // 然后我們需要在構造方法中初始化這個緩存對象
 42     // 另外,我們不可能把所有的緩存空間拿來用
 43     @RequiresApi(api = Build.VERSION_CODES.HONEYCOMB_MR1)
 44     public ImageLoader(ListView listView){
 45 
 46         mListView = listView;
 47         mTasks = new HashSet<>();
 48 
 49         // 獲取最大可用內存
 50         int maxMemory = (int) Runtime.getRuntime().maxMemory();
 51         int cachaSize =  maxMemory / 4;
 52         // 創建LruCache對象,同時用匿名內部類的方式重寫方法
 53         lruCache = new LruCache<String,Bitmap>(cachaSize){
 54             @Override
 55             protected int sizeOf(String key, Bitmap value) {
 56                 // 我們需要直接返回Bitmap value的實際大小
 57                 //return super.sizeOf(key, value);
 58                 // 在每次存入緩存的時候調用
 59                 return value.getByteCount();
 60             }
 61         };
 62     }
 63 
 64     // 然后我們要寫倆個方法:1、將bitmap存入緩存中 2、從緩存中取出bitmap
 65 
 66     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 67     public void addBitmapToCache(String url, Bitmap bitmap){
 68         if (getBitMapFromCache(url) == null){
 69             lruCache.put(url,bitmap);
 70         }
 71     }
 72 
 73     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 74     public Bitmap getBitMapFromCache(String url){
 75         return lruCache.get(url);
 76     }
 77 
 78 
 79     private Handler handler = new Handler(){
 80         @Override
 81         public void handleMessage(Message msg) {
 82             super.handleMessage(msg);
 83 
 84             if (mImageView.getTag().equals(mURLStr)){
 85                 mImageView.setImageBitmap((Bitmap) msg.obj);
 86             }
 87 
 88 
 89         }
 90     };
 91 
 92     // 根據可見項起止位置加載可見項
 93     public void loadImages(int startIndex,int endIndex){
 94         for (int i = startIndex; i < endIndex; i++) {
 95             String url = NewsAdapter.URLS[i];
 96             // 在異步請求之前,先判斷緩存中是否有,有的話就取出直接加載
 97             Bitmap bitmap = getBitMapFromCache(url);
 98             if (bitmap == null){
 99                 // 如果沒有,就異步任務加重
100 //                new NewsAsyncTask(imageView, (String) imageView.getTag()).execute(urlString);// 這兩個參數分別傳遞的目的地可以理解一下
101                 NewsAsyncTask task = new NewsAsyncTask(url);
102                 task.execute(url);
103                 mTasks.add(task);
104 
105 
106 
107             }else{
108                 ImageView loadImageView = (ImageView) mListView.findViewWithTag(url);
109                 loadImageView.setImageBitmap(bitmap);
110             }
111         }
112     }
113 
114     public void cancelAllTask(){
115         if (mTasks != null){
116             for (NewsAsyncTask newsTask: mTasks) {
117                 newsTask.cancel(false);
118             }
119         }
120     }
121 
122 
123 
124 
125 
126     public Bitmap getBitmapFromURL(String urlString){
127         Bitmap bitmap = null;
128         InputStream is = null;
129 
130         try {
131             URL url = new URL(urlString);
132             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
133             is = new BufferedInputStream(connection.getInputStream());
134             bitmap = BitmapFactory.decodeStream(is);
135             // 最后要關閉http連接
136             connection.disconnect();
137 //            Thread.sleep(1000);// 睡眠1秒
138         } catch (IOException e) {
139             e.printStackTrace();
140         }
141 //        catch (InterruptedException e) {
142 //            e.printStackTrace();
143 //        }
144         finally {
145 
146             try {
147                 is.close();
148             } catch (IOException e) {
149                 e.printStackTrace();
150             }
151         }
152 
153         return bitmap;
154     }
155 
156 
157     // ==================使用AsyncTask====================
158     public void showImageByAsyncTask(ImageView imageView, final String urlString){
159         // 在異步請求之前,先判斷緩存中是否有,有的話就取出直接加載
160         Bitmap bitmap = getBitMapFromCache(urlString);
161         if (bitmap == null){
162             // 如果沒有,就異步任務加重
163 //            new NewsAsyncTask( urlString).execute(urlString);// 這兩個參數分別傳遞的目的地可以理解一下
164             imageView.setImageResource(R.mipmap.ic_launcher);
165         }else{
166             imageView.setImageBitmap(bitmap);
167         }
168 
169     }
170 
171     private class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{
172 
173         // 需要私有的ImageView對象和構造方法來傳遞ImageView對象
174 //        private ImageView mImageView;
175         private String mURlString;
176 
177 //        public NewsAsyncTask(ImageView imageView,String urlString){
178 //            mImageView = imageView;
179 //            mURlString = urlString;
180 //        }
181 
182         public NewsAsyncTask(String urlString){
183             mURlString = urlString;
184         }
185 
186         @Override
187         protected Bitmap doInBackground(String... strings) {
188             // 在這個方法中,完成異步下載的任務
189             String url = strings[0];
190             // 從網絡中獲取圖片
191             Bitmap bitmap = getBitmapFromURL(strings[0]);
192             if (bitmap != null){
193                 // 將網絡加載出來的圖片存儲緩存
194                 addBitmapToCache(url,bitmap);
195             }
196             return bitmap;
197         }
198 
199 
200         @Override
201         protected void onPostExecute(Bitmap bitmap) {
202             super.onPostExecute(bitmap);
203             // 然后在這個方法中設置imageViewd
204 
205             ImageView executeImageView = (ImageView) mListView.findViewWithTag(mURlString);
206             if (bitmap != null && executeImageView != null){
207                 executeImageView.setImageBitmap(bitmap);
208             }
209             // 執行完當前任務,自然要把當前Task任務remove
210             mTasks.remove(this);
211 
212 //            if (mImageView.getTag().equals(mURlString)){
213 //                mImageView.setImageBitmap(bitmap);
214 //            }
215 
216         }
217     }
218 
219     // ==================使用多線程====================
220     public void showImageByThread(ImageView imageView, final String urlString){
221 
222         mImageView = imageView;
223         mURLStr = urlString;
224 
225         new Thread(){
226             @Override
227             public void run() {
228                 super.run();
229                 Bitmap bitmap = getBitmapFromURL(urlString);
230                 // 當前線程是子線程,並不是UI主線程
231                 // 不是Message message = new Message();
232                 Message message = Message.obtain();
233                 message.obj = bitmap;
234                 handler.sendMessage(message);
235 
236             }
237         }.start();
238     }
239 }

 但是,以上的代碼還有一個不足之處,就是最開始啟動的時候,因為沒有滾動ScorllView,所以默認是沒有加載圖片的,但是我們可以做一個預加載:

完整源碼:

  1 package com.example.heyang.myapplication;
  2 
  3 import android.annotation.TargetApi;
  4 import android.content.Context;
  5 import android.os.Build;
  6 import android.support.annotation.RequiresApi;
  7 import android.view.LayoutInflater;
  8 import android.view.View;
  9 import android.view.ViewGroup;
 10 import android.widget.AbsListView;
 11 import android.widget.BaseAdapter;
 12 import android.widget.ImageView;
 13 import android.widget.ListView;
 14 import android.widget.TextView;
 15 
 16 import java.util.List;
 17 
 18 /**
 19  * Created by HeYang on 16/10/6.
 20  */
 21 
 22 public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{
 23 
 24     // 適配器對象需要傳入Bean數據集合對象,類似IOS的模型數組集合
 25     private List<NewsBean> beanList;
 26     // 然后要傳入LayoutInflater對象,用來獲取xml文件的視圖控件
 27     private LayoutInflater layoutInflater;
 28 
 29     private ImageLoader imageLoader;
 30 
 31     private boolean isFirstLoadImage;
 32 
 33     // 可見項的起止index
 34     private int mStart,mEnd;
 35     // 因為我們需要存儲起止項所有的url地址
 36     public static String[] URLS;
 37 
 38     // 創建構造方法
 39     @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
 40     public NewsAdapter(MainActivity context, List<NewsBean> data, ListView listView){
 41 
 42         isFirstLoadImage = true;
 43 
 44         beanList = data;
 45         layoutInflater = LayoutInflater.from(context);// 這個context對象就是Activity對象
 46         imageLoader = new ImageLoader(listView);
 47 
 48         // 將模型數組中的url字符串單獨存儲在靜態數組中
 49         int dataSize = data.size();
 50         URLS = new String[dataSize];
 51         for (int i = 0; i < dataSize; i++) {
 52             URLS[i] = data.get(i).newsIconURL;
 53         }
 54 
 55         // 已經要記得注冊
 56         listView.setOnScrollListener(this);
 57     }
 58 
 59     @Override
 60     public int getCount() {
 61         return beanList.size();
 62     }
 63 
 64     @Override
 65     public Object getItem(int i) {
 66         // 因為beanList是數組,通過get訪問對應index的元素
 67         return beanList.get(i);
 68     }
 69 
 70     @Override
 71     public long getItemId(int i) {
 72         return i;
 73     }
 74 
 75 
 76     @Override
 77     public View getView(int i, View view, ViewGroup viewGroup) {
 78         ViewHolder viewHolder = null;
 79         if (view == null){
 80             viewHolder = new ViewHolder();
 81             // 每一個View都要和layout關聯
 82             view = layoutInflater.inflate(R.layout.item_layout,null);
 83             // 在R.layout.item_layout中有三個控件對象
 84             // 現在全部傳遞給view對象了
 85             viewHolder.tvTitle = (TextView) view.findViewById(R.id.tv_title);
 86             viewHolder.tvContent = (TextView) view.findViewById(R.id.tv_content);
 87             viewHolder.ivIcon = (ImageView) view.findViewById(R.id.iv_icon);
 88 
 89             view.setTag(viewHolder);
 90 
 91         }else {
 92 
 93             // 重復利用,但是由於里面的View展示的數據顯然需要重新賦值
 94             viewHolder = (ViewHolder) view.getTag();
 95         }
 96         viewHolder.tvTitle.setText(beanList.get(i).newsTitle);
 97         viewHolder.tvContent.setText(beanList.get(i).newsContent);
 98         // 先默認加載系統圖片
 99         viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);  // 類似加載占位圖片
100         viewHolder.ivIcon.setTag(beanList.get(i).newsIconURL);
101         // 將ImageView對象和URLSting對象傳入進去
102 //        new ImageLoader().showImageByThread(viewHolder.ivIcon,beanList.get(i).newsIconURL);
103         imageLoader.showImageByAsyncTask(viewHolder.ivIcon,beanList.get(i).newsIconURL);
104         return view;
105     }
106 
107     @Override
108     public void onScrollStateChanged(AbsListView absListView, int i) {
109         // SCROLL_STATE_IDLE :  滾動結束
110         if (i == SCROLL_STATE_IDLE){
111             // 加載可見項
112             imageLoader.loadImages(mStart,mEnd);
113         }else{
114             // 停止所有任務
115             imageLoader.cancelAllTask();
116         }
117     }
118 
119     @Override
120     public void onScroll(AbsListView absListView, int i, int i1, int i2) {
121         // i是第一個可見元素 i1是當前可見元素的長度
122         mStart = i;
123         mEnd = i + i1;
124         // 第一次預加載可見項
125         if (isFirstLoadImage && i1 > 0){
126             imageLoader.loadImages(mStart,mEnd);
127             isFirstLoadImage = false;
128         }
129     }
130 
131     // 最后需要一個匿名內部類來創建一個臨時緩存View的對象
132     class ViewHolder{
133         public TextView tvContent,tvTitle;
134         public ImageView ivIcon;
135     }
136 }

就這樣完成了異步加載的完整最優化的功能:

完整源碼下載地址:

 https://github.com/HeYang123456789/Android-ListView-AsyncLoader-Demo

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM