应用中图片的加载速度和图片的显示效果直接关系到用户的体验,也关系到应用得成败,你可以想象一下在一个应用中如果显示图片速度需要几分钟甚至10几分钟,可想而知你的应用将要面对什么样的命运。今天和同事讨论了一下安卓图片的加载问题,觉得图片的加载方式可以通过是内存缓存+本地存储+服务器获取三种方式结合是使用,为什么呢?
一.图片处理方式分析
内存缓存:无疑的这种方式速度是最快的,但是考虑到如果有大量的图片需要处理,那么对于我们的有限内存的手机来说这种体验将是非常糟糕的,如果超出了虚拟机(JVM)分配给这个应用的最大内存,它就会报OOM(内存溢出)错误;当然我们也可以设置缓存的大小,通过程序来控制缓存的的清理,如,时间周期清理或者按照图片的使用频率等方式,这种方式不会报错,但是图片的存储容量是很小的。
优点:加载速度快 占用空间少 缺点:占用内存多 不持久化
本地存储:这种存储方式相对第一种来说速度会慢点,现在的手机SD卡来说读取速度还是不错的,其实我们也可以单独采取这种方式来处理图片。可以采取和第一种相同的处理方式来控制图片缓存的大小,不过相对于第一种可用的空间会大的多。
优点:可用的缓存空间相对较多 不占用内存 缺点:常用的未驻内存所以说我们可以采用内存缓存和本地存储的方式来对图片进行处理,这种方式既能够利用内存缓存速度快的优点,又能够通过本地存储来扩展缓存空间;其中内存缓存用来存储频率较高的图片,本地存储扩展存储剩下图片。具体的做法是:
(1)第一次打开应用时,自动存储完程序分配好的内存空间缓存,如果有多余的图片则存储到SD卡中,如果还有多余则覆盖SD卡中前面已经存储的。
(2)再次打开应用时,则判断图片是否已经存在(先和内存比较然后和SD卡中比较),如果存在则直接加载,并且让使用频率加1,同时比较频率使用次数,来判断哪些需要加载到内存缓存中,那些放在SD卡中;如果不存在则从网络中下载图片,空间不足则自动覆盖掉前面使用频率较少,或者时间过期的图片。
二. Android-Universal-Image-Loader开源项目
今天的主角出来了,Android-Universal-Image-Loader开源项目(点击打开链接),该项目提供了非常多的接口,并且使用了三种方式来对图片进行处理。
具体的原理可以参考博客浅谈开源项目Android-Universal-Image-Loader(Part 3.1)和开源项目:Android-Universal-Image-Loader总结
(1)项目引用
下子文件在downloads文件夹中将universal-image-loader-1.8.4.jar文件引入到项目中
(2)参数设置
Android-Universal-Image-Loader有很多的参数设置,具体的可以去查看文档,例子只给出了常见的设置
public class MainActivity extends Activity {
ListView listView;
String pathex="https://lh6.googleusercontent.com/-jZgveEqb6pg/T3R4kXScycI/AAAAAAAAAE0/xQ7CvpfXDzc/s1024/sample_image_01.jpg";//<span style="color:#ff0000;">图片的http://地址,请自己添加,这里的地址不一定可以使用</span>
String[] imageUrls=new String[]{pathex,pathex,pathex,pathex};
DisplayImageOptions options;
ImageLoader imageLoader = ImageLoader.getInstance();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView=(ListView) findViewById(android.R.id.list);//android.R表示是内置的资源
options = new DisplayImageOptions.Builder()
.showStubImage(R.drawable.pdt_sample)// 设置图片在下载期间显示的图片
.showImageForEmptyUri(R.drawable.pdt_sample)// 设置图片Uri为空或是错误的时候显示的图片
.showImageOnFail(R.drawable.pdt_sample)// 设置图片加载/解码过程中错误时候显示的图片
.cacheInMemory()// 是否緩存都內存中
.cacheOnDisc()// 是否緩存到sd卡上
.displayer(new SimpleBitmapDisplayer()).build();//图片显示方式为正常显示
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(
getApplicationContext())
.threadPriority(Thread.NORM_PRIORITY - 2)// 设置线程的优先级
.denyCacheImageMultipleSizesInMemory()// 当同一个Uri获取不同大小的图片,缓存到内存时,只缓存一个。默认会缓存多个不同的大小的相同图片
.discCacheFileNameGenerator(new Md5FileNameGenerator())// 设置缓存文件的名字
.discCacheFileCount(60)// 缓存文件的最大个数
.tasksProcessingOrder(QueueProcessingType.LIFO)// 设置图片下载和显示的工作队列排序
.build();
//Initialize ImageLoader with configuration
imageLoader.init(config);
((ListView) listView).setAdapter(new ItemAdapter());
listView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
startImagePagerActivity(position);
}
});
//ImageLoader.getInstance().displayImage("http://cms.kineticspace.net/taipo/POI/webapp/app/view/upload/1399014224.jpg", imageView);
}
@Override
public void onBackPressed() {
AnimateFirstDisplayListener.displayedImages.clear();
super.onBackPressed();
}
private void startImagePagerActivity(int position) {
/*Intent intent = new Intent(this, ImagePagerActivity.class);
intent.putExtra(Extra.IMAGES, imageUrls);
intent.putExtra(Extra.IMAGE_POSITION, position);
startActivity(intent);*/
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
class ItemAdapter extends BaseAdapter {
private ImageLoadingListener animateFirstListener = new AnimateFirstDisplayListener();
private class ViewHolder {
public TextView text;
public ImageView image;
}
@Override
public int getCount() {
return imageUrls.length;
}
@Override
public Object getItem(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View view = convertView;
final ViewHolder holder;
if (convertView == null) {
view = getLayoutInflater().inflate(R.layout.item_list_shopcar, parent, false);
holder = new ViewHolder();
holder.text = (TextView) view.findViewById(R.id.text);
holder.image = (ImageView) view.findViewById(R.id.image);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
holder.text.setText("Item " + (position + 1));
imageLoader.displayImage(imageUrls[position], holder.image, options, animateFirstListener);
return view;
}
}
private static class AnimateFirstDisplayListener extends SimpleImageLoadingListener {
static final List<String> displayedImages = Collections.synchronizedList(new LinkedList<String>());
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
if (loadedImage != null) {
ImageView imageView = (ImageView) view;
boolean firstDisplay = !displayedImages.contains(imageUri);
if (firstDisplay) {
FadeInBitmapDisplayer.animate(imageView, 500);
displayedImages.add(imageUri);
}
}
}
}
}
布局文件:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#fff"
android:divider="#f00"
android:dividerHeight="0.5dp"/>
布局文件: item_list_shopcar.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/image"
android:adjustViewBounds="true"
android:contentDescription="@string/descr_image"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="5dp"
android:scaleType="fitXY"/>
<TextView
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_marginLeft="20dip"
android:textSize="22sp" />
</LinearLayout>
这里可以添加子页面,我们有添加具体可参考,项目的sample例子,今天就到这吧,下班收工。