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