Java.lang.OutOfMemoryError для хранения изображений в sqlite db

Я хочу хранить изображения в моей базе данных. Также я хочу проверить, если изображение и заголовок уже находятся в базе данных. Если это так, он не добавит их в базу данных. Это мой класс.

Достопримечательности

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. Лог-код указал на ошибку в моей контактной таблице.

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. Большое спасибо!

Ответы

Ответ 1

У вас есть несколько мест, где полное изображение (при условии, что оно большое) хранит в памяти:

  • Объект контакта имеет его. Все загруженные изображения находятся в imageArry, который является переменной уровня экземпляра.

    public class Attractions extends ListActivity {
        DataBaseHandler db = new DataBaseHandler(this);
        ArrayList<Contact> imageArry = new ArrayList<Contact>();
    
  • в ContactImageAdapter.getView вы создаете другую копию изображения как BMP в объекте держателя и передаете его из метода. Итак, в какой-то момент у вас недостаточно памяти, чтобы сохранить их все. Также я уверен, что для decodeStream требуется еще немного памяти.

В конце концов, это не предсказуемо, когда каждый новый держатель, созданный в getView, будет очищен GC. Обычно для такой ситуации, когда объект, созданный как новый в некотором методе, затем передается обратно вызывающему методу, этот объект будет собираться только с помощью полного GC.

Итак, как сказал "Software Sainath", не храните изображения в базе данных... и не храните их в памяти.

P.S. Затем укажите ссылку на внешний файл изображения. Это также сэкономит время, чтобы загрузить представление. Изображение будет в кеше, и если пользователь хотя бы однажды его получит, он не будет снова проходить через сеть.

Я думаю, что изображения там не часто меняют их. другим изображением контакта будет другой файл...

Ответ 2

Я написал ответ на несколько схожую проблему некоторое время назад, здесь - это ссылка, которую вы можете проверить. Проблема заключается в подходе к сохранению изображений в базе данных, вы не должны этого делать. Вместо этого напишите изображения в виде файлов в памяти телефона и используйте их дальше.

Ответ 3

  • Не храните Image to Sqlite Database в конечном итоге, вы столкнетесь с ошибкой памяти после трех или пяти изображений, сохраненных в базе данных. Это не лучшая практика, максимальная память, выделенная для поля в строке в sqlite, меньше 3 мб, помните об этом.

  • Вместо сохранения изображений в базу данных, сохраните изображения внутри папки приложения, сохраните путь к базе данных.

  • Загрузите свое изображение так же, как и к вашему адаптеру изображения. Скажем, ваше изображение имеет разрешение 1280x720 и размер 2 МБ, это займет то же место в вашей памяти. Куча.

Вы можете либо масштабировать изображение, либо загружать его как растровое изображение в свой адаптер 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. Это связано с тем, что мы собираемся масштабировать изображение, соответствующее представлению изображения, с улучшением пикселя.

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, что поможет вам начать, если вы хотите использовать picasso.

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

Ответ 4

Пожалуйста, не забудьте использовать тип ссылочного типа с быстрой сборкой мусора при загрузке изображений из сети.

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

Ответ 5

  • Не храните Image to Sqlite Database. Это не лучшая практика.
  • Вместо сохранения изображений в базу данных держите изображения в хранилище, но если вы хотите сохранить их в приватном состоянии, сохраните их в папке вашего приложения и сохраните путь к базе данных.
  • Используйте одну из хорошо известных библиотек, таких как http://square.github.io/picasso/ или https://github.com/bumptech/glide, они предлагают большую помощь в вопросах памяти, а также некоторые интересные эффекты перехода. Я рекомендую использовать Glide, потому что он очень хорошо работает на устройстве с ограниченными ограничениями памяти.