Android异步加载
一、为什么要使用异步加载?
1.Android是单线程模型
2.耗时操作阻碍UI线程
二、异步加载最常用的两种方式
1.多线程、线程池
2.AsyncTask
三、实现ListView图文混排
3-1 实现读取网页中的json数据到ListView中 (图片首先为默认图片)
3.1.1:主布局只有一个ListView和一个listView_item的布局
3.1.2:网页json数据的链接(http://www.imooc.com/api/teacher?type=4&num=30),打开后为json数据,开发者可以通过使用Json格式化工具进行清楚的查看json数据的信息
3.1.3:书写解析网页JSON数据的代码
package cn.edu.bzu.async_listview; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.net.MalformedURLException; import java.net.URL; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.widget.ListView; public class MainActivity extends Activity { private ListView mListView; private static String URL="http://www.imooc.com/api/teacher?type=4&num=30"; //慕课网提供的api链接 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView=(ListView) findViewById(R.id.lv_main); new NewAsyncTask().execute(URL); } /** *实现网络的异步访问 */ class NewAsyncTask extends AsyncTask<String, Void, List<NewsBean> >{ //参数:params:传入值 progress :进程 Result:返回值 @Override protected List<NewsBean> doInBackground(String... params) { return getJsonData(params[0]); //得到从url读取的JSON数据 } @Override protected void onPostExecute(List<NewsBean> newsBean) { // 将生成的newsBean设置给ListView super.onPostExecute(newsBean); NewsAdapter adapter=new NewsAdapter(MainActivity.this, newsBean);//创建适配器对象 mListView.setAdapter(adapter); } } /** * 将url对应的json数据转换为我们所封装的NewsBean对象 * @param url * @return newsList */ private List<NewsBean> getJsonData(String url) { List<NewsBean> newsBeanList=new ArrayList<NewsBean>(); try { String jsonString=readStream(new URL(url).openStream()); //此句功能与url.openConnection().getInputStream()相同,可根据URL直接联网获取数据,返回值类型 InputStream; //Log.d("json",jsonString ); 打印读取的json信息 //解析json数据 JSONObject jsonObject; NewsBean newsBean; //用于封装jsonObject jsonObject=new JSONObject(jsonString); //json数据添加到jsonObject中 JSONArray jsonArray=jsonObject.getJSONArray("data"); //取出json中的data数据,data为一个数组类型 for(int i=0;i<jsonArray.length();i++){ //取出data中的数据 jsonObject=jsonArray.getJSONObject(i); newsBean=new NewsBean(); newsBean.imgIconUrl=jsonObject.getString("picSmall"); newsBean.newsTitle=jsonObject.getString("name"); newsBean.newsContent=jsonObject.getString("description"); newsBeanList.add(newsBean); } } catch (IOException e) { e.printStackTrace(); } catch (JSONException e) { e.printStackTrace(); } return newsBeanList; } /** * 通过inputStream解析网页所返回的数据 * @param is * @return result */ private String readStream(InputStream is){ InputStreamReader isr; String result=""; try { String line=""; //每行的数据 isr=new InputStreamReader(is,"utf-8"); //字节流转换为字符流 BufferedReader br=new BufferedReader(isr); //将字符流以buffer的形式读取出来 while((line=br.readLine())!=null){ result+=line; //拼接到result中 } } catch (UnsupportedEncodingException e) { e.printStackTrace(); }catch (IOException e) { e.printStackTrace(); } return result; } }
思路解析:
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"/>
3-2 实现在ListView中添加网络中图片
&由于加载图片是一种耗时操作,所以我们可以通过新建Thread的方法或者继承AsyncTask来实现
3.2.1:我们通过新线程的方式来实现加载网络图片,新建ImageLoader.java
package cn.edu.bzu.async_listview; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import android.os.Message; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Handler; import android.widget.ImageView; /** * 用于处理图片的加载 * @author monster * */ public class ImageLoader { private ImageView mImageView; private String mUrl; /** * UI主线程 */ private Handler mHandler=new Handler(){ public void handleMessage(Message msg) { super.handleMessage(msg); //通过设置tag属性避免缓存图片对正确图片的影响 if(mImageView.getTag().equals(mUrl)){ mImageView.setImageBitmap((Bitmap) msg.obj); } }; }; /** * 通过多线程的方式加载图片 * @param imageView * @param url */ public void showImageByThread(ImageView imageView,final String url){ mImageView=imageView; //将ImageView保存进成员变量中 mUrl=url; new Thread(){ @Override public void run() { super.run(); Bitmap bitmap=getBitmapFromURL(url); Message message=Message.obtain(); message.obj=bitmap; mHandler.sendMessage(message); //将内容发送到Handle线程中 } }.start(); } /** * 通过url得到bitmap * @param urlString * @return bitmap */ public Bitmap getBitmapFromURL(String urlString){ Bitmap bitmap; InputStream is = null; try { URL url=new URL(urlString); HttpURLConnection connection=(HttpURLConnection) url.openConnection(); //打开链接 //注意是:HttpURLConnection而不是HttpsURLConnection is=new BufferedInputStream(connection.getInputStream()); bitmap=BitmapFactory.decodeStream(is); //将这个流转换为bitmap connection.disconnect(); //资源释放 return bitmap; } catch (java.io.IOException e) { e.printStackTrace(); }finally{ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } }
思路解析:
1)新建一个线程来实现加载图片
2)创建加载图片的方法,方法的参数为图片的url,这个url可以通过解析刚才的json数据得到
HttpURLConnection connection=(HttpURLConnection) url.openConnection(); //打开链接 //注意是:HttpURLConnection而不是HttpsURLConnection通过这条语句,将连接转化成流,然后得到流,最后将流转换为bitmap对象
3)得到bitmap对象后,我们新建Handler线程,在这个线程中进行图片的更换,由于bitmap在我们的新线程中,所以我们通过handler的消息传递进行将bitmap对象传入到主线程中去
4)由于ListView的缓存机制,所以我们通过在适配器为图片设置tag的方法从而实现图片的正确加载,避免导致图片的来回替换
在Handler中,我们通过通过设置判断tag属性,来判断图片的url是否相等
附录:ListView适配器的代码:
package cn.edu.bzu.async_listview; import java.util.List; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; public class NewsAdapter extends BaseAdapter { private List<NewsBean> mList; private LayoutInflater mInflater; public NewsAdapter(Context context,List<NewsBean> data){ mList=data; mInflater=LayoutInflater.from(context); } @Override public int getCount() { return mList.size(); } @Override public Object getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int positon, View convertView, ViewGroup parent) { ViewHolder viewHolder=null; if(convertView==null){ viewHolder=new ViewHolder(); convertView=mInflater.inflate(R.layout.listview_item, null); //布局转化为视图 viewHolder.ivIcon=(ImageView) convertView.findViewById(R.id.iv_icon); viewHolder.tvTitle=(TextView) convertView.findViewById(R.id.tv_title); viewHolder.tv_Content=(TextView) convertView.findViewById(R.id.tv_content); convertView.setTag(viewHolder); }else{ viewHolder=(ViewHolder) convertView.getTag(); } viewHolder.ivIcon.setImageResource(R.drawable.ic_launcher); String url=mList.get(positon).imgIconUrl; viewHolder.ivIcon.setTag(url); new ImageLoader().showImageByThread(viewHolder.ivIcon,url); //图片id,图片的链接 viewHolder.tvTitle.setText(mList.get(positon).newsTitle); viewHolder.tv_Content.setText(mList.get(positon).newsContent); return convertView; } class ViewHolder{ public TextView tvTitle,tv_Content; public ImageView ivIcon; } }
3.2.2 :我们通过使用继承AsyncTask的方法来实现图片的异步加载
首先我们需要创建一个方法:showImageByAsyncTask,并且传入值为ImageView以及图片的url
其次新建一个类继承AsyncTask,这个类为匿名内部类,为ImageLoder中的类:
private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap>{ private ImageView mImageView; private String mUrl; public NewsAsyncTask(ImageView imageView,String url){ mImageView=imageView; mUrl=url; } @Override protected Bitmap doInBackground(String... params) { return getBitmapFromURL(params[0]); } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); if(mImageView.getTag().equals(mUrl)){ mImageView.setImageBitmap(bitmap); } } }
然后仅仅需要修改NewsAdapter中为图片控件赋值的代码即可:
//new ImageLoader().showImageByThread(viewHolder.ivIcon,url); //图片id,图片的链接 --->>使用多线程的方法 new ImageLoader().showImageByAsyncTask(viewHolder.ivIcon,url); //使用继承AsyncTask的方式实现图片的异步加载
至此,我们为控件赋值以及异步加载数据的功能已经实现,我们来看下效果:
四、LruCache缓存机制(使用内存空间换取效率)
问题分析:上述的程序中存在一个问题,问题是当用户每次刷新的时候,都需要从网络中读取数据并且进行加载,所以这样增加了用户的流量费用,不利于用户的使用
那么如何提高用户的体验??---->>>>使用缓存
Lru算法:
Lru:Least Recently Used 近期最少使用算法
Android提供了LruCache类来实现这个缓存算法
思路:1.声明LruCache对象
2.在构造方法中实现创建LruCache对象,在这里需要实现它的一个内部方法,用于告诉系统图片的大小,在此之前,你需要得到运行的最大内存,然后用最大内存的一部分
3.添加两个方法:一个将图片加入到缓存,一个从缓存中读取图片
---->>将图片加入到缓存,从网络中读取数据,如果图片不为空,则加入到缓存
----->>从缓存中读取图片,通过键值对的形式读取图片
4.在异步加载图片的使用,首先需要从缓存中读取图片,如果图片为空的话,加载图片,加载出来的图片添加到缓存中
5.在adapter中创建ImageLoader对象,实现添加适配器的时候自动加载缓存机制
附:代码
package cn.edu.bzu.async_listview; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import android.os.AsyncTask; import android.os.Message; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Handler; import android.util.LruCache; import android.widget.ImageView; /** * 用于处理图片的加载 * @author monster * */ public class ImageLoader { private ImageView mImageView; private String mUrl; private LruCache<String, Bitmap> mCaches ; //用户图片的缓存 public ImageLoader(){ int maxMemory=(int) Runtime.getRuntime().maxMemory(); //获取最大可用内存 int cacheSize=maxMemory/4; //缓存的大小 mCaches=new LruCache<String,Bitmap>(cacheSize){ @Override protected int sizeOf(String key, Bitmap value) { //在每次存入缓存的时候调用 return value.getByteCount(); //告诉系统,存入的图片的大小 } }; } /** * 把bitmap加入到缓存中 * @param url * @param bitmap */ public void addBitmapToCache(String url,Bitmap bitmap){ if(getBitmapFromCache(url)==null){ mCaches.put(url, bitmap); } } /** * 把图片从缓存中取出来 * @param url * @return bitmap */ public Bitmap getBitmapFromCache(String url){ return mCaches.get(url); } /** * UI主线程 */ private Handler mHandler=new Handler(){ public void handleMessage(Message msg) { super.handleMessage(msg); //通过设置tag属性避免缓存图片对正确图片的影响 if(mImageView.getTag().equals(mUrl)){ mImageView.setImageBitmap((Bitmap) msg.obj); } }; }; /** * 通过多线程的方式加载图片 * @param imageView * @param url */ public void showImageByThread(ImageView imageView,final String url){ mImageView=imageView; //将ImageView保存进成员变量中 mUrl=url; new Thread(){ @Override public void run() { super.run(); Bitmap bitmap=getBitmapFromURL(url); Message message=Message.obtain(); message.obj=bitmap; mHandler.sendMessage(message); //将内容发送到Handle线程中 } }.start(); } /** * 通过url得到bitmap * @param urlString * @return bitmap */ public Bitmap getBitmapFromURL(String urlString){ Bitmap bitmap; InputStream is = null; try { URL url=new URL(urlString); HttpURLConnection connection=(HttpURLConnection) url.openConnection(); //打开链接 //注意是:HttpURLConnection而不是HttpsURLConnection is=new BufferedInputStream(connection.getInputStream()); bitmap=BitmapFactory.decodeStream(is); //将这个流转换为bitmap connection.disconnect(); //资源释放 return bitmap; } catch (java.io.IOException e) { e.printStackTrace(); }finally{ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } /** * 通过AsyncTask的方式异步加载图片 * @param imageView * @param url */ public void showImageByAsyncTask(ImageView imageView,String url){ Bitmap bitmap=getBitmapFromCache(url); //从缓存中取出图片 if(bitmap==null){ new NewsAsyncTask(imageView,url).execute(url); }else{ imageView.setImageBitmap(bitmap); } } private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap>{ private ImageView mImageView; private String mUrl; public NewsAsyncTask(ImageView imageView,String url){ mImageView=imageView; mUrl=url; } /** * 从网络中获取图片,如果图片已经下载,则加入到缓存 */ @Override protected Bitmap doInBackground(String... params) { String url=params[0]; Bitmap bitmap=getBitmapFromURL(url); if(bitmap!=null){ addBitmapToCache(url, bitmap); } return bitmap ; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); if(mImageView.getTag().equals(mUrl)){ mImageView.setImageBitmap(bitmap); } } } }
五:源代码下载:
https://github.com/monsterLin/Async_ListView