Android 异步加载

Android的Lazy Load主要体现在网络数据(图片)异步加载、数据库查询、复杂业务逻辑处理以及费时任务操作导致的异步处理等方面。在介绍Android开发过程中,异步处理这个常见的技术问题之前,我们简单回顾下Android开发过程中需要注意的几个地方。

Android应用开发过程中必须遵循单线程模型(Single Thread Model)的原则。因为Android的UI操作并不是线程安全的,所以涉及UI的操作必须在UI线程中完成。但是并非所有的操作都能在主线程中进行,Google工程师在设计上约定,Android应用在5s内无响应的话会导致ANR(Application Not Response),这就要求开发者必须遵循两条法则:1、不能阻塞UI线程,2、确保只在UI线程中访问Android UI工具包。于是,开启子线程进行异步处理的技术方案应运而生。

本文以自定义ListView,异步加载网络图片示例,总结了Android开发过程中,常用的三种异步加载的技术方案。

思路解析:

    1) 一个内部类继承AsyncTask,书写未实现的方法,其中在方法中有一个doBackground()方法,在其方法中书写得到Json数据的方法

   2) 书写通过inputStream解析网页所返回的数据,只有拿到网页中的json数据才能实现解析的操作

   3)将url对应的json数据转换为我们所封装的NewsBean对象,在这个方法中,我们通过第二步拿到了json数据,然后进行json数据的解析,并且封装到实体类对象中,这样你的实体类中就有解析的json数据了

  4)创建ListView的适配器

  5)在继承AsyncTask的类中书写onPostExecute()方法,在这个方法中,实现绑定适配器,加载数据源的操作

  6)在onCreate方法中执行这个异步操作:new NewAsyncTask().execute(URL);并且传入url地址

  7)在清单文件中添加联网权限 <use-permission  android:name="android.permission.INTERNET"/>

1:相关资源:activity_main.xml:

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="wrap_content">
 5     <ListView 
 6         android:id="@+id/lv_main"
 7         android:layout_width="match_parent"
 8         android:layout_height="match_parent"/>
 9    
10     </LinearLayout>

2:子布局:

 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     xmlns:tools="http://schemas.android.com/tools"
 3     android:layout_width="match_parent"
 4     android:layout_height="wrap_content"
 5     android:orientation="horizontal" >
 6     
 7     <ImageView 
 8         android:id="@+id/iv_icon"
 9         android:layout_width="64dp"
10         android:layout_height="64dp"
11         android:src="@drawable/ic_launcher"
12         android:padding="5dp"
13         android:contentDescription="@string/iv_icon_text"/>
14     
15     <LinearLayout 
16         android:layout_width="match_parent"
17         android:layout_height="match_parent"
18         android:orientation="vertical"
19         android:paddingLeft="4dp"
20         android:gravity="center">
21         <TextView 
22             android:id="@+id/tv_title"
23             android:layout_width="match_parent"
24             android:layout_height="wrap_content"
25             android:text="@string/title_text"
26             android:maxLines="1"
27             android:textSize="15sp"/>
28         <TextView 
29             android:id="@+id/tv_content"
30             android:layout_width="match_parent"
31             android:layout_height="wrap_content"
32             android:text="@string/content_text"
33             android:maxLines="3"
34             android:textSize="10sp"/>
35     </LinearLayout>
36     </LinearLayout>

3:我们需要新建一个Bean类,用于声明一些属性和方法。

 1 package cn.edu.bzu.async_listview;
 2 
 3 public class NewsBean {
 4     public String imgIconUrl;   //图片的URL
 5     public String newsTitle;     //课程的标题
 6     public String newsContent;   //课程的内容
 7     
 8     public NewsBean(String imgIconUrl, String newsTitle, String newsContent) {
 9         super();
10         this.imgIconUrl = imgIconUrl;
11         this.newsTitle = newsTitle;
12         this.newsContent = newsContent;
13     }
14 
15     public NewsBean() {
16         super();
17     }
18 
19     public String getImgIconUrl() {
20         return imgIconUrl;
21     }
22 
23     public void setImgIconUrl(String imgIconUrl) {
24         this.imgIconUrl = imgIconUrl;
25     }
26 
27     public String getNewsTitle() {
28         return newsTitle;
29     }
30 
31     public void setNewsTitle(String newsTitle) {
32         this.newsTitle = newsTitle;
33     }
34 
35     public String getNewsContent() {
36         return newsContent;
37     }
38 
39     public void setNewsContent(String newsContent) {
40         this.newsContent = newsContent;
41     }
42     
43     
44 }
View Code

4:创建适配器,获取数据。

思路解析:

    1)新建一个线程来实现加载图片

    2)创建加载图片的方法,方法的参数为图片的url,这个url可以通过解析刚才的json数据得到

1
HttpURLConnection connection=(HttpURLConnection) url.openConnection(); //打开链接   //注意是:HttpURLConnection而不是HttpsURLConnection

     通过这条语句,将连接转化成流,然后得到流,最后将流转换为bitmap对象

    3)得到bitmap对象后,我们新建Handler线程,在这个线程中进行图片的更换,由于bitmap在我们的新线程中,所以我们通过handler的消息传递进行将bitmap对象传入到主线程中去

   4)由于ListView的缓存机制,所以我们通过在适配器为图片设置tag的方法从而实现图片的正确加载,避免导致图片的来回替换

    在Handler中,我们通过通过设置判断tag属性,来判断图片的url是否相等

    附录:ListView适配器的代码:

 1 package cn.edu.bzu.async_listview;
 2 
 3 import java.util.List;
 4 
 5 import android.content.Context;
 6 import android.view.LayoutInflater;
 7 import android.view.View;
 8 import android.view.ViewGroup;
 9 import android.widget.BaseAdapter;
10 import android.widget.ImageView;
11 import android.widget.TextView;
12 
13 public class NewsAdapter extends BaseAdapter {
14     private List<NewsBean> mList;
15     private LayoutInflater mInflater;//把layout布作为我们的每一个item
16     private ImageLoader mImageLoader;
17     //添加一个构造方法
18     public NewsAdapter(Context context,List<NewsBean> data){
19         mList=data;//将数组映射过来
20         mInflater=LayoutInflater.from(context);
21         mImageLoader=new ImageLoader();
22     }
23     @Override
24     public int getCount() {
25         return mList.size();
26     }
27 
28     @Override
29     public Object getItem(int position) {
30         return mList.get(position);//直接返回Position
31     }
32 
33     @Override
34     public long getItemId(int position) {
35         return position;
36     }
37     //最重要的:
38     public View getView(int positon, View convertView, ViewGroup parent) {
39         ViewHolder viewHolder=null;
40         if(convertView==null){
41             viewHolder=new ViewHolder();
42             convertView=mInflater.inflate(R.layout.listview_item, null); //布局转化为视图
43             //对其中元素进行初始化
44             viewHolder.ivIcon=(ImageView) convertView.findViewById(R.id.iv_icon);
45             viewHolder.tvTitle=(TextView) convertView.findViewById(R.id.tv_title);
46             viewHolder.tv_Content=(TextView) convertView.findViewById(R.id.tv_content);
47             convertView.setTag(viewHolder);//设置标贴
48         }else{
49             viewHolder=(ViewHolder) convertView.getTag();
50         }
51         viewHolder.ivIcon.setImageResource(R.drawable.ic_launcher);
52         
53         String url=mList.get(positon).imgIconUrl;
54         viewHolder.ivIcon.setTag(url);
55         //new ImageLoader().showImageByThread(viewHolder.ivIcon,url);  //图片id,图片的链接
56         mImageLoader.showImageByAsyncTask(viewHolder.ivIcon,url);  //使用继承AsyncTask的方式实现图片的异步加载
57         viewHolder.tvTitle.setText(mList.get(positon).newsTitle);
58         viewHolder.tv_Content.setText(mList.get(positon).newsContent);
59         
60         return convertView;
61     }
62     class ViewHolder{
63         public TextView tvTitle,tv_Content;
64         public ImageView ivIcon;
65     }
66 }
View Code

5:在加载图片的时候,我们需要避免因网罗或者缓存的原因等造成的程序图片缓慢,卡的现象声明一个ImageLoad类,在其中对图片进行处理。

我们通过使用继承AsyncTask的方法来实现图片的异步加载

首先我们需要创建一个方法:showImageByAsyncTask,并且传入值为ImageView以及图片的url。其次新建一个类继承AsyncTask,这个类为匿名内部类,

  1 package cn.edu.bzu.async_listview;
  2 
  3 import java.io.BufferedInputStream;
  4 import java.io.IOException;
  5 import java.io.InputStream;
  6 import java.net.HttpURLConnection;
  7 import java.net.URL;
  8 
  9 import android.os.AsyncTask;
 10 import android.os.Message;
 11 
 12 
 13 import android.graphics.Bitmap;
 14 import android.graphics.BitmapFactory;
 15 import android.os.Handler;
 16 import android.util.LruCache;
 17 import android.widget.ImageView;
 18 
 19 /**
 20  * 用于处理图片的加载
 21  *
 22  */
 23 public class ImageLoader {
 24     private ImageView mImageView;
 25     private String mUrl;
 26     private LruCache<String, Bitmap> mCaches ;  //用户图片的缓存
 27     
 28     public ImageLoader(){
 29         int maxMemory=(int) Runtime.getRuntime().maxMemory();  //获取最大可用内存
 30         int cacheSize=maxMemory/4;  //缓存的大小
 31         mCaches=new LruCache<String,Bitmap>(cacheSize){
 32             @Override
 33             protected int sizeOf(String key, Bitmap value) {
 34                 //在每次存入缓存的时候调用
 35                 return value.getByteCount(); //告诉系统,存入的图片的大小
 36             }
 37         };
 38     }
 39     /**
 40      * 把bitmap加入到缓存中
 41      * @param url
 42      * @param bitmap
 43      */
 44     public void addBitmapToCache(String url,Bitmap bitmap){
 45         if(getBitmapFromCache(url)==null){
 46             mCaches.put(url, bitmap);
 47         }
 48     }
 49     
 50     /**
 51      * 把图片从缓存中取出来
 52      * @param url
 53      * @return bitmap
 54      */
 55     public Bitmap getBitmapFromCache(String url){
 56         return mCaches.get(url);
 57     }
 58     /**
 59      * UI主线程
 60      */
 61     private Handler mHandler=new Handler(){
 62         public void handleMessage(Message msg) {
 63             super.handleMessage(msg); 
 64             //通过设置tag属性避免缓存图片对正确图片的影响
 65             if(mImageView.getTag().equals(mUrl)){
 66                 mImageView.setImageBitmap((Bitmap) msg.obj);
 67             }
 68         };
 69     };
 70     /**
 71      * 通过多线程的方式加载图片
 72      * @param imageView
 73      * @param url
 74      */
 75     public void showImageByThread(ImageView imageView,final String url){
 76         mImageView=imageView; //将ImageView保存进成员变量中
 77         mUrl=url;
 78         new Thread(){
 79             @Override
 80             public void run() {
 81                 super.run();
 82                 Bitmap bitmap=getBitmapFromURL(url);
 83                 Message message=Message.obtain();
 84                 message.obj=bitmap;
 85                 mHandler.sendMessage(message); //将内容发送到Handle线程中
 86             }
 87         }.start();
 88     }
 89     /**
 90      * 通过url得到bitmap
 91      * @param urlString
 92      * @return bitmap
 93      */
 94     public Bitmap getBitmapFromURL(String urlString){
 95         Bitmap bitmap;
 96         InputStream is = null;
 97         try {
 98             URL url=new URL(urlString);
 99             HttpURLConnection connection=(HttpURLConnection) url.openConnection(); //打开链接   //注意是:HttpURLConnection而不是HttpsURLConnection
100             is=new BufferedInputStream(connection.getInputStream());
101             bitmap=BitmapFactory.decodeStream(is); //将这个流转换为bitmap
102             connection.disconnect(); //资源释放
103             return bitmap;
104         } catch (java.io.IOException e) {
105             e.printStackTrace();
106         }finally{
107             try {
108                 is.close();
109             } catch (IOException e) {
110                 e.printStackTrace();
111             }
112         }
113         return null;
114     }
115     /**
116      * 通过AsyncTask的方式异步加载图片
117      * @param imageView
118      * @param url
119      */
120     public void showImageByAsyncTask(ImageView imageView,String url){
121         Bitmap bitmap=getBitmapFromCache(url);  //从缓存中取出图片
122         if(bitmap==null){
123             new NewsAsyncTask(imageView,url).execute(url);    
124         }else{
125             imageView.setImageBitmap(bitmap);
126         }
127         
128     }
129     private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap>{
130         private ImageView mImageView;
131         private String mUrl;
132         public NewsAsyncTask(ImageView imageView,String url){
133             mImageView=imageView;
134             mUrl=url;
135         }
136         /**
137          * 从网络中获取图片,如果图片已经下载,则加入到缓存
138          */
139         @Override
140         protected Bitmap doInBackground(String... params) {
141             String url=params[0];
142             Bitmap bitmap=getBitmapFromURL(url);
143             if(bitmap!=null){
144                 addBitmapToCache(url, bitmap);
145             }
146             return bitmap ;
147         }
148         @Override
149         protected void onPostExecute(Bitmap bitmap) {
150             super.onPostExecute(bitmap);
151             if(mImageView.getTag().equals(mUrl)){
152                  mImageView.setImageBitmap(bitmap);    
153             }
154         }
155     }
156 }
View Code

6:MainActivity:

  1 package cn.edu.bzu.async_listview;
  2 
  3 import java.io.BufferedReader;
  4 import java.io.IOException;
  5 import java.io.InputStream;
  6 import java.io.InputStreamReader;
  7 import java.io.UnsupportedEncodingException;
  8 import java.util.ArrayList;
  9 import java.util.List;
 10 import java.net.MalformedURLException;
 11 import java.net.URL;
 12 
 13 import org.json.JSONArray;
 14 import org.json.JSONException;
 15 import org.json.JSONObject;
 16 
 17 import android.app.Activity;
 18 import android.os.AsyncTask;
 19 import android.os.Bundle;
 20 import android.util.Log;
 21 import android.widget.ListView;
 22 
 23 public class MainActivity extends Activity {
 24 
 25     private ListView mListView;
 26     private static String URL="http://www.imooc.com/api/teacher?type=4&num=30";  //慕课网提供的api链接
 27     @Override
 28     protected void onCreate(Bundle savedInstanceState) {
 29         super.onCreate(savedInstanceState);
 30         setContentView(R.layout.activity_main);
 31         mListView=(ListView) findViewById(R.id.lv_main);
 32         new NewAsyncTask().execute(URL);
 33     }
 34     
 35     /**
 36      *实现网络的异步访问
 37      */
 38     class NewAsyncTask extends AsyncTask<String, Void, List<NewsBean> >{ //参数:params:传入值    progress :进程   Result:返回值 
 39         
 40         @Override
 41         protected List<NewsBean> doInBackground(String... params) {
 42             return getJsonData(params[0]);  //得到从url读取的JSON数据
 43         } 
 44         @Override
 45             protected void onPostExecute(List<NewsBean> newsBean) {
 46                 // 将生成的newsBean设置给ListView
 47                 super.onPostExecute(newsBean);
 48                 NewsAdapter adapter=new NewsAdapter(MainActivity.this, newsBean);//创建适配器对象
 49                 mListView.setAdapter(adapter);
 50             }
 51     }
 52         /**
 53          * 将url对应的json数据转换为我们所封装的NewsBean对象
 54          * @param url
 55          * @return newsList
 56          */
 57     private List<NewsBean> getJsonData(String url) {
 58         List<NewsBean> newsBeanList=new ArrayList<NewsBean>();
 59         try {
 60             String jsonString=readStream(new URL(url).openStream()); //此句功能与url.openConnection().getInputStream()相同,可根据URL直接联网获取数据,返回值类型 InputStream;
 61             //Log.d("json",jsonString ); // 打印读取的json信息
 62             //解析json数据
 63             JSONObject jsonObject; 
 64             NewsBean newsBean; //用于封装jsonObject
 65             
 66             jsonObject=new JSONObject(jsonString);  //json数据添加到jsonObject中
 67             JSONArray jsonArray=jsonObject.getJSONArray("data"); //取出json中的data数据,data为一个数组类型
 68             for(int i=0;i<jsonArray.length();i++){
 69                 //取出data中的数据
 70                 jsonObject=jsonArray.getJSONObject(i);
 71                 newsBean=new NewsBean();
 72                 //获取图片以及title,content
 73                 newsBean.imgIconUrl=jsonObject.getString("picSmall");
 74                 newsBean.newsTitle=jsonObject.getString("name");
 75                 newsBean.newsContent=jsonObject.getString("description");
 76                 newsBeanList.add(newsBean);//把newsBean对象添加到list集合中。
 77             }
 78             
 79         } catch (IOException e) {
 80             e.printStackTrace();
 81         } catch (JSONException e) {
 82             e.printStackTrace();
 83         }
 84         return newsBeanList;
 85     }
 86     
 87     /**
 88      * 通过inputStream解析网页所返回的数据
 89      * @param is
 90      * @return  result
 91      */
 92     private String readStream(InputStream is){
 93          InputStreamReader isr;  
 94          String result="";
 95          try {
 96              String line=""; //每行的数据
 97             isr=new InputStreamReader(is,"utf-8");  //字节流转换为字符流
 98             BufferedReader br=new BufferedReader(isr);  //将字符流以buffer的形式读取出来
 99             while((line=br.readLine())!=null){
100                 result+=line;  //拼接到result中
101             }
102         } catch (UnsupportedEncodingException e) {
103             e.printStackTrace();
104         }catch (IOException e) {
105             e.printStackTrace();
106         }
107          return result;
108     }
109                                                                  
110     }
View Code

仅仅需要修改NewsAdapter中为图片控件赋值的代码即可:

1
2
//new ImageLoader().showImageByThread(viewHolder.ivIcon,url);  //图片id,图片的链接  --->>使用多线程的方法
new ImageLoader().showImageByAsyncTask(viewHolder.ivIcon,url);  //使用继承AsyncTask的方式实现图片的异步加载

  至此,我们为控件赋值以及异步加载数据的功能已经实现,我们来看下效果:

原文地址:https://www.cnblogs.com/xuyinghui/p/4665477.html