为什么要异步加载:
为了用户体验,避免卡顿
Android系统要求使用异步加载,耗时操作会阻塞UI线程
异步加载的常用的方式:
多线程/线程池
AsyncTask
以下以加载网络图片为示例
在主类之外声明一个单独的MyBean类:
class MyBean{
public String MyIconUrl;
public String MyTitle;
public String MyContent;
}
和一个MyAdapter类:
public class MyAdapter extends BaseAdapter{
private List<MyBean> mList;
private LayoutInflater mInflater;
public MyAdapter(Context context, List<MyBean> data){
mList = data;
mInflater = LayoutInflater.from(context);
}
public int getCount(){
return mList.size();
}
public Object getItem(int position){
return mList.get(position);
}
public long getItemId(int position){
return position;
}
public View getView(int position, View convertView, ViewGroup parent){
ViewHolder viewHolder = null;
if(convertView == null){
viewHolder = new ViewHolder();
convertView = mInflater.inflate(R.layout.item_layout, null);
viewHolder.ivIcon = (ImageView)convertView.findViewById(R.id.iv_icon);
viewHolder.tvTitle = (TextView)convertView.findViewById(R.id.tv_title);
viewHolder.tvContent = (TextView)convertView.findViewById(R.id.tv_content);
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder)convertView.getTag();
}
viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);//这是使用默认的ic_launcher作为图标
//-----------------下述是调用异步加载图片的内容作为图标
new ImageLoader().showImageByThread(viewHolder.ivIcon, mList.get(position).myIconUrl);
//-----------------
viewHolder.tvTitle.setText(mList.get(position).MyTitle);
viewHolder.tvContent.setText(mList.get(position).MyContent);
return convertView;
}
class ViewHolder{
public TextView tvTitle, tvContent;
public ImageView ivIcon;
}
}
接下来是主类中的方法:
private ListView mListView;
private static String URL = "http://www.xxxxxxxxx.com/xxx/xxx/xxx";
protected void OnCreate(Bundle savedInstanceState){
super.OnCreate(savedInstanceState);
setContentView(R.Layout.activity_main);
mListView = (ListView)findViewById(R.id.lv_main);
new MyAsyncTask().execute(URL);
}
private List<MyBean> getJsonData(String url){
//将Url的Json数据转化为所封装的MyBean对象
List<MyBean> myBeanList = new ArrayList<>();
try{
String jsonString = readStream(new URL(url).openStream());
//上述语句功能与url.openConnection().getInputStream()相同,
//可根据URL直接联网获取网络数据,简单粗暴,返回值类型为InputStream
JSONObject jsonObject;
MyBean myBean;
try{
jsonObject = new jsonObject(jsonString);
JSONArray jsonArray = jsonObject.getJSONArray("data");
for(int i =0;i<jsonArray.length();i++){
jsonObject = jsonArray.getJSONObject(i);
myBean = new MyBean();
myBean.MyIconUrl = jsonObject.getString("picSmall");
myBean.MyTitle = jsonObject.getString("name");
myBean.MyContent = jsonObject.getString("description");
myBeanList.add(myBean);
//循环完毕时,将所有数据加入MyBean对象中,之后就可以将数据设置在ListView中
}
}catch(JSONException e){
e.printStackTrace();
}
}catch(IOException e){
e.printStackTrace();
}
return myBeanList;
}
private String readStream(InputStream is){
//通过InputStream解析网页返回的数据
InputSrteamReader isr;
String result = "";
try{
String line = "";
isr = new InputStream(is, "utf-8");
BufferedReader br = new BufferedReader(isr);
while((line = br.readLine())!= null){
result+=line;
}
}catch(UnsupportedEncodingException e){
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
return result;
}
class MyAsyncTask extends AsyncTask<String, Void, List<MyBean>>{
//实现网络的异步访问
protected List<MyBean> doInBackground(String... params){
return getJsonData(params[0]);
}
protected void onPostExecute(List<MyBean> myBean){
super.onPostExecute(myBean);
MyAdapter adapter = new MyAdapter(MainActivity.this, myBean );
mListView.setAdapter(adapter);
}
}
上述实现了ListView图文混排
接下来是对图像的异步加载:
public class ImageLoader{
private ImageView mImageView;
private Handler mHandler = new Handler();
private Handler handler = new Handler(){
public void handleMessage(Message msg){
super.handleMessage(msg);
mImageView.setImageBitmap((Bitmap)msg.obj);
}
}
public void showImageByThread(ImageView imageView, String url){
mImageView = imageView;
new Thread(){
public void run(){
super.run();
Bitmap bitmao = getBitmapFromURL(url);
Message message = Message.obtain();
message.obj = bitmap;
mHandler.sendMessage(message);
}
}.start();
}
public Bitmap getBitmapFromURL(String urlStirng){
Bitmap bitmap;
InputStream is;
try{
URL url = new URL(urlStirng);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
is = new BufferedInputStream(connection.getInputStream());
bitmap = BitmapFactory.decodeStream(is);
connection.disconnect();
return bitmap;
}catch(java.io.IOException e){
e.printStackTrace();
}finally{
is.close();
}
return null;
}
}
事实上,上面的方法,在网速不是很快的时候,会出现刷新时图片错乱的现象,因为ListView的缓存机制导致图片显示错位,则只需要改进一步,在MyAdapter类中的getView()方法中增加三行代码:
viewHolder.ivIcon.setImageResource(R.mipmap.ic_launcher);
String utl = mList.get(position).MyIconUrl;
viewHolder.ivIcon.setTag(utl);
new ImageLoader().showImageByThread(viewHolder.ivIcon, url);
viewHolder.tvTitle.setText(mList.get(position).MyTitle);
viewHolder.tvContent.setText(mList.get(position).MyContent);
return convertView;
之后在ImageLoader类中handleMessage()方法中增加判断语句,最终修改为:
public class ImageLoader{
private ImageView mImageView;
private Handler mHandler = new Handler();
private String mUrl;
private Handler handler = new Handler(){
public void handleMessage(Message msg){
super.handleMessage(msg);
if(mImageView.getTag().equals(mUrl))
mImageView.setImageBitmap((Bitmap)msg.obj);
}
}
public void showImageByThread(ImageView imageView, String url){
mImageView = imageView;
mUrl = url;
new Thread(){
public void run(){
super.run();
Bitmap bitmao = getBitmapFromURL(url);
Message message = Message.obtain();
message.obj = bitmap;
mHandler.sendMessage(message);
}
}.start();
}
public Bitmap getBitmapFromURL(String urlStirng){
Bitmap bitmap;
InputStream is;
try{
URL url = new URL(urlStirng);
HttpURLConnection connection = (HttpURLConnection)url.openConnection();
is = new BufferedInputStream(connection.getInputStream());
bitmap = BitmapFactory.decodeStream(is);
connection.disconnect();
return bitmap;
}catch(java.io.IOException e){
e.printStackTrace();
}finally{
is.close();
}
return null;
}
}
一般有两种办法避免ListView的缓存特性带来的ListView的Item的错乱,一种是在BaseAdapter中设置tag,将身份验证信息与相应的Item绑定,在加载时判断身份验证信息是否正确,正确才设置图片操作,第二种,是使用成员变量,将对应的数据进行缓存,从而避免网络下载时间不确定导致的时序的混乱。大部分的异步加载错误都可以使用这两种办法解决。
使用AsyncTask()方法异步加载
public void showImageByAsyncTask(ImageView imageView, String url){
new MyAsyncTask(imageView).execute(url);
}
private class MyAsyncTask extends AsyncTask<String, Void, Bitmap>{
private ImageView imageView;
public MyAsyncTask(ImageView imageView){
mImageView = imageView;
}
protected Bitmap doInBackground(String... params){
return getBitmapFromURL(params[0]);
}
protected void onPostExecute(Bitmap bitmap){
super.onPostExecute(bitmap);
mImageView.setImageBitmap(bitmap);
}
}
之后再回到MyAdapter类中,将多线程的方法加载图片的语句换掉:
//new ImageLoader().showImageByThread(viewHolder.ivIcon, url);
new ImageLoader().showImageByAsyncTask(viewHolder.ivIcon, url);
同样,AsyncTask方法也会出现刷新时图片错乱的现象,因为AsyncTask本身就是基于多线程,所以对其进行处理,修改之后:
public void showImageByAsyncTask(ImageView imageView, String url){
new MyAsyncTask(imageView, url).execute(url);
}
private class MyAsyncTask extends AsyncTask<String, Void, Bitmap>{
private ImageView imageView;
private String mUrl;
public MyAsyncTask(ImageView imageView, String url){
mImageView = imageView;
mUrl = url;
}
protected Bitmap doInBackground(String... params){
return getBitmapFromURL(params[0]);
}
protected void onPostExecute(Bitmap bitmap){
super.onPostExecute(bitmap);
if(mImageView.getTag().equals(mUrl))
mImageView.setImageBitmap(bitmap);
}
}
至此,便实现了异步加载网络图片。