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

   

原文地址:https://www.cnblogs.com/boy1025/p/4543801.html