我的Android进阶之旅------>android异步加载图片显示,并且对图片进行缓存实例

简介: 先来看看效果示意图: step1:新建项目DataAsyncLoad,如下图所示 step2:设置应用的UI界面 a.应用的主界面    main.

先来看看效果示意图:


step1:新建项目DataAsyncLoad,如下图所示


step2:设置应用的UI界面

a.应用的主界面    main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<ListView  
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:id="@+id/listView"
    />
</LinearLayout>

b.每个ListView的界面  listview_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="horizontal" >
    <ImageView 
        android:layout_width="42dp"
        android:layout_height="42dp"
        android:id="@+id/imageView"  
        />

    <TextView 
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textColor="#FFFFFF"
        android:id="@+id/textView"        
        />
</LinearLayout>

step3:写一些辅助类     cn.roco.data.utilsMD5.java

package cn.roco.data.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5 {
	public static String getMD5(String content) {
		try {
			MessageDigest digest = MessageDigest.getInstance("MD5");
			digest.update(content.getBytes());
			return getHashString(digest);
			
		} catch (NoSuchAlgorithmException 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();
    }
}


step4:写应用使用的JavaBean  cn.roco.data.domain.Contact.java

package cn.roco.data.domain;

public class Contact {
	private int id;
	private String name;
	private String image;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getImage() {
		return image;
	}

	public void setImage(String image) {
		this.image = image;
	}

	public Contact(int id, String name, String image) {
		this.id = id;
		this.name = name;
		this.image = image;
	}
	public Contact(){
		
	}
}

step5:写一个应用的service层,用于对javabean进行操作  cn.roco.data.service.ContactService.java

package cn.roco.data.service;

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;

import cn.roco.data.domain.Contact;
import cn.roco.data.utils.MD5;

public class ContactService {
	/**
	 * 获取联系人数据
	 * 
	 * @return
	 * @throws Exception
	 */
	public static List<Contact> getContacts() throws Exception {
		String path = "http://192.168.1.100:8080/Hello/contact.xml";
		HttpURLConnection connection = (HttpURLConnection) new URL(path)
				.openConnection();
		connection.setConnectTimeout(5000);
		connection.setRequestMethod("GET");
		if (connection.getResponseCode() == 200) {
			return parseXML(connection.getInputStream());
		}
		return null;
	}
	/**转化XML获取数据
	 * 服务器端的xml文件如下。。。。。。
	 * <?xml version="1.0" encoding="UTF-8"?>
		<contacts>
			<contact id="1">
				<name>Roco_1</name>
				<image src="http://192.168.1.100:8080/Hello/images/1.png" />
			</contact>
			.......
		</contacts>*/
	private static List<Contact> parseXML(InputStream inputStream)
			throws Exception {
		List<Contact> contacts = new ArrayList<Contact>();
		Contact contact = null;

		XmlPullParser pullParser = Xml.newPullParser();
		pullParser.setInput(inputStream, "UTF-8");
		int event = pullParser.getEventType();
		while (event != XmlPullParser.END_DOCUMENT) {
			switch (event) {
			case XmlPullParser.START_TAG:
				if ("contact".equals(pullParser.getName())) {
					contact = new Contact();
					contact.setId(new Integer(pullParser.getAttributeValue(0)));
				} else if ("name".equals(pullParser.getName())) {
					contact.setName(pullParser.nextText());
				} else if ("image".equals(pullParser.getName())) {
					contact.setImage(pullParser.getAttributeValue(0));
				}
				break;
			case XmlPullParser.END_TAG:
				if ("contact".equals(pullParser.getName())) {
					contacts.add(contact);
					contact = null;
				}
			}
			event = pullParser.next();
		}
		return contacts;
	}

	/**
	 * 获取网络图片,如果图片存在于缓存中,就返回该图片,否则从网络中加载该图片并缓存起来
	 * 
	 * @param path
	 *            图片路径
	 * @return
	 */
	public static Uri getImage(String imagePath, File cacheDir)
			throws Exception {
		//缓存文件的文件名用MD5进行加密
		File localFile = new File(cacheDir, MD5.getMD5(imagePath)
				+ imagePath.substring(imagePath.lastIndexOf("."))); 
		if (localFile.exists()) {
			return Uri.fromFile(localFile);
		} else {
			HttpURLConnection connection = (HttpURLConnection) new URL(
					imagePath).openConnection();
			connection.setConnectTimeout(5000);
			connection.setRequestMethod("GET");
			//将文件缓存起来
			if (connection.getResponseCode() == 200) {
				FileOutputStream outputStream = new FileOutputStream(localFile);
				InputStream inputStream = connection.getInputStream();
				byte[] buffer = new byte[1024];
				int len = 0;
				while ((len = inputStream.read(buffer)) != -1) {
					outputStream.write(buffer, 0, len);
				}
				inputStream.close();
				outputStream.close();
				return Uri.fromFile(localFile);
			}
		}
		return null;
	}
}


step6:写一个Adapter用于对ListView进行数据更新 cn.roco.data.adapter.ContactAdapter.java

package cn.roco.data.adapter;

import java.io.File;
import java.util.List;
import cn.roco.data.R;
import cn.roco.data.domain.Contact;
import cn.roco.data.service.ContactService;
import android.content.Context;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
/**适配器,用于更新View*/
public class ContactAdapter extends BaseAdapter {
	private List<Contact> data;
	private int listviewItem;
	private File cache;
	/**
	 * LayoutInflater的作用类似于 findViewById(),不同点是LayoutInflater是用来找layout文件夹下的xml布局文件,并且实例化!
	 * 而 findViewById()是找具体某一个xml下的具体 widget控件(如:Button,TextView等)。
	 */
	private LayoutInflater layoutInflater;

	public ContactAdapter(Context context, List<Contact> data,
			int listviewItem, File cache) {
		this.data = data;
		this.listviewItem = listviewItem;
		this.cache = cache;
		this.layoutInflater = (LayoutInflater) context
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//取得xml里定义的view
		/***
		 * getSystemService()是Android很重要的一个API,它是Activity的一个方法,
		 * 根据传入的NAME来取得对应的Object,然后转换成相应的服务对象。以下介绍系统相应的服务。 
		 * 	传入的Name 					返回的对象 					说明 
			WINDOW_SERVICE   			WindowManager 			管理打开的窗口程序 
			LAYOUT_INFLATER_SERVICE 	LayoutInflater 			取得xml里定义的view 
			ACTIVITY_SERVICE 			ActivityManager 		管理应用程序的系统状态 
			POWER_SERVICE 				PowerManger 			电源的服务 
			ALARM_SERVICE 				AlarmManager 			闹钟的服务 
			NOTIFICATION_SERVICE 		NotificationManager 	状态栏的服务 
			KEYGUARD_SERVICE 			KeyguardManager 		键盘锁的服务 
			LOCATION_SERVICE 			LocationManager 		位置的服务,如GPS 
			SEARCH_SERVICE 				SearchManager 			搜索的服务 
			VEBRATOR_SERVICE 			Vebrator 				手机震动的服务 
			CONNECTIVITY_SERVICE 		Connectivity 			网络连接的服务 
			WIFI_SERVICE 				WifiManager 			Wi-Fi服务 
			TELEPHONY_SERVICE 			TeleponyManager 		电话服务 

		 */
	}

	/** 得到数据的总数 */
	@Override
	public int getCount() {
		return data.size();
	}

	/** 根据数据索引,得到集合中所对应的数据 */
	@Override
	public Object getItem(int position) {
		return data.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		ImageView imageView = null;
		TextView textView = null;
		
		if (convertView == null) {
			convertView = layoutInflater.inflate(listviewItem, null);
			imageView = (ImageView) convertView.findViewById(R.id.imageView);
			textView = (TextView) convertView.findViewById(R.id.textView);
			convertView.setTag(new DataWrapper(imageView, textView));//将内容包装起来以备以后使用
		} else {
			DataWrapper dataWrapper=(DataWrapper) convertView.getTag();//将包装类取出来
			//从包装类中取数据
			imageView=dataWrapper.getImageView();
			textView=dataWrapper.getTextView();
		}
		Contact contact=data.get(position);
		textView.setText(contact.getName());
		/**异步加载图片文件*/
		asynchImageLoad(imageView,contact.getImage());
		return convertView;
	}
	
	/*
	//该方法会创建很多的线程,也会很耗资源
	private void asynchImageLoad(final ImageView imageView, final String imagePath) {
		final Handler handler=new Handler(){
			@Override
			public void handleMessage(Message msg) {//运行在主线程中
				Uri uri=(Uri) msg.obj;
				if (uri!=null&&imageView!=null) {
					imageView.setImageURI(uri);
				}
			}
		};
		Runnable runnable=new Runnable() {
			@Override
			public void run() {
				try {
					Uri uri=ContactService.getImage(imagePath, cache);
					handler.sendMessage(handler.obtainMessage(10,uri));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};
		new Thread(runnable).start();
	}
	*/
	
	/**异步加载图片文件*/
	private void asynchImageLoad(ImageView imageView, String imagePath) {
		AsycImageTask asycImageTask=new AsycImageTask(imageView);
		asycImageTask.execute(imagePath);
	}
	/**
	 * 使用AsyncTask提高性能
	 * 可选方法:
		1,  onprogressupdate(progress…) 可以使用进度条增加用户体验度。此方法在主线程执行,用户显示任务执行的进度。
		2,  onpreExecute()  这里是最新用户调用excute时的接口,当任务执行之前开始调用此方法,可以在这里显示进度对话框。
		3,  onCancelled()  用户调用取消时,要做的操作。
		
		 AsyncTask<Params, Progress, Result> 
		 AsyscTask定义了三种泛型类型params,progress和result.
		 1,  params启动任务执行的输入参数,比如http请求的URL
		 2,  progress后台任务执行的百分比
		 3,  result后台执行任务最终返回的结果,比如String,比如我需要得到的list。
		
			使用AsyncTask类,遵守的准则:1,  Task的实例必须在UI thread中创建;2,  Execute方法必须在UI thread中调用
			3,  不要手动的调用onPfreexecute(),onPostExecute(result)Doinbackground(params…),onProgressupdate(progress…)这几个方法;
			4,  该task只能被执行一次,否则多次调用时将会出现异常;
			AsyncTask的整个调用过程都是从execute方法开始的,一旦在主线程中调用execute方法,就可以通过onpreExecute方法,
			这是一个预处理方法,比如可以在这里开始一个进度框,同样也可以通过onprogressupdate方法给用户一个进度条的显示,增加用户体验;
			最后通过onpostexecute方法,相当于handler处理UI的方式,在这里可以使用在doinbackground得到的结果处理操作UI。
			此方法在主线程执行,任务执行的结果作为此方法的参数返回
	 */
	private final class AsycImageTask extends AsyncTask<String, Integer, Uri>{
		private ImageView imageView;
		public AsycImageTask(ImageView imageView) {
			this.imageView=imageView;
		}
		/**
		 *  后台执行,比较耗时的操作都可以放在这里。
			注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作
			,通常需要较长的时间。在执行过程中可以调用
			Public progress(progress…)来更新任务的进度。
		 */
		@Override
		protected Uri doInBackground(String... params) {//子线程中执行
			try {
				return ContactService.getImage(params[0], cache);
			} catch (Exception e) {
				e.printStackTrace();
			}
			return null;
		}
		/**
		 * 相当于handler处理UI的方式,在这里可以使用在doinbackground得到的结果
		 * 处理操作UI。此方法在主线程执行,任务执行的结果作为此方法的参数返回。
		 */
		@Override
		protected void onPostExecute(Uri result) {//运行在主线程
			if (result!=null&&imageView!=null) {
				imageView.setImageURI(result);
			}
		}
		
	}
	
	/**数据包装类*/
	private final class DataWrapper {
		private ImageView imageView;
		private TextView textView;

		public ImageView getImageView() {
			return imageView;
		}

		public TextView getTextView() {
			return textView;
		}

		public DataWrapper(ImageView imageView, TextView textView) {
			this.imageView = imageView;
			this.textView = textView;
		}
	}

}


step7:应用的主程序  cn.roco.data.MainActivity.java

package cn.roco.data;

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

import cn.roco.data.adapter.ContactAdapter;
import cn.roco.data.domain.Contact;
import cn.roco.data.service.ContactService;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.widget.ListView;

public class MainActivity extends Activity {
	private ListView listView;
	/**缓存文件*/
	private File cache;
	
	/**接受消息,处理消息 ,此Handler会与当前主线程一块运行
	 * 使用匿名内部类来复写Handler当中的handlerMessage()方法  */
	Handler handler = new Handler() {
		// 接受数据
		public void handleMessage(android.os.Message msg) {
			//设置适配器,将获取的数据使用适配器更新View
			listView.setAdapter(new ContactAdapter(MainActivity.this,
					(List<Contact>) msg.obj, R.layout.listview_item, cache));
		};
	};

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		listView = (ListView) this.findViewById(R.id.listView);
		/**在SD卡中生成缓存目录*/
		cache = new File(Environment.getExternalStorageDirectory(), "cache");
		/**如果目录不存在就新建一个*/
		if (!cache.exists())  cache.mkdir();
			
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					//获取联系人数据
					List<Contact> data= ContactService.getContacts();
					// 向Handler发送消息,更新UI
					handler.sendMessage(handler.obtainMessage(22, data));
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();
	}
	
	@Override
	protected void onDestroy() {
		/**清除缓存文件*/
		for (File file:cache.listFiles()) {
			file.delete();
		}
		cache.delete();
		super.onDestroy();
	}
	
}

step8:AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
	package="cn.roco.data" android:versionCode="1" android:versionName="1.0">
	<uses-sdk android:minSdkVersion="8" />
	
	<!-- 访问Internet权限 -->
	<uses-permission android:name="android.permission.INTERNET" />
	<!-- 在SD卡中创建和删除文件权限 -->
	<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
	<!-- 往SD卡中写入数据权限 -->
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

	<application android:icon="@drawable/icon" android:label="@string/app_name">
		<activity android:name=".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>

step9:编写服务器端的代码,主要是一个contact.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<contacts>
	<contact id="1">
		<name>Roco_1</name>
		<image src="http://192.168.1.100:8080/Hello/images/1.png" />
	</contact>
	<contact id="2">
		<name>Roco_2</name>
		<image src="http://192.168.1.100:8080/Hello/images/2.png" />
	</contact>
	<contact id="3">
		<name>Roco_3</name>
		<image src="http://192.168.1.100:8080/Hello/images/3.png" />
	</contact>
	<contact id="4">
		<name>Roco_4</name>
		<image src="http://192.168.1.100:8080/Hello/images/4.png" />
	</contact>
	<contact id="5">
		<name>Roco_5</name>
		<image src="http://192.168.1.100:8080/Hello/images/5.png" />
	</contact>
	<contact id="6">
		<name>Roco_6</name>
		<image src="http://192.168.1.100:8080/Hello/images/6.png" />
	</contact>
	<contact id="7">
		<name>Roco_7</name>
		<image src="http://192.168.1.100:8080/Hello/images/7.png" />
	</contact>
	<contact id="8">
		<name>Roco_8</name>
		<image src="http://192.168.1.100:8080/Hello/images/8.png" />
	</contact>
	<contact id="9">
		<name>Roco_9</name>
		<image src="http://192.168.1.100:8080/Hello/images/9.png" />
	</contact>
	<contact id="10">
		<name>Roco_10</name>
		<image src="http://192.168.1.100:8080/Hello/images/10.png" />
	</contact>
	<contact id="11">
		<name>Roco_11</name>
		<image src="http://192.168.1.100:8080/Hello/images/11.png" />
	</contact>
	<contact id="12">
		<name>Roco_12</name>
		<image src="http://192.168.1.100:8080/Hello/images/12.png" />
	</contact>
	<contact id="13">
		<name>Roco_13</name>
		<image src="http://192.168.1.100:8080/Hello/images/13.png" />
	</contact>
	<contact id="14">
		<name>Roco_14</name>
		<image src="http://192.168.1.100:8080/Hello/images/14.png" />
	</contact>
	<contact id="15">
		<name>Roco_15</name>
		<image src="http://192.168.1.100:8080/Hello/images/15.png" />
	</contact>
	<contact id="16">
		<name>Roco_16</name>
		<image src="http://192.168.1.100:8080/Hello/images/16.png" />
	</contact>
	<contact id="17">
		<name>Roco_17</name>
		<image src="http://192.168.1.100:8080/Hello/images/17.png" />
	</contact>
	<contact id="18">
		<name>Roco_18</name>
		<image src="http://192.168.1.100:8080/Hello/images/18.png" />
	</contact>
	<contact id="19">
		<name>Roco_19</name>
		<image src="http://192.168.1.100:8080/Hello/images/19.png" />
	</contact>
	<contact id="20">
		<name>Roco_20</name>
		<image src="http://192.168.1.100:8080/Hello/images/20.png" />
	</contact>
</contacts>

以及在images目录下放置了一些图片



step10:将项目部署到模拟器上运行效果如下图:

         


==================================下面看一个gif动画===========================================

                      



在SD卡中会生成缓存文件


当应用退出的时候,会将缓存文件删除



有了缓存文件,只要应用没有退出,即使联网不成功,也可以读取缓存中的图片文件



==================================================================================================

  作者:欧阳鹏  欢迎转载,与人分享是进步的源泉!

  转载请保留原文地址http://blog.csdn.net/ouyang_peng

==================================================================================================


相关文章
|
3月前
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
41 2
|
25天前
|
缓存 Java Shell
Android 系统缓存扫描与清理方法分析
Android 系统缓存从原理探索到实现。
48 15
Android 系统缓存扫描与清理方法分析
|
4月前
|
Java Android开发
Android面试题经典之Glide取消加载以及线程池优化
Glide通过生命周期管理在`onStop`时暂停请求,`onDestroy`时取消请求,减少资源浪费。在`EngineJob`和`DecodeJob`中使用`cancel`方法标记任务并中断数据获取。当网络请求被取消时,`HttpUrlFetcher`的`cancel`方法设置标志,之后的数据获取会返回`null`,中断加载流程。Glide还使用定制的线程池,如AnimationExecutor、diskCacheExecutor、sourceExecutor和newUnlimitedSourceExecutor,其中某些禁止网络访问,并根据CPU核心数动态调整线程数。
138 2
|
25天前
|
Android开发 UED
Android 中加载 Gif 动画
【10月更文挑战第20天】加载 Gif 动画是 Android 开发中的一项重要技能。通过使用第三方库或自定义实现,可以方便地在应用中展示生动的 Gif 动画。在实际应用中,需要根据具体情况进行合理选择和优化,以确保用户体验和性能的平衡。可以通过不断的实践和探索,进一步掌握在 Android 中加载 Gif 动画的技巧和方法,为开发高质量的 Android 应用提供支持。
|
2月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
70 8
|
3月前
|
存储 缓存 Java
Android项目架构设计问题之优化业务接口数据的加载效率如何解决
Android项目架构设计问题之优化业务接口数据的加载效率如何解决
45 0
|
3月前
|
Java Android开发 Kotlin
Android项目架构设计问题之要在Glide库中加载网络图片到ImageView如何解决
Android项目架构设计问题之要在Glide库中加载网络图片到ImageView如何解决
37 0
|
4月前
|
缓存 编解码 安全
Android经典面试题之Glide的缓存大揭秘
Glide缓存机制包括内存和硬盘缓存。内存缓存使用弱引用的ActiveResources和LRU策略,硬盘缓存利用DiskLruCache。Engine.load方法首先尝试从内存和弱引用池加载,然后从LRU缓存中加载图片,增加引用计数并移出LRU。若缓存未命中,启动新任务或加入现有任务。内存大小根据设备内存动态计算,限制在0.4以下。DiskLruCache使用自定义读写锁,保证并发安全,写操作通过锁池管理,确保高效。
121 0
|
存储 缓存 Java
android 加载图片oom若干方案小结
本文根据网上提供的一些技术方案加上自己实际开发中遇到的情况小结。 众所周知,每个Android应用程序在运行时都有一定的内存限制,限制大小一般为16MB或24MB(视手机而定)。一般我们可以通过获取当前线程的可运行内存来判断,比如系统分给当前运行内存只有16M,而你的图片就有16M,这肯定会oom的。 相关知识介绍 1.颜色模型 常见的颜色模型有RGB、YUV、
1506 0
|
Android开发 数据格式 XML
android -- 加载gif 防止oom
项目中涉及到gif图片的展示,原来使用的是gifview,但是当频繁的,加载过大图片的时候会出现OOM的问题,后来去github上面找相关的库: https://github.com/koral--/android-gif-drawable android-gif-drawable是比较好的,并...
1140 0

热门文章

最新文章

下一篇
无影云桌面