【问题标题】:java.lang.OutOfMemoryError on storing images in sqlite dbjava.lang.OutOfMemoryError 在 sqlite db 中存储图像
【发布时间】:2013-12-04 05:41:39
【问题描述】:

我想将图像存储在我的数据库中。我还想检查图像和标题是否已经在数据库中。如果是这样,它不会将它们添加到数据库中。这是我的课。

景点

public class Attractions extends ListActivity {
DataBaseHandler db = new DataBaseHandler(this);
ArrayList<Contact> imageArry = new ArrayList<Contact>();
List<Contact> contacts;
ContactImageAdapter adapter;
int ctr, loaded; 
int [] landmarkImages={R.drawable.oblation,R.drawable.eastwood,R.drawable.ecopark,R.drawable.circle};
String []landmarkDetails = { "Oblation", "Eastwood", "Ecopark", "QC Circle"};

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_attractions);
    ctr  = db.checkContact(landmarkDetails[loaded]);



    // get image from drawable

    /**
     * CRUD Operations
     * */
    // Inserting Contacts
    Log.d("Insert: ", "Inserting ..");


    for(loaded=0; loaded <landmarkDetails.length;loaded++){

        Bitmap image = BitmapFactory.decodeResource(getResources(),
                landmarkImages[loaded]);


        // convert bitmap to byte
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        byte imageInByte[] = stream.toByteArray();
        Log.d("Going to load images", "Image "+ loaded);

        Log.d("Goind to load objects", "loading");

        if(ctr == 0){
            Log.d("Nothing Loaded", "Loading Now");
            db.addContact(new Contact(landmarkDetails[loaded], imageInByte));}
            Log.d(landmarkDetails[loaded], "Loaded!");
            image.recycle();
    }
    loadFromDb();


}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.attractions, menu);


    return true;
}

public void loadFromDb(){
    // Reading all contacts from database
            contacts = db.getAllContacts();
            for (Contact cn : contacts) {
                String log = "ID:" + cn.getID() + " Name: " + cn.getName()
                        + " ,Image: " + cn.getImage();

                // Writing Contacts to log
                Log.d("Result: ", log);
                //add contacts data in arrayList
                imageArry.add(cn);

            }
            adapter = new ContactImageAdapter(this, R.layout.screen_list,
                    imageArry);
            ListView dataList = (ListView) findViewById(android.R.id.list);
            dataList.setAdapter(adapter);

}

public void onPause(){
    super.onPause();
}

public void onResume(){
    super.onResume();

}


}

它在模拟器上运行良好,但我尝试在我的 S4 上进行测试,然后在尝试了 3 次上这个课程后,它强制停止。我用usb调试试过了,logcat显示java.lang.outofmemoryerror。 logcat 指出了我的联系人图像适配器中的错误。

ContactImageAdapter

public class ContactImageAdapter extends ArrayAdapter<Contact>{
 Context context;
    int layoutResourceId;   
   // BcardImage data[] = null;
    ArrayList<Contact> data=new ArrayList<Contact>();
    public ContactImageAdapter(Context context, int layoutResourceId, ArrayList<Contact> data) {
        super(context, layoutResourceId, data);
        this.layoutResourceId = layoutResourceId;
        this.context = context;
        this.data = data;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        ImageHolder holder = null;

        if(row == null)
        {
            LayoutInflater inflater = ((Activity)context).getLayoutInflater();
            row = inflater.inflate(layoutResourceId, parent, false);

            holder = new ImageHolder();
            holder.txtTitle = (TextView)row.findViewById(R.id.txtTitle);
            holder.imgIcon = (ImageView)row.findViewById(R.id.imgIcon);
            row.setTag(holder);
        }
        else
        {
            holder = (ImageHolder)row.getTag();
        }

        Contact picture = data.get(position);
        holder.txtTitle.setText(picture._name);
        //convert byte to bitmap take from contact class

        byte[] outImage=picture._image;
        ByteArrayInputStream imageStream = new ByteArrayInputStream(outImage);
        Bitmap theImage = BitmapFactory.decodeStream(imageStream);
        holder.imgIcon.setImageBitmap(theImage);
       return row;

    }

    static class ImageHolder
    {
        ImageView imgIcon;
        TextView txtTitle;
    }
}

并指向这条线Bitmap theImage = BitmapFactory.decodeStream(imageStream);

我对管理图像和存储图像知之甚少(几乎没有)。我也启用了android:largeHeap,但多次尝试仍然强制关闭。我希望有人可以帮助我解决这个问题,或者至少向我展示一种将文本和图像存储到 sqlite db 的不同方式。非常感谢!

【问题讨论】:

  • 一般建议将文件存储在App Directory(外部)中,并根据需要在数据库中维护文件路径插入图像和从数据库中检索图像是复杂的操作
  • 图片有多大?也许您需要在存储之前调整它的大小并压缩它。作为一名业余摄影师,我知道您可以将 jpg 压缩到至少 80%,而对质量没有明显影响,在大多数情况下也可以压缩到 70%(假设您不在同一张图像上多次执行此操作)。您目前拥有 100% 的质量比。如果您调整大小并进行压缩,这可能会对所需的大小产生很大的影响,从而影响所需的内存。
  • 检查所有图片有多少mb。如果它更大,你会得到oom error
  • 向任何数据库中添加图像或大块,无论您使用的是 SQLite、Oracle 还是 MS SQL Server,都是一个糟糕的想法,它将导致数据库的所有表中的巨大性能损失。

标签: java android sqlite bitmap out-of-memory


【解决方案1】:
  1. 最后不要将图像存储到 Sqlite 数据库,在将三或五个图像保存到数据库后会遇到内存不足的错误。这不是最佳实践,在 sqlite 中为连续字段分配的最大内存小于 3mb,请注意这一点。

  2. 不要将图像保存到数据库,而是将图像保存在您的应用文件夹中,保存到数据库的路径。

  3. 您正在将图像原样加载到图像适配器。假设您的图像是 1280x720 分辨率和 2mb 大小,它将在您的内存堆中占用相同的空间。

您可以像这样缩小图像并将其作为位图加载到适配器 ImageView。

在将图像加载为位图之前,先获取它的高度和宽度。

//Code read the image and give you image height and width.it won't load your bitmap. 


  BitmapFactory.Options option = new BitmapFactory.Options();
                option.inJustDecodeBounds = true;
                BitmapFactory.decodeFile(your_image_url,option);
                int image_original_width = option.outHeight;
                int image_original_height = option.outWidth;

现在要缩小图像,您必须知道 ImageView 的宽度和高度。这是因为我们将缩小与 imageview 匹配的图像与像素完美。

int image_view_width = image_view.getWidht();
 int image_view_height = image_view.getHeight();
 int new_width;
 int new_height;

float scaled_width;
 if(image_original_width>image_view_width)
{     //if the image_view width is lesser than original_image_width ,you have to scaled down the image.  
     scale_value =(float)image_original_width/(float)image_view_width; 
     new_width = image_original_width/scaled_value;
     new_height = image_orignal_height/scale_value
}
else
{
  // use the image_view width and height as sacling value widht and height;
      new_width = image_view_width;
      new_height = image_view_height;
}

现在缩小您的位图并像这样加载它。

  // this will load a bitmap with 1/4 the size of the original one.
   // this to lower your bitmap memory occupation in heap. 
   BitmapFactory.Options option = new BitmapFactory.Options();
   option.inSampleSize = 4;
   Bitmap current_bitmap = BitmapFactory.decodeFile(image_url,option);
   Bitmap scaled_bitmap =      Bitmap.createScaledBitmap(current_bitmap,new_width,new_height,true);
holder.imgIcon.setImageBitmap(scaled_bitmap);
//release memory occupied by current_bitmap in heap, as we are no longer using it. 
current_bitmap.recycle();

如果您想了解更多关于位图和内存的信息,请查看link

如果您不想自己处理重新缩放位图。您可以使用 GlidePicasso 库,它们的作用相同。

我写了一篇关于使用毕加索在列表视图中加载图像的文章,如果你想使用毕加索,这将帮助你开始。

http://codex2android.blogspot.in/2015/11/picasso-android-example.html

【讨论】:

    【解决方案2】:
    1. 不要将图像存储到 Sqlite 数据库。这不是最佳做法。
    2. 不要将图像保存到数据库中,而是将图像保存在存储中,但如果您想将它们保密,请将它们保存在您的应用文件夹中并保存到数据库的路径。
    3. 使用像http://square.github.io/picasso/https://github.com/bumptech/glide 这样的知名库之一,它们对内存问题和一些很酷的过渡效果有很大帮助。 我推荐使用 Glide,因为它在内存限制较低的设备上运行良好

    【讨论】:

      【解决方案3】:

      从网络加载图像时,请确保使用快速垃圾回收合格的引用类型

      import java.lang.ref.SoftReference;
      import java.util.Collections;
      import java.util.HashMap;
      import java.util.Map;
      import android.graphics.Bitmap;
        
      public class MemoryCache {
          private Map<String, SoftReference<Bitmap>> cache=Collections.synchronizedMap(new HashMap<String, SoftReference<Bitmap>>());
        
          public Bitmap get(String id){
              if(!cache.containsKey(id))
                  return null;
              SoftReference<Bitmap> ref=cache.get(id);
              return ref.get();
          }
        
          public void put(String id, Bitmap bitmap){
              cache.put(id, new SoftReference<Bitmap>(bitmap));
          }
        
          public void clear() {
              cache.clear();
          }
      }

      【讨论】:

        【解决方案4】:

        前段时间我写了一个类似问题的答案,here 是您可以查看的链接。问题在于将图像保存到数据库中的方法,您不应该这样做。相反,将图像作为文件写入手机内存并进一步使用。

        【讨论】:

          【解决方案5】:

          你有多个地方保存整个图像(假设它很大)在内存中:

          1. 接触对象有它。所有加载的图像都在 imageArry 中,它是实例级变量。

            public class Attractions extends ListActivity {
                DataBaseHandler db = new DataBaseHandler(this);
                ArrayList<Contact> imageArry = new ArrayList<Contact>();
            
          2. ContactImageAdapter.getView 方法中,您在holder 对象中创建另一个图像副本作为BMP,并将其传递到方法之外。 因此,在某些时候,您没有足够的内存来保存所有这些。我还确定decodeStream 需要更多内存才能执行。

          毕竟,在getView 中创建的每个新持有人何时会被 GC 清理是无法预测的。 通常对于这种情况,当对象在某个方法中创建为新对象,然后传递回调用方法时,该对象只会被 Full GC 收集。

          所以,正如“Software Sainath”所说,不要将图像存储在数据库中...... 也不要将它们保存在内存中。

          附:然后向视图提供外部图像文件的链接。这也将节省加载视图的时间。图片会在缓存中,如果用户至少得到一次,就不会再次通过网络。

          我猜图像不会经常自行更改。联系人的另一张图片将是另一个文件……

          【讨论】:

            猜你喜欢
            • 2015-01-26
            • 2012-04-09
            • 2014-10-31
            • 2021-04-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2016-07-22
            • 1970-01-01
            相关资源
            最近更新 更多