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

 

posted @ 2016-10-06 15:39  何杨  阅读(4914)  评论(0编辑  收藏  举报