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类,用于声明一些属性和方法。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 }
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适配器的代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 }
5:在加载图片的时候,我们需要避免因网罗或者缓存的原因等造成的程序图片缓慢,卡的现象声明一个ImageLoad类,在其中对图片进行处理。
我们通过使用继承AsyncTask的方法来实现图片的异步加载
首先我们需要创建一个方法:showImageByAsyncTask,并且传入值为ImageView以及图片的url。其次新建一个类继承AsyncTask,这个类为匿名内部类,
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 }
6:MainActivity:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
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 }
仅仅需要修改NewsAdapter中为图片控件赋值的代码即可:
1
2
|
//new ImageLoader().showImageByThread(viewHolder.ivIcon,url); //图片id,图片的链接 --->>使用多线程的方法
new
ImageLoader().showImageByAsyncTask(viewHolder.ivIcon,url);
//使用继承AsyncTask的方式实现图片的异步加载
|
至此,我们为控件赋值以及异步加载数据的功能已经实现,我们来看下效果: