Android 异步下载图片并缓存到本地以节约网络流量

在Android开发中我们经常有这样的需求,从服务器上下载xml或者JSON类型的数据,其中包括一些图片资源,本demo模拟了这个需求,从网络上加载XML资源,其中包括图片,我们要做的是解析XML里面的数据,并且把图片缓存到本地一个cache目录里面,并且用一个自定义的Adapter去填充到LIstView,demo运行效果就不上贴了,需要的朋友自己敲一下代码:


通过本demo,可以学会一下几点:

(1)怎么解析一个XML文件

(2)该demo中用到的缓存图片到本地一个临时目录的思想是怎么样的?

(3)AsyncTask类的使用,由于要异步下载的加载数据,就必须开启线程,但是开启线程时有时候不能很好的控制线程的数量,线程数量太大的时候手机就会很快被卡死,这里就采用了AsyncTask类去解决这个问题,这个类里面封装了线程池的技术,从而保证不会因开启太多的线程而消耗太多的资源。

(4)本demo中的Handler类的使用情况

(5)自定义Adapter的使用


1.先建立xml文件activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <ListView
        android:id="@+id/lv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/hello_world" />

</RelativeLayout>

2.item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/main_iv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="niad" >
    </ImageView>

    <TextView
        android:id="@+id/main_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>


3.Contact.java

package com.shine.async;

public class Contact {
	private String name;
	private String imageUrl;
	private int id;
	
	
	public Contact() {
		super();
	}
	public Contact(String name, String imageUrl, int id) {
		super();
		this.name = name;
		this.imageUrl = imageUrl;
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getImageUrl() {
		return imageUrl;
	}
	public void setImageUrl(String imageUrl) {
		this.imageUrl = imageUrl;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	
}


4.MD5.java

为了数据的安全,对下载的图片的文件名进行了MD5加密。

package com.shine.async;

import java.security.MessageDigest;

public class MD5 {
	
	public static String getMD5(String content){
		try {
			MessageDigest digest=MessageDigest.getInstance("MD5");
			digest.update(content.getBytes());
			return getHashString(digest);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
}

	private static String getHashString(MessageDigest digest) {
		StringBuilder builder=new StringBuilder();
		for (byte b:digest.digest()) {
			builder.append(Integer.toHexString((b>>4)&0xf));
			builder.append(Integer.toHexString(b&0xf));
		}
		return builder.toString();
	}
}

5.ContactService.java

注意:一个service类

1.HttpURLConnection conn = (HttpURLConnection) url.openConnection();获取一个链接,从而进行通讯
2.怎么利用XxmlPullPaser类去解析XML,从而把数据封装成对象
3.getImageURI(String path, File cache) 这个方法具体实现
4.Uri.fromFile(file);这个方法能够直接返回一个Uri来

package com.shine.async;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.xmlpull.v1.XmlPullParser;

import android.net.Uri;
import android.util.Xml;

public class ContactService {
	//从服务器上获取数据
	public List<Contact> getContactAll() throws Exception{
		List<Contact> contacts=null;
		String path="http://10.0.1.163/shine/contact.xml";
		URL url=new URL(path);
		HttpURLConnection conn=(HttpURLConnection) url.openConnection();
		conn.setConnectTimeout(3000);
		conn.setRequestMethod("GET");
		if (conn.getResponseCode()==HttpURLConnection.HTTP_OK) {
			InputStream is=conn.getInputStream();
			//这里获取数据直接放在xmlpullparser里面解析;
			contacts=xmlParser(is);
			return contacts;
		}
		return null;
	}

	private List<Contact> xmlParser(InputStream is) throws Exception {
		List<Contact> contacts=null;
		Contact contact=null;
		XmlPullParser parser=Xml.newPullParser();
		parser.setInput(is, "UTF-8");
		int eventType=parser.getEventType();
		while ((eventType=parser.next())!=XmlPullParser.END_DOCUMENT) {
			switch (eventType) {
			case XmlPullParser.START_TAG:
				if (parser.getName().equals("contacts")) {
					contacts=new ArrayList<Contact>();
				}else if (parser.getName().equals("contact")) {
					contact=new Contact();
					contact.setId(Integer.valueOf(parser.getAttributeValue(0)));
				}else if(parser.getName().equals("name")){
					contact.setName(parser.nextText());
				}else if(parser.getName().equals("image")){
					contact.setImageUrl(parser.getAttributeValue(0));
				}
				break;
			case XmlPullParser.END_TAG:
				if (parser.getName().equals("contact")) {
					contacts.add(contact);
				}
				break;
			}	
		}
		return contacts;
	}
	
	
	
	public  Uri getImageURI(String path,File cache) throws Exception{
		String name=MD5.getMD5(path)+path.substring(path.lastIndexOf("."));
		File file=new File(cache,name);
		//如果图片在本地缓存目录中,则不去服务器下载
		if (file.exists()) {
			//Uri.fromFile(path)这个方法能得到文件的URI
			return Uri.fromFile(file);
		}else {
			//从网络获取图片
			URL url=new URL(path);
			HttpURLConnection conn=(HttpURLConnection) url.openConnection();
			conn.setConnectTimeout(5000);
			conn.setRequestMethod("GET");
			conn.setDoInput(true);
			if (conn.getResponseCode()==200) {
				InputStream is=conn.getInputStream();
				FileOutputStream fos=new FileOutputStream(file);
				byte[] buffer=new byte[1024];
				int len=0;
				while ((len=is.read(buffer))!=-1) {
					fos.write(buffer,0,len);
				}
				is.close();
				fos.close();
				System.out.println(Uri.fromFile(file)+"URI对象的的发的范德萨发生大===========");
				return Uri.fromFile(file);
			}
		}
	return null;
	}
	
}

6.MyContactAdapter.java

自定义Adapter中,我们要注意 AsyncImageTask这个类继承了AsyncTask类,

AsyncTask是Android中常用来做异步任务的类,对线程池进行了封装


package com.shine.async;

import java.io.File;
import java.util.List;

import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
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 MyContactAdapter extends BaseAdapter{

	protected  static final int SUCCESS_GET_IMAGE=0;
	private Context context;
	private List<Contact> contacts;
	private File cache;
	private LayoutInflater inflater;
//自定义构造函数
	public MyContactAdapter(Context context, List<Contact> contacts, File cache) {
		super();
		this.context = context;
		this.contacts = contacts;
		this.cache = cache;
		inflater=(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	}

	@Override
	public int getCount() {
		return contacts.size();
	}
	@Override
	public Object getItem(int arg0) {
		
		return contacts.get(arg0);
	}
	@Override
	public long getItemId(int arg0) {
		return arg0;
	}
	@Override
	public View getView(int position, View convertView, ViewGroup arg2) {
		//获取item,在得到控件
		//2.绑定数据
		//3.绑定数据到item
		Hooker hooker;
		if (convertView==null) {
			convertView=inflater.inflate(R.layout.item, null);
			hooker = new Hooker();
			hooker.iv=(ImageView) convertView.findViewById(R.id.main_iv);
			hooker.tv=(TextView) convertView.findViewById(R.id.main_tv);
			convertView.setTag(hooker);
		} else {
			hooker = (Hooker) convertView.getTag();
		}
		Contact contact=contacts.get(position);
		//异步加载图片(线程池+handler)----》AsyncTask
		asyncLoadImage(hooker.iv,contact.getImageUrl());
		hooker.tv.setText(contact.getName());
		return convertView;
	}
	private void asyncLoadImage(ImageView iv, String imageUrl) {
		ContactService service=new ContactService();
		AsyncImageTask task=new AsyncImageTask(service,iv);
		System.out.println(imageUrl+"+++++++++++++__________________))))))))))))))))))");
		task.execute(imageUrl);
		
	}
	static class Hooker{
		public ImageView iv;
		public TextView tv;
	}

	private final class AsyncImageTask extends AsyncTask<String, Integer, Uri>{

		private ContactService service;
		private ImageView iv;
		
		public AsyncImageTask(ContactService service, ImageView iv) {
			super();
			this.service = service;
			this.iv = iv;
		}
		//后台运行的子线程
		@Override
		protected Uri doInBackground(String... params) {
			// TODO Auto-generated method stub
			System.out.println(params[0].toString()+"=============获取图片的地址为:");
			try {
				return service.getImageURI(params[0], cache);
			} catch (Exception e) {
				e.printStackTrace();
			}
			return null;
		}
		@Override
		protected void onPostExecute(Uri result) {
			super.onPostExecute(result);
			iv.setImageURI(result);
			
		}
	}
	
	
	

	/** 
     * 采用普通方式异步的加载图片 
     */  
    /*private void asyncloadImage(final ImageView iv_header, final String path) { 
        final Handler mHandler = new Handler() { 
            @Override 
            public void handleMessage(Message msg) { 
                super.handleMessage(msg); 
                if (msg.what == SUCCESS_GET_IMAGE) { 
                    Uri uri = (Uri) msg.obj; 
                    if (iv_header != null && uri != null) { 
                        iv_header.setImageURI(uri); 
                    } 
 
                } 
            } 
        }; 
        // 子线程,开启子线程去下载或者去缓存目录找图片,并且返回图片在缓存目录的地址 
        Runnable runnable = new Runnable() { 
            @Override 
            public void run() { 
                ContactService service = new ContactService(); 
                try { 
                    //这个URI是图片下载到本地后的缓存目录中的URI 
                    Uri uri = service.getImageURI(path, cache); 
                    Message msg = new Message(); 
                    msg.what = SUCCESS_GET_IMAGE; 
                    msg.obj = uri; 
                    mHandler.sendMessage(msg); 
                } catch (Exception e) { 
                    e.printStackTrace(); 
                } 
            } 
        }; 
        new Thread(runnable).start(); 
    }*/  
}

7.MainActivity.java

注意:Activity中,应注意一下几点:

1.初始化了一个缓存目录,这个目录最好是应用开启就去创建好,为后续缓存图片做准备,在这里把数据存放在SDCard上

2.要去服务器加载数据,这个耗时操作最好开启线程加载数据,加载完毕后去异步的更新UI线程,利用Handler机制能很好的解决这个问题

3.最后退出应用的时候,要删除掉缓存目录和目录里面的数据,避免给手机制造更多的垃圾文件。

package com.shine.async;

import java.io.File;
import java.util.List;

import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.widget.ListView;

@SuppressLint("HandlerLeak")
public class MainActivity extends Activity {
	private ListView lv;
	private File cache;
	private MyContactAdapter adapter;
	protected static final int SUCCESS_GET_CONTACT = 0;

	@SuppressLint("HandlerLeak")
	private Handler mHandler = new Handler() {
		@SuppressWarnings("unchecked")
		public void handleMessage(android.os.Message msg) {
			if (msg.what == SUCCESS_GET_CONTACT) {
				List<Contact> contacts = (List<Contact>) msg.obj;
				System.out.println(contacts.get(1).getImageUrl()
						+ "==================");
				adapter = new MyContactAdapter(getApplicationContext(),
						contacts, cache);
				lv.setAdapter(adapter);
			}
		};
	};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		lv = (ListView) this.findViewById(R.id.lv);
		// 创建系统缓存目录,系统一运行就创建缓存目录
		cache = new File(Environment.getExternalStorageDirectory(), "cache");
		System.out.println(Environment.getExternalStorageDirectory()
				+ ":sd卡路径的");
		if (!cache.exists()) {
			cache.mkdirs();
			// cache.mkdir();
			System.out.println("chuanj成功" + cache.toString());
		}
		// 获取数据,主ui线程是不能做耗时的操作的,所以启动子线程来做
		/*
		 * new Thread(new Runnable() {
		 * 
		 * @Override public void run() { // TODO Auto-generated method stub
		 * 
		 * } });
		 */
		new Thread() {
			public void run() {
				System.out.println("===========子线程运行了");
				ContactService service = new ContactService();
				List<Contact> contacts = null;
				try {
					contacts = service.getContactAll();
				} catch (Exception e) {
					e.printStackTrace();
				}
				// 子线程通过Message对象封装信息,并且用初始化好的,
				// Handler对象的sendMessage()方法把数据发送到主线程中,从而达到更新UI主线程的目的
				Message msg = new Message();
				msg.what = SUCCESS_GET_CONTACT;
				msg.obj = contacts;
				mHandler.sendMessage(msg);
			};
		}.start();
	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		super.onDestroy();
		// 清空缓存
		File[] files = cache.listFiles();
		for (File file : files) {
			file.delete();
		}
		cache.delete();
	}

}

8.Manifest.xml

这个要注意加上权限

访问网络权限,读写sd卡的权限

就是由于这个没有添加我调试了一个小时才看出来。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.shine.async"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.INTERNET"/>
 <!-- 在SDCard中创建与删除文件权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <!-- 往SDCard中写入数据权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.shine.async.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

9。下面是我们从服务器获取的并解析的xml文件:contact.xml

<?xml version="1.0" encoding="utf-8"?>
<contacts>  
    <contact id="1">
        <name>王明</name>  
        <image src="http://10.0.1.163/shine/a11.png"/>  
    </contact>  
    <contact id="2">  
        <name>刘能</name>  
        <image src="http://10.0.1.163/shine/a12.png"/>  
    </contact>      
    <contact id="3">  
        <name>赵四</name>  
        <image src="http://10.0.1.163/shine/a13.png"/>  
    </contact>          
    <contact id="4">  
        <name>老七</name>  
        <image src="http://10.0.1.163/shine/a14.png"/>  
    </contact>          
    <contact id="5">  
        <name>谢广坤</name>  
        <image src="http://10.0.1.163/shine/a15.png"/>  
    </contact>  
    <contact id="6">  
        <name>小雨</name>  
        <image src="http://10.0.1.163/shine/a16.png"/>  
    </contact>      
    <contact id="7">  
        <name>刘一水</name>  
        <image src="http://10.0.1.163/shine/a17.png"/>  
    </contact>          
    <contact id="8">  
        <name>刘楠</name>  
        <image src="http://10.0.1.163/shine/a18.png"/>  
    </contact>          
      
    <contact id="9">  
        <name>香秀</name>  
        <image src="http://10.0.1.163/shine/a19.png"/>  
    </contact>  
    <contact id="10">  
        <name>七大爷</name>  
        <image src="http://10.0.1.163/shine/a20.png"/>  
    </contact>      
    <contact id="11">  
        <name>老大</name>  
        <image src="http://10.0.1.163/shine/a21.png"/>  
    </contact>         
</contacts>  

总结:

通过这个demo,可以看出Android中会经常需要进行异步任务的处理,所以我们会常常用到自己手动开启线程,handler机制,或者AsyncTask类等手段来保证应用的性能。
谢谢!!!


你可能感兴趣的:(android,apk,网络,图片,asynchronous)