文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android App中实现相册瀑布流展示的实例分享

2022-06-06 08:41

关注

传统界面的布局方式总是行列分明、坐落有序的,这种布局已是司空见惯,在不知不觉中大家都已经对它产生了审美疲劳。这个时候瀑布流布局的出现,就给人带来了耳目一新的感觉,这种布局虽然看上去貌似毫无规律,但是却有一种说不上来的美感,以至于涌现出了大批的网站和应用纷纷使用这种新颖的布局来设计界面。
记得我在之前已经写过一篇关于如何在Android上实现照片墙功能的文章了,但那个时候是使用的GridView来进行布局的,这种布局方式只适用于“墙”上的每张图片大小都相同的情况,如果图片的大小参差不齐,在GridView中显示就会非常的难看。而使用瀑布流的布局方式就可以很好地解决这个问题,因此今天我们也来赶一下潮流,看看如何在Android上实现瀑布流照片墙的功能。
首先还是讲一下实现原理,瀑布流的布局方式虽然看起来好像排列的很随意,其实它是有很科学的排列规则的。整个界面会根据屏幕的宽度划分成等宽的若干列,由于手机的屏幕不是很大,这里我们就分成三列。每当需要添加一张图片时,会将这张图片的宽度压缩成和列一样宽,再按照同样的压缩比例对图片的高度进行压缩,然后在这三列中找出当前高度最小的一列,将图片添加到这一列中。之后每当需要添加一张新图片时,都去重复上面的操作,就会形成瀑布流格局的照片墙,示意图如下所示。

201648235457901.png (347×459)

听我这么说完后,你可能会觉得瀑布流的布局非常简单嘛,只需要使用三个LinearLayout平分整个屏幕宽度,然后动态地addView()进去就好了。确实如此,如果只是为了实现功能的话,就是这么简单。可是别忘了,我们是在手机上进行开发,如果不停地往LinearLayout里添加图片,程序很快就会OOM。因此我们还需要一个合理的方案来对图片资源进行释放,这里仍然是准备使用LruCache算法,这个具体的在文后会专门讲,先知道是用这么回事~
下面我们就来开始实现吧,新建一个Android项目,起名叫PhotoWallFallsDemo,并选择4.0的API。
第一个要考虑的问题是,我们到哪儿去收集这些大小参差不齐的图片呢?这里我事先在百度上搜索了很多张风景图片,并且为了保证它们访问的稳定性,我将这些图片都上传到了我的CSDN相册里,因此只要从这里下载图片就可以了。新建一个Images类,将所有相册中图片的网址都配置进去,代码如下所示:


public class Images { 
 public final static String[] imageUrls = new String[] { 
   "http://img.my.csdn.net/uploads/201309/01/1378037235_3453.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037235_9280.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037234_3539.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037234_6318.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037194_2965.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037193_1687.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037193_1286.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037192_8379.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037178_9374.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037177_1254.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037177_6203.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037152_6352.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037151_9565.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037151_7904.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037148_7104.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037129_8825.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037128_5291.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037128_3531.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037127_1085.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037095_7515.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037094_8001.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037093_7168.jpg", 
   "http://img.my.csdn.net/uploads/201309/01/1378037091_4950.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949643_6410.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949642_6939.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949630_4505.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949630_4593.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949629_7309.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949629_8247.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949615_1986.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949614_8482.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949614_3743.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949614_4199.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949599_3416.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949599_5269.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949598_7858.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949598_9982.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949578_2770.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949578_8744.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949577_5210.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949577_1998.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949482_8813.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949481_6577.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949480_4490.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949455_6792.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949455_6345.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949442_4553.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949441_8987.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949441_5454.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949454_6367.jpg", 
   "http://img.my.csdn.net/uploads/201308/31/1377949442_4562.jpg" }; 
} 

然后新建一个ImageLoader类,用于方便对图片进行管理,代码如下所示:


public class ImageLoader { 
  
 private static LruCache<String, Bitmap> mMemoryCache; 
  
 private static ImageLoader mImageLoader; 
 private ImageLoader() { 
  // 获取应用程序最大可用内存 
  int maxMemory = (int) Runtime.getRuntime().maxMemory(); 
  int cacheSize = maxMemory / 8; 
  // 设置图片缓存大小为程序最大可用内存的1/8 
  mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
   @Override 
   protected int sizeOf(String key, Bitmap bitmap) { 
    return bitmap.getByteCount(); 
   } 
  }; 
 } 
  
 public static ImageLoader getInstance() { 
  if (mImageLoader == null) { 
   mImageLoader = new ImageLoader(); 
  } 
  return mImageLoader; 
 } 
  
 public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
  if (getBitmapFromMemoryCache(key) == null) { 
   mMemoryCache.put(key, bitmap); 
  } 
 } 
  
 public Bitmap getBitmapFromMemoryCache(String key) { 
  return mMemoryCache.get(key); 
 } 
 public static int calculateInSampleSize(BitmapFactory.Options options, 
   int reqWidth) { 
  // 源图片的宽度 
  final int width = options.outWidth; 
  int inSampleSize = 1; 
  if (width > reqWidth) { 
   // 计算出实际宽度和目标宽度的比率 
   final int widthRatio = Math.round((float) width / (float) reqWidth); 
   inSampleSize = widthRatio; 
  } 
  return inSampleSize; 
 } 
 public static Bitmap decodeSampledBitmapFromResource(String pathName, 
   int reqWidth) { 
  // 第一次解析将inJustDecodeBounds设置为true,来获取图片大小 
  final BitmapFactory.Options options = new BitmapFactory.Options(); 
  options.inJustDecodeBounds = true; 
  BitmapFactory.decodeFile(pathName, options); 
  // 调用上面定义的方法计算inSampleSize值 
  options.inSampleSize = calculateInSampleSize(options, reqWidth); 
  // 使用获取到的inSampleSize值再次解析图片 
  options.inJustDecodeBounds = false; 
  return BitmapFactory.decodeFile(pathName, options); 
 } 
} 

这里我们将ImageLoader类设成单例,并在构造函数中初始化了LruCache类,把它的最大缓存容量设为最大可用内存的1/8。然后又提供了其它几个方法可以操作LruCache,以及对图片进行压缩和读取。
接下来新建MyScrollView继承自ScrollView,代码如下所示:


public class MyScrollView extends ScrollView implements OnTouchListener { 
  
 public static final int PAGE_SIZE = 15; 
  
 private int page; 
  
 private int columnWidth; 
  
 private int firstColumnHeight; 
  
 private int secondColumnHeight; 
  
 private int thirdColumnHeight; 
  
 private boolean loadOnce; 
  
 private ImageLoader imageLoader; 
  
 private LinearLayout firstColumn; 
  
 private LinearLayout secondColumn; 
  
 private LinearLayout thirdColumn; 
  
 private static Set<LoadImageTask> taskCollection; 
  
 private static View scrollLayout; 
  
 private static int scrollViewHeight; 
  
 private static int lastScrollY = -1; 
  
 private List<ImageView> imageViewList = new ArrayList<ImageView>(); 
  
 private static Handler handler = new Handler() { 
  public void handleMessage(android.os.Message msg) { 
   MyScrollView myScrollView = (MyScrollView) msg.obj; 
   int scrollY = myScrollView.getScrollY(); 
   // 如果当前的滚动位置和上次相同,表示已停止滚动 
   if (scrollY == lastScrollY) { 
    // 当滚动的最底部,并且当前没有正在下载的任务时,开始加载下一页的图片 
    if (scrollViewHeight + scrollY >= scrollLayout.getHeight() 
      && taskCollection.isEmpty()) { 
     myScrollView.loadMoreImages(); 
    } 
    myScrollView.checkVisibility(); 
   } else { 
    lastScrollY = scrollY; 
    Message message = new Message(); 
    message.obj = myScrollView; 
    // 5毫秒后再次对滚动位置进行判断 
    handler.sendMessageDelayed(message, 5); 
   } 
  }; 
 }; 
  
 public MyScrollView(Context context, AttributeSet attrs) { 
  super(context, attrs); 
  imageLoader = ImageLoader.getInstance(); 
  taskCollection = new HashSet<LoadImageTask>(); 
  setOnTouchListener(this); 
 } 
  
 @Override 
 protected void onLayout(boolean changed, int l, int t, int r, int b) { 
  super.onLayout(changed, l, t, r, b); 
  if (changed && !loadOnce) { 
   scrollViewHeight = getHeight(); 
   scrollLayout = getChildAt(0); 
   firstColumn = (LinearLayout) findViewById(R.id.first_column); 
   secondColumn = (LinearLayout) findViewById(R.id.second_column); 
   thirdColumn = (LinearLayout) findViewById(R.id.third_column); 
   columnWidth = firstColumn.getWidth(); 
   loadOnce = true; 
   loadMoreImages(); 
  } 
 } 
  
 @Override 
 public boolean onTouch(View v, MotionEvent event) { 
  if (event.getAction() == MotionEvent.ACTION_UP) { 
   Message message = new Message(); 
   message.obj = this; 
   handler.sendMessageDelayed(message, 5); 
  } 
  return false; 
 } 
  
 public void loadMoreImages() { 
  if (hasSDCard()) { 
   int startIndex = page * PAGE_SIZE; 
   int endIndex = page * PAGE_SIZE + PAGE_SIZE; 
   if (startIndex < Images.imageUrls.length) { 
    Toast.makeText(getContext(), "正在加载...", Toast.LENGTH_SHORT) 
      .show(); 
    if (endIndex > Images.imageUrls.length) { 
     endIndex = Images.imageUrls.length; 
    } 
    for (int i = startIndex; i < endIndex; i++) { 
     LoadImageTask task = new LoadImageTask(); 
     taskCollection.add(task); 
     task.execute(Images.imageUrls[i]); 
    } 
    page++; 
   } else { 
    Toast.makeText(getContext(), "已没有更多图片", Toast.LENGTH_SHORT) 
      .show(); 
   } 
  } else { 
   Toast.makeText(getContext(), "未发现SD卡", Toast.LENGTH_SHORT).show(); 
  } 
 } 
  
 public void checkVisibility() { 
  for (int i = 0; i < imageViewList.size(); i++) { 
   ImageView imageView = imageViewList.get(i); 
   int borderTop = (Integer) imageView.getTag(R.string.border_top); 
   int borderBottom = (Integer) imageView 
     .getTag(R.string.border_bottom); 
   if (borderBottom > getScrollY() 
     && borderTop < getScrollY() + scrollViewHeight) { 
    String imageUrl = (String) imageView.getTag(R.string.image_url); 
    Bitmap bitmap = imageLoader.getBitmapFromMemoryCache(imageUrl); 
    if (bitmap != null) { 
     imageView.setImageBitmap(bitmap); 
    } else { 
     LoadImageTask task = new LoadImageTask(imageView); 
     task.execute(imageUrl); 
    } 
   } else { 
    imageView.setImageResource(R.drawable.empty_photo); 
   } 
  } 
 } 
  
 private boolean hasSDCard() { 
  return Environment.MEDIA_MOUNTED.equals(Environment 
    .getExternalStorageState()); 
 } 
  
 class LoadImageTask extends AsyncTask<String, Void, Bitmap> { 
   
  private String mImageUrl; 
   
  private ImageView mImageView; 
  public LoadImageTask() { 
  } 
   
  public LoadImageTask(ImageView imageView) { 
   mImageView = imageView; 
  } 
  @Override 
  protected Bitmap doInBackground(String... params) { 
   mImageUrl = params[0]; 
   Bitmap imageBitmap = imageLoader 
     .getBitmapFromMemoryCache(mImageUrl); 
   if (imageBitmap == null) { 
    imageBitmap = loadImage(mImageUrl); 
   } 
   return imageBitmap; 
  } 
  @Override 
  protected void onPostExecute(Bitmap bitmap) { 
   if (bitmap != null) { 
    double ratio = bitmap.getWidth() / (columnWidth * 1.0); 
    int scaledHeight = (int) (bitmap.getHeight() / ratio); 
    addImage(bitmap, columnWidth, scaledHeight); 
   } 
   taskCollection.remove(this); 
  } 
   
  private Bitmap loadImage(String imageUrl) { 
   File imageFile = new File(getImagePath(imageUrl)); 
   if (!imageFile.exists()) { 
    downloadImage(imageUrl); 
   } 
   if (imageUrl != null) { 
    Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource( 
      imageFile.getPath(), columnWidth); 
    if (bitmap != null) { 
     imageLoader.addBitmapToMemoryCache(imageUrl, bitmap); 
     return bitmap; 
    } 
   } 
   return null; 
  } 
   
  private void addImage(Bitmap bitmap, int imageWidth, int imageHeight) { 
   LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 
     imageWidth, imageHeight); 
   if (mImageView != null) { 
    mImageView.setImageBitmap(bitmap); 
   } else { 
    ImageView imageView = new ImageView(getContext()); 
    imageView.setLayoutParams(params); 
    imageView.setImageBitmap(bitmap); 
    imageView.setScaleType(ScaleType.FIT_XY); 
    imageView.setPadding(5, 5, 5, 5); 
    imageView.setTag(R.string.image_url, mImageUrl); 
    findColumnToAdd(imageView, imageHeight).addView(imageView); 
    imageViewList.add(imageView); 
   } 
  } 
   
  private LinearLayout findColumnToAdd(ImageView imageView, 
    int imageHeight) { 
   if (firstColumnHeight <= secondColumnHeight) { 
    if (firstColumnHeight <= thirdColumnHeight) { 
     imageView.setTag(R.string.border_top, firstColumnHeight); 
     firstColumnHeight += imageHeight; 
     imageView.setTag(R.string.border_bottom, firstColumnHeight); 
     return firstColumn; 
    } 
    imageView.setTag(R.string.border_top, thirdColumnHeight); 
    thirdColumnHeight += imageHeight; 
    imageView.setTag(R.string.border_bottom, thirdColumnHeight); 
    return thirdColumn; 
   } else { 
    if (secondColumnHeight <= thirdColumnHeight) { 
     imageView.setTag(R.string.border_top, secondColumnHeight); 
     secondColumnHeight += imageHeight; 
     imageView 
       .setTag(R.string.border_bottom, secondColumnHeight); 
     return secondColumn; 
    } 
    imageView.setTag(R.string.border_top, thirdColumnHeight); 
    thirdColumnHeight += imageHeight; 
    imageView.setTag(R.string.border_bottom, thirdColumnHeight); 
    return thirdColumn; 
   } 
  } 
   
  private void downloadImage(String imageUrl) { 
   HttpURLConnection con = null; 
   FileOutputStream fos = null; 
   BufferedOutputStream bos = null; 
   BufferedInputStream bis = null; 
   File imageFile = null; 
   try { 
    URL url = new URL(imageUrl); 
    con = (HttpURLConnection) url.openConnection(); 
    con.setConnectTimeout(5 * 1000); 
    con.setReadTimeout(15 * 1000); 
    con.setDoInput(true); 
    con.setDoOutput(true); 
    bis = new BufferedInputStream(con.getInputStream()); 
    imageFile = new File(getImagePath(imageUrl)); 
    fos = new FileOutputStream(imageFile); 
    bos = new BufferedOutputStream(fos); 
    byte[] b = new byte[1024]; 
    int length; 
    while ((length = bis.read(b)) != -1) { 
     bos.write(b, 0, length); 
     bos.flush(); 
    } 
   } catch (Exception e) { 
    e.printStackTrace(); 
   } finally { 
    try { 
     if (bis != null) { 
      bis.close(); 
     } 
     if (bos != null) { 
      bos.close(); 
     } 
     if (con != null) { 
      con.disconnect(); 
     } 
    } catch (IOException e) { 
     e.printStackTrace(); 
    } 
   } 
   if (imageFile != null) { 
    Bitmap bitmap = ImageLoader.decodeSampledBitmapFromResource( 
      imageFile.getPath(), columnWidth); 
    if (bitmap != null) { 
     imageLoader.addBitmapToMemoryCache(imageUrl, bitmap); 
    } 
   } 
  } 
   
  private String getImagePath(String imageUrl) { 
   int lastSlashIndex = imageUrl.lastIndexOf("/"); 
   String imageName = imageUrl.substring(lastSlashIndex + 1); 
   String imageDir = Environment.getExternalStorageDirectory() 
     .getPath() + "/PhotoWallFalls/"; 
   File file = new File(imageDir); 
   if (!file.exists()) { 
    file.mkdirs(); 
   } 
   String imagePath = imageDir + imageName; 
   return imagePath; 
  } 
 } 
} 

MyScrollView是实现瀑布流照片墙的核心类,这里我来重点给大家介绍一下。首先它是继承自ScrollView的,这样就允许用户可以通过滚动的方式来浏览更多的图片。这里提供了一个loadMoreImages()方法,是专门用于加载下一页的图片的,因此在onLayout()方法中我们要先调用一次这个方法,以初始化第一页的图片。然后在onTouch方法中每当监听到手指离开屏幕的事件,就会通过一个handler来对当前ScrollView的滚动状态进行判断,如果发现已经滚动到了最底部,就会再次调用loadMoreImages()方法去加载下一页的图片。
那我们就要来看一看loadMoreImages()方法的内部细节了。在这个方法中,使用了一个循环来加载这一页中的每一张图片,每次都会开启一个LoadImageTask,用于对图片进行异步加载。然后在LoadImageTask中,首先会先检查一下这张图片是不是已经存在于SD卡中了,如果还没存在,就从网络上下载,然后把这张图片存放在LruCache中。接着将这张图按照一定的比例进行压缩,并找出当前高度最小的一列,把压缩后的图片添加进去就可以了。
另外,为了保证照片墙上的图片都能够合适地被回收,这里还加入了一个可见性检查的方法,即checkVisibility()方法。这个方法的核心思想就是检查目前照片墙上的所有图片,判断出哪些是可见的,哪些是不可见。然后将那些不可见的图片都替换成一张空图,这样就可以保证程序始终不会占用过高的内存。当这些图片又重新变为可见的时候,只需要再从LruCache中将这些图片重新取出即可。如果某张图片已经从LruCache中被移除了,就会开启一个LoadImageTask,将这张图片重新加载到内存中。
然后打开或新建activity_main.xml,在里面设置好瀑布流的布局方式,如下所示:


<com.example.photowallfallsdemo.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/my_scroll_view" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" > 
 <LinearLayout 
  android:layout_width="match_parent" 
  android:layout_height="wrap_content" 
  android:orientation="horizontal" > 
  <LinearLayout 
   android:id="@+id/first_column" 
   android:layout_width="0dp" 
   android:layout_height="wrap_content" 
   android:layout_weight="1" 
   android:orientation="vertical" > 
  </LinearLayout> 
  <LinearLayout 
   android:id="@+id/second_column" 
   android:layout_width="0dp" 
   android:layout_height="wrap_content" 
   android:layout_weight="1" 
   android:orientation="vertical" > 
  </LinearLayout> 
  <LinearLayout 
   android:id="@+id/third_column" 
   android:layout_width="0dp" 
   android:layout_height="wrap_content" 
   android:layout_weight="1" 
   android:orientation="vertical" > 
  </LinearLayout> 
 </LinearLayout> 
</com.example.photowallfallsdemo.MyScrollView>

 
可以看到,这里我们使用了刚才编写好的MyScrollView作为根布局,然后在里面放入了一个直接子布局LinearLayout用于统计当前滑动布局的高度,然后在这个布局下又添加了三个等宽的LinearLayout分别作为第一列、第二列和第三列的布局,这样在MyScrollView中就可以动态地向这三个LinearLayout里添加图片了。
最后,由于我们使用到了网络和SD卡存储的功能,因此还需要在AndroidManifest.xml中添加以下权限:


<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 
<uses-permission android:name="android.permission.INTERNET" /> 

这样我们所有的编码工作就已经完成了,现在可以尝试运行一下,效果如下图所示:

201648235542999.gif (235×418)

LruCache图片缓存技术
在你应用程序的UI界面加载一张图片是一件很简单的事情,但是当你需要在界面上加载一大堆图片的时候,情况就变得复杂起来。在很多情况下,(比如使用ListView, GridView 或者 ViewPager 这样的组件),屏幕上显示的图片可以通过滑动屏幕等事件不断地增加,最终导致OOM。
为了保证内存的使用始终维持在一个合理的范围,通常会把被移除屏幕的图片进行回收处理。此时垃圾回收器也会认为你不再持有这些图片的引用,从而对这些图片进行GC操作。用这种思路来解决问题是非常好的,可是为了能让程序快速运行,在界面上迅速地加载图片,你又必须要考虑到某些图片被回收之后,用户又将它重新滑入屏幕这种情况。这时重新去加载一遍刚刚加载过的图片无疑是性能的瓶颈,你需要想办法去避免这个情况的发生。
这个时候,使用内存缓存技术可以很好的解决这个问题,它可以让组件快速地重新加载和处理图片。下面我们就来看一看如何使用内存缓存技术来对图片进行缓存,从而让你的应用程序在加载很多图片的时候可以提高响应速度和流畅性。
内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。
在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:
你的设备可以为每个应用程序分配多大的内存?
设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
图片的尺寸和大小,还有每张图片会占据多少内存空间。
图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。
并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。
下面是一个使用 LruCache 来缓存图片的例子:


private LruCache<String, Bitmap> mMemoryCache; 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
  // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。 
  // LruCache通过构造函数传入缓存值,以KB为单位。 
  int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 
  // 使用最大可用内存值的1/8作为缓存的大小。 
  int cacheSize = maxMemory / 8; 
  mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
    @Override 
    protected int sizeOf(String key, Bitmap bitmap) { 
      // 重写此方法来衡量每张图片的大小,默认返回图片数量。 
      return bitmap.getByteCount() / 1024; 
    } 
  }; 
} 
public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
  if (getBitmapFromMemCache(key) == null) { 
    mMemoryCache.put(key, bitmap); 
  } 
} 
public Bitmap getBitmapFromMemCache(String key) { 
  return mMemoryCache.get(key); 
} 

在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。
当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。


public void loadBitmap(int resId, ImageView imageView) { 
  final String imageKey = String.valueOf(resId); 
  final Bitmap bitmap = getBitmapFromMemCache(imageKey); 
  if (bitmap != null) { 
    imageView.setImageBitmap(bitmap); 
  } else { 
    imageView.setImageResource(R.drawable.image_placeholder); 
    BitmapWorkerTask task = new BitmapWorkerTask(imageView); 
    task.execute(resId); 
  } 
} 

BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。


class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
  // 在后台加载图片。 
  @Override 
  protected Bitmap doInBackground(Integer... params) { 
    final Bitmap bitmap = decodeSampledBitmapFromResource( 
        getResources(), params[0], 100, 100); 
    addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); 
    return bitmap; 
  } 
} 

掌握了以上两种方法,不管是要在程序中加载超大图片,还是要加载大量图片,都不用担心OOM的问题了!

您可能感兴趣的文章:Android瀑布流照片墙实现 体验不规则排列的美感Android RecyclerView详解之实现 ListView GridView瀑布流效果android中UIColletionView瀑布流布局实现思路以及封装的实现android控件封装 自己封装的dialog控件android 自定义控件 自定义属性详细介绍Android中Spinner(下拉框)控件的使用详解Android下拉刷新上拉加载控件(适用于所有View)Android控件系列之TextView使用介绍android ListView和ProgressBar(进度条控件)的使用方法Android控件之ListView用法实例详解Android开发之瀑布流控件的实现与使用方法示例


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯