Heguming

Heguming

0个粉丝

8

问答

0

专栏

6

资料

Heguming  发布于  2015-08-20 21:01:42
采纳率 0%
8个问答
4833

由OOM问题引发的分析

本帖最后由 Heguming 于 2015-8-21 18:09 编辑

最近,在做一个导览系统,主要功能是为游人提供馆内各层的导览图,方便人们参观。
项目刚开始,一切都有条不紊的进行。由于接触过几个这方面的项目,因此进行起来还算顺利。

然而,项目进行过程中,确实遇到了问题:OOM-内存溢出

有经验的工程师会立即想到BUG的源头:图片尺寸过大或加载数量过多

本项目开发过程中,多次出现该问题,具体细节及解决方法如下:

1.主界面
   对于此处的OOM,我采取了一个较为规避的方法,将原定计划的ImageView+GestureDetector改为WebView。
   经过修改,不仅解决了OOM问题,还使得手势操作较GestureDetector更为灵敏,流畅,毕竟是采用了WebView内置的Zoom工具,效果就是不一样。然而同样存在一个问题,即WebView加载过程中,会出现短暂的空白期,但相对于原定计划,该瑕疵可以忽略。

2.管理员界面
   此处需要同时加载两张图片,若加载图片过大,会报OOM错误。即使选取图片尺寸较小,也会感到很明显的卡顿现象。这极大的影响了用户的交互体验。对于此处OOM问题,我采取的措施是加载缩略图。
   加载缩略图,目前主要有两种方式:
   1.通过ThumbnailUtils工具类直接创建缩略图:
       newBitmap = ThumbnailUtils.extractThumbnail(oldBitmap, width, height);  
   2.利用Option属性压缩图片生成缩略图:
       BitmapFactory.Options options = new BitmapFactory.Options();
       options.inJustDecodeBounds = true;
       Bitmap bitmap = BitmapFactory.decodeFile(path, options);
       options.inSampleSize = scale;  //缩放倍数
       options.inJustDecodeBounds = false;
       bitmap = BitmapFactory.decodeFile(path, options);
       ThumbnailUtils是Android 2.2版本中,新增的一个用于实现缩略图的工具类,此工具类功能强大,利用内置的常数和方法,可以轻松快捷的实现图片和视频的缩略图功能。
       对于这两种方式,我都尝试了一遍。相比较而言,第二种方式加载图片缩略图更快,故此处采用第二种方式。
3.文件浏览
   此处需要同时显示同一文件夹下的所有图片文件的缩略图,然后以列表的形式显示在ItemView中。对于此类需要同时加载多张图片的场合,通常采用异步方式进行。
   异步加载图片缩略图可以通过AsyncLoadedImage类实现。
   AsyncLoadedImage类继承AsyncTask异步加载类,通过调用publishProgress方法更新onProgressUpdate贮存缩略图信息到Adapter,通过监听Adapter Change实现异步加载缩略图。
   然而,经过测试,发现通过这种方法加载图片效果效果并不理想。因此,我采取了另外一种方式。
     
   首先,在ListView Adapter中,禁止ItemView中的ImageView控件的图片加载。如果在此Adapter中getView方法内加载图片,会导致图片的重复加载。
   因为getView方法是用于获取将要显示的View,即每滑动一次,就会加载一次图片,对于已经加载过的,是无法保存的。
   
   其次,将列表改为可上拉加载的,这样在显示刷新页面的footView时,可以进行数据的更新及图片的显示。
   由于将setImage放在了Adapter外,故而此时加载的图片是可以保存的,是不随getView而实时动态重复加载的。
   然而,此处调试过程中,发现了一个致命的逻辑错误。
   由于我是在更新数据的同时加载ImageView的,此时父View-ListView尚未生成,即listView并未执行getView方法,故而无法通过子View-ItemView获取控件ImageView的ID。
   而我想要的效果是,更新完数据后,同时加载图片,最后通过ListView显示出来。
   很明显,这陷入了逻辑循环悖论之中。即若要加载A View,必须先加载B View,而若要加载B View,又必须先加载A View。
   因而,该效果最后被否定。只有先获得ListView,然后通过getChildAt方法获得ItemView,最终获得ImageView的ID。

   可是,又有一个问题随之而生,即何时更新ImageView。很显然,必须ListView一被显示,即执行Update方法更新ImageView。那么,问题来了,如何监听ListView显示完成?
   由于开发经验不深,在我的印象中,貌似ListView并没有该listener-onCreateView。
   最初设想是通过在getView中进行判断,一旦满足某个条件,即通过自定义接口,通知Activity更新ImageView。但实验证明,当该条件满足时,ListView也并未显示完成。
   
   而后,又通过多种方法,但都以失败告终。但最后,终于让我想到了。
   之所以用"想到",而非"找到",可能是我太大意了,竟然忘了listView.setOnScrollListener监听器。该监听器主要包含两个方法: onScroll和onScrollStateChanged。
   前者监听滚动事件,后者监听滚动状态改变事件。
   其中,onScroll中有一个visibleItemCount参数,即可以通过它判断ListView是否显示完成,即visibleItemCount>0。
   初级程序员很容易犯的一个错误就是,将onScroll方法误以为监听手指拖动。
   实际上,除了手指拖动,该方法还包括监听系统自动滚动。故而,可以通过监听页面的系统滚动,实现监听ListView的显示完成事件。

   好了,言归正传,下一步,也是最关键的一部,建立LruCache类缓存已加载过的图片,用于图片的二次加载。
   当然,除了LruCache缓存,还可以通过弱引用SoftReference的方式异步加载图片。
   但是网上有言,说从 Android 2.3 开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。
   同时,Android 3.0 中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。
   故而,此处采用LruCache方式来缓存图片。如果即将加载的图片在LruCache中有缓存,则直接从LruCache中读取,这大大提高了图片的加载效率。

   最后,通过线程,将已加载的缩略图保存成缩略图文件,方便日后再次加载。

   总结一下上述加载大量图片的思路流程:

   1.判断LruCache中是否有该缩略图缓存,如果有,直接加载,如果没有,进入第二步。

   2.判断自创建的缩略图文件夹中是否有该缩略图文件,如果有,加载,并将缩略图(非原图)存入LruCache中缓存,如果没有,进入第三步。

   3.生成缩略图文件,存入自创建的缩略图文件夹,并将缩略图(非原图)存入LruCache中缓存,结束。

   需要注意的小细节是:加载图片时,尽量不要从事耗时的操作,故此将生成缩略图文件的方法,放入线程中异步执行。

   好了,测试证明,通过这种方法加载的图片列表,拖动起来顺滑而流畅,没有丝毫卡顿。
   大功告成!

   文章结束处,附上关键代码:
     
         LruCacheBitmap:
         (实现图片压缩并缓存)

      [code]package com.example.pic;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.view.View;
import android.widget.ImageView;

import com.example.model.Info;

public class LruCacheBitmap {

        private android.util.LruCache lruCache;
        private final static String THUMBNAIL = Environment
                        .getExternalStorageDirectory() + "/display/thumbnail/";

        private static ArrayList IMG_SUFFIX = new ArrayList();
        static {
                IMG_SUFFIX.add("jpg");
                IMG_SUFFIX.add("JPG");
                IMG_SUFFIX.add("png");
                IMG_SUFFIX.add("PNG");
                IMG_SUFFIX.add("bmp");
                IMG_SUFFIX.add("BMP");
        }

        public LruCacheBitmap(android.util.LruCache lruCache) {
                this.lruCache = lruCache;
        }

        private Bitmap getThumbnail(String path, int width, int height) {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inJustDecodeBounds = true;
                BitmapFactory.decodeFile(path, options);
                options.inSampleSize = calculateInSampleSize(options, width, height);
                options.inJustDecodeBounds = false;
                Bitmap src = BitmapFactory.decodeFile(path, options);
                Bitmap bitmap = createScaleBitmap(src, width, height);
                return bitmap;
        }

        private static int calculateInSampleSize(BitmapFactory.Options options,
                        int reqWidth, int reqHeight) {
                final int height = options.outHeight;
                final int width = options.outWidth;
                int inSampleSize = 1;
                if (height > reqHeight || width > reqWidth) {
                        final int halfHeight = height / 2;
                        final int halfWidth = width / 2;
                        while ((halfHeight / inSampleSize) > reqHeight
                                        && (halfWidth / inSampleSize) > reqWidth) {
                                inSampleSize *= 2;
                        }
                }
                return inSampleSize;
        }

        private static Bitmap createScaleBitmap(Bitmap src, int dstWidth,
                        int dstHeight) {
                Bitmap dst = Bitmap.createScaledBitmap(src, dstWidth, dstHeight, false);
                if (src != dst) { // 如果没有缩放,那么不回收
                        src.recycle(); // 释放Bitmap的native像素数组
                }
                return dst;
        }

        private void addBitmapToLruCache(String path, Bitmap bitmap) {
                if (getBitmapFromLruCache(path) == null) {
                        lruCache.put(path, bitmap);
                }
        }

        private Bitmap getBitmapFromLruCache(String path) {
                return lruCache.get(path);
        }

        public void setImageForImageView(String imagePath, ImageView imageView) {
                Bitmap bitmap = getBitmapFromLruCache(imagePath);
                if (bitmap != null) {
                        imageView.setImageBitmap(bitmap);
                }
        }

        public void loadBitmaps(List infos, View view, int width, int height,
                        int firstVisibleItem, int visibleItemCount) {
                try {
                        for (int i = firstVisibleItem; i < firstVisibleItem
                                        + visibleItemCount; i++) {
                                String name = infos.get(i).getName();
                                String path = infos.get(i).getPath();
                                boolean isDirectory = infos.get(i).getIsDirectory();
                                if ((!isDirectory)
                                                && (name.lastIndexOf(".") > 0)
                                                && (IMG_SUFFIX.contains(name.substring(name
                                                                .lastIndexOf(".") + 1)))) {
                                        Bitmap bitmap = getBitmapFromLruCache(path);
                                        if (bitmap == null) {
                                                bitmap = readThumbnailFromFile(name);
                                                if (bitmap == null) {
                                                        bitmap = getThumbnail(path, width, height);
                                                        final Bitmap copyBitmap = bitmap;
                                                        final String copyName = name;
                                                        new Thread() {
                                                                @Override
                                                                public void run() {
                                                                        // TODO Auto-generated method stub
                                                                        super.run();
                                                                        saveThumbnailToFile(copyBitmap, copyName);
                                                                }
                                                        }.start();
                                                }
                                                addBitmapToLruCache(path, bitmap);
                                        }
                                        ImageView imageView = (ImageView) view
                                                        .findViewWithTag(path);
                                        if (imageView != null && bitmap != null) {
                                                imageView.setImageBitmap(bitmap);
                                        }
                                }
                        }
                } catch (Exception e) {
                        e.printStackTrace();
                }
        }

        private void saveThumbnailToFile(Bitmap bitmap, String name) {
                File dir = new File(THUMBNAIL);
                if (!dir.exists()) {
                        dir.mkdirs();
                }
                File file = new File(THUMBNAIL + name);
                try {
                        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(
                                        new FileOutputStream(file));
                        bitmap.compress(Bitmap.CompressFormat.JPEG, 80,
                                        bufferedOutputStream);
                        bufferedOutputStream.flush();
                        bufferedOutputStream.close();
                } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                }
        }

        private Bitmap readThumbnailFromFile(String name) {
                File file = new File(THUMBNAIL + name);
                // 若该文件存在
                if (file.exists()) {
                        Bitmap bitmap = BitmapFactory.decodeFile(THUMBNAIL + name);
                        return bitmap;
                }
                return null;
        }

}
[/code]

         FileListViewAdapter:
         (ListViewAdapter)

      [code]package com.example.adapter;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.display.R;
import com.example.model.Info;

public class FileListViewAdapter extends BaseAdapter {

        private List datas = new ArrayList();
        private LayoutInflater layoutInflater;
        private SetImage setImage;

        private int[] logos = { R.drawable.unknow, R.drawable.jpg, R.drawable.png,
                        R.drawable.avi, R.drawable.doc, R.drawable.html, R.drawable.mp3,
                        R.drawable.pdf, R.drawable.ppt, R.drawable.rar, R.drawable.txt,
                        R.drawable.wav, R.drawable.zip };

        enum Type {
                FILE, DIRECTORY;
        }

        public FileListViewAdapter(Context context, List datas) {
                super();
                this.datas = datas;
                setImage = (SetImage) context;
                this.layoutInflater = (LayoutInflater) context
                                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        @Override
        public int getCount() {
                if (datas != null && !datas.isEmpty())
                        return datas.size();
                else
                        return 0;
        }

        @Override
        public Info getItem(int position) {
                return datas.get(position);
        }

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

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
                View view = convertView;
                final ViewHolder holder;
                if (convertView == null) {
                        view = layoutInflater.inflate(R.layout.item_list_file, null);
                        holder = new ViewHolder();
                        holder.imageViewFolder = (ImageView) view
                                        .findViewById(R.id.imageViewFolder);
                        holder.textViewPathName = (TextView) view
                                        .findViewById(R.id.textViewPathName);
                        holder.textViewNext = (TextView) view
                                        .findViewById(R.id.textViewNext);
                        holder.imageViewThumbnail = (ImageView) view
                                        .findViewById(R.id.imageViewThumbnail);
                        view.setTag(holder);
                } else {
                        holder = (ViewHolder) view.getTag();
                }

                Info info = getItem(position);
                String name = info.getName();
                String path = info.getPath();
                boolean isDirectory = info.getIsDirectory();
                if (isDirectory) {
                        holder.imageViewFolder.setBackgroundResource(R.drawable.folder);
                        holder.textViewNext.setVisibility(View.VISIBLE);
                        holder.imageViewThumbnail.setVisibility(View.GONE);
                } else {
                        String fileSuffix = getSuffix(name);
                        int index = 0;
                        switch (fileSuffix) {
                        case "jpg":
                                index = 1;
                                break;
                        case "png":
                                index = 2;
                                break;
                        case "avi":
                                index = 3;
                                break;
                        case "doc":
                                index = 4;
                                break;
                        case "html":
                                index = 5;
                                break;
                        case "mp3":
                                index = 6;
                                break;
                        case "pdf":
                                index = 7;
                                break;
                        case "ppt":
                                index = 8;
                                break;
                        case "rar":
                                index = 9;
                                break;
                        case "txt":
                                index = 10;
                                break;
                        case "wav":
                                index = 11;
                                break;
                        case "zip":
                                index = 12;
                                break;
                        default:
                                index = 0;
                                break;
                        }
                        if ((index == 1) || (index == 2)) {
                                holder.textViewNext.setVisibility(View.GONE);
                                holder.imageViewThumbnail.setVisibility(View.VISIBLE);
                        } else {
                                holder.textViewNext.setVisibility(View.INVISIBLE);
                                holder.imageViewThumbnail.setVisibility(View.GONE);
                        }
                        holder.imageViewFolder.setBackgroundResource(logos[index]);
                }
                holder.textViewPathName.setText(name);
                holder.imageViewThumbnail.setTag(path);
                setImage.setImage(path, holder.imageViewThumbnail);
                return view;
        }

        private class ViewHolder {
                private ImageView imageViewFolder;
                private TextView textViewPathName;
                private TextView textViewNext;
                private ImageView imageViewThumbnail;
        }

        private String getSuffix(String name) {
                if (name.lastIndexOf(".") < 0) // Don't have the suffix
                        return "";
                String fileSuffix = name.substring(name.lastIndexOf(".") + 1);
                return fileSuffix;
        }

        public interface SetImage {
                public void setImage(String path, ImageView imageView);
        }
}
[/code]

      原创,转载请注明出处  :o
      
      

      
           
     
     
      
   
我来回答
回答2个
时间排序
认可量排序

jp1017

0个粉丝

78

问答

0

专栏

9

资料

jp1017 2015-08-20 21:14:04
认可0
感谢分享:lol
我竟然看完了,尽管看不懂,里面有好多内功哦:(
要学的太多了,刚学了一个月,东西好多,刚看完8天的学习视频
还请给在下指点一二:P
这里先行谢过!:D

jp1017

0个粉丝

78

问答

0

专栏

9

资料

jp1017 2015-08-21 13:19:17
认可0
ListView和GridView里滚动监听方法setOnScrollListener介绍
[url]http://www.ebaina.com/bbs/forum.php?mod=viewthread&tid=8530&fromuid=8410[/url]
(出处: 易百纳论坛)
或将文件直接拖到这里
悬赏:
E币
网盘
* 网盘链接:
* 提取码:
悬赏:
E币

Markdown 语法

  • 加粗**内容**
  • 斜体*内容*
  • 删除线~~内容~~
  • 引用> 引用内容
  • 代码`代码`
  • 代码块```编程语言↵代码```
  • 链接[链接标题](url)
  • 无序列表- 内容
  • 有序列表1. 内容
  • 缩进内容
  • 图片![alt](url)
+ 添加网盘链接/附件

Markdown 语法

  • 加粗**内容**
  • 斜体*内容*
  • 删除线~~内容~~
  • 引用> 引用内容
  • 代码`代码`
  • 代码块```编程语言↵代码```
  • 链接[链接标题](url)
  • 无序列表- 内容
  • 有序列表1. 内容
  • 缩进内容
  • 图片![alt](url)
举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

易百纳技术社区