因為移動端軟件開發思維模式或者說是開發的架構其實是不分平台和編程語言的,就拿安卓和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
