关于在sqlite db中存储图像的java.lang.OutOfMemoryError

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

景点

public class Attractions extends ListActivity { DataBaseHandler db = new DataBaseHandler(this); ArrayList imageArry = new ArrayList(); List 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中指出了错误。

ContactImageAdapter

 public class ContactImageAdapter extends ArrayAdapter{ Context context; int layoutResourceId; // BcardImage data[] = null; ArrayList data=new ArrayList(); public ContactImageAdapter(Context context, int layoutResourceId, ArrayList 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的不同方式。 非常感谢!

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

  1. 联系对象有它。 所有加载的图像都在imageArry中,它是实例级variables。

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

毕竟,当在getView创建的每个新持有者都将被GC清除时,它是不可预测的。 通常对于这种情况,当对象在某些方法中创建为new时,然后传递回调用方法,该对象将仅由Full GC收集。

因此,正如“软件Sainath”所说,不要将图像存储在数据库中……也不要将它们保存在内存中。

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

我想那里的图像不会经常改变它们自己。 联系人的另一个图像将是另一个文件…

我刚才写了一个类似问题的答案, 这里是你可以检查的链接。 问题在于将图像保存到数据库中的方法,您不应该这样做。 而是将图像作为文件写在手机存储器上并进一步使用。

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

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

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

您可以按比例缩放图像并将其作为位图加载到适配器ImageView中。

在将图像加载为Bitmap之前,请获取高度和宽度。

 //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(); 

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

如果您不想自己处理重新缩放位图。 你可以使用同样的Glide或Picasso库。

我写了一篇关于使用Picasso在listview中加载图片的文章,如果你想使用毕加索,它将帮助你开始。

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

从网络加载图像时,请确保使用符合快速垃圾回收条件的参考types

 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> cache=Collections.synchronizedMap(new HashMap>()); public Bitmap get(String id){ if(!cache.containsKey(id)) return null; SoftReference ref=cache.get(id); return ref.get(); } public void put(String id, Bitmap bitmap){ cache.put(id, new SoftReference(bitmap)); } public void clear() { cache.clear(); } } 
  1. 不要将Image存储到Sqlite数据库。 这不是最好的做法。
  2. 而不是将图像保存到数据库,而是将图像保存在存储中,但如果要将它们保密,则将它们保存在app文件夹中并保存到数据库的路径。
  3. 使用http://square.github.io/picasso/或https://github.com/bumptech/glide等众所周知的库之一,它们可以提供有关内存问题以及一些很酷的过渡效果的极大帮助。 我建议使用Glide,因为它在内存限制较低的设备上运行良好