java – Problem with memory overflow when loading images from the Internet Universal Image Loader

Question:

So. There is a task, there is a separate activity for the news feed. Those. the user scrolls the feed and posts with pictures and text are displayed on the screen. All this is loaded from a certain site and the user can scroll through the feed endlessly by scrolling 100-200 or even 1000 posts with pictures.

I decided to use UIL for these purposes, because from the description I realized that it not only downloads pictures from the Internet, but also manages the cache and unloads from memory what is not currently visible on the screen.

But the problem is that after loading 30-40 pictures, nothing was loaded further (just instead of pictures there was a stub "Error". And in the log it was written about outOfMemory. I decided to try other libraries, including Picasso, Fresco. But the problem remained .

Initially, the algorithm was as follows – posts were loaded in batches of 10 pieces at once into a container (I took the markup for each post from a separate layout and connected it using LayoutInflater). Then, thinking that the problem might be in this method, I decided to use RecycleView with an adapter. But that didn't help either. Now I generally rewrote everything from scratch and for the purity of the experiment I decided to load only pictures on the screen, without unnecessary markup, adapters, inflaters and other things. Only pictures that are loaded into dynamically created ImageViews.

And still the problem is the same. Having tried the UIL settings, I seem to have achieved a more or less acceptable result – now not 30-40 images are loaded, but 100-130. But this is not enough, it is necessary to be able to load an infinite number of pictures.

If I understand correctly, then the pictures are simply not removed from memory when I scroll the tape further and because of this, the memory overflows. On one forum, I was told that such libraries (I really asked about Picasso at that moment, but the essence is the same) are not designed to solve such problems and you need to implement unloading and loading images from memory yourself.

Tell me what am I doing wrong?

package com.freescribbler.freescribbler;

import android.app.Activity;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Toast;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.HashCodeFileNameGenerator;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.ImageScaleType;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.utils.StorageUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.util.ArrayList;


public class HomeActivity extends Activity {
    serverAPI serverAPI1; //Класс запросов к серверу
    SharedPreferences myPref; //Настройки доступа к серверу
    final String USER_CONNECTID = "user_connectid"; //ID
    final String USER_TOKEN = "user_token"; //Токен
    int lastPost = 0;
    Boolean morePostLoad = false;
    int Counter; //Счётчик выведенных постов
    ImageLoader imageLoader;
    int imgCount=0;
    ArrayList<ImageView> iv = new ArrayList<ImageView>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);

        serverAPI1 = new serverAPI(); //инициализируем класс
        loadLoginData();//загружаем данные доступа к серверу
        Counter = 0; //инициализируем счётчик


        imageLoader = ImageLoader.getInstance(); // Получили экземпляр
        imageLoader.init(ImageLoaderConfiguration.createDefault(HomeActivity.this));
        File cacheDir = StorageUtils.getCacheDirectory(this);
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this)
                .memoryCacheExtraOptions(480, 800) // default = device screen dimensions
                        //.diskCacheExtraOptions(480, 800, null)
                .threadPoolSize(3) // default
                .threadPriority(Thread.NORM_PRIORITY - 2) // default
                .tasksProcessingOrder(QueueProcessingType.FIFO) // default
                .denyCacheImageMultipleSizesInMemory()
                        //.memoryCache(new LruMemoryCache(5 * 1024 * 1024))
                        //.memoryCacheSize(5 * 1024 * 1024)
                .memoryCacheSizePercentage(13) // default
                .diskCache(new UnlimitedDiskCache(cacheDir)) // default
                .diskCacheSize(20 * 1024 * 1024)
                        //.diskCacheFileCount(100)
                .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default
                .writeDebugLogs()
                .build();

        //Picasso.with(HomeActivity.this).setIndicatorsEnabled(true); //Включаем дебаг у пикассо
        new AsyncTaskGetPosts().execute();// Запускаем асинхронное выполнение загрузки данных с сервера, с последующим выводом постов

        Button loadMore = (Button) findViewById(R.id.loadMoreButton);
        loadMore.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (morePostLoad) {
                    new AsyncTaskGetPostsMore().execute();
                }

            }
        });
    }

    //Загружаем данные для запросов к серверу
    void loadLoginData() {
        myPref = getSharedPreferences("myPref", MODE_PRIVATE);

        //Проверяем, существует ли токен
        if (myPref.contains(USER_TOKEN)) {
            serverAPI1.token = myPref.getString(USER_TOKEN, "");
            serverAPI1.connectid = myPref.getString(USER_CONNECTID, "");
            Log.e("Токен существует", serverAPI1.token);
        } else {
            serverAPI1.token = null;
            serverAPI1.connectid = null;
            Toast toast = Toast.makeText(getApplicationContext(),
                    "Пользователь не залогинен", Toast.LENGTH_LONG);
            toast.show();
            Log.e("Токена нет", "null");
        }
    }


    //Здесь мы получаем от сервера картинки
    public class AsyncTaskGetPosts extends AsyncTask<String, Void, String> {
        @Override
        //Создаём асинхронную задачу в фоне
        protected String doInBackground(String... params) {
            return serverAPI1.loadPosts(30); //Запрашиваем 30 постов
        }

        @Override
        //То что выполнится по завершении фонового процесса
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            //вызываем метод, где обрабатываем JSON и выводим картинки
            makePosts(result);
            morePostLoad = true;
        }
    }

    //Здесь мы получаем от сервера картинки
    public class AsyncTaskGetPostsMore extends AsyncTask<String, Void, String> {
        @Override
        //Создаём асинхронную задачу в фоне
        protected String doInBackground(String... params) {
            return serverAPI1.loadPosts(20, lastPost); //Запрашиваем 20 постов
        }

        @Override
        //То что выполнится по завершении фонового процесса
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            //вызываем метод, где обрабатываем JSON и выводим картинки
            makePosts(result);
            morePostLoad = true;
        }
    }


    //Метод вывода постов
    void makePosts(String postData) {
        try {
            //Создаём объект JSON
            JSONObject jsnResp = new JSONObject(postData);
            JSONArray jposts = jsnResp.getJSONArray("data");
            lastPost = jsnResp.getInt("lastpost");
            //Цикл вывода картинок
            for (int i = 0; i < jposts.length(); i++) {
                JSONObject curentPost = new JSONObject(jposts.getString(i));

                String picURL; //переменная для хранения ссылок на картинки
                //Узнаём дал ли сервер картинку
                if (curentPost.getString("pics").equals("-1")) {
                    //При негативном результате ссылка будет null
                    Log.d("PICS", "is -1");
                    picURL = null;
                } else {
                    //Позитивный результат
                    JSONArray picsInPost = curentPost.getJSONArray("pics");
                    JSONObject picTest = new JSONObject(picsInPost.getString(0));
                    //Вытаскиваем ссылку для картинки
                    picURL = picTest.getString("picname");
                    //Создаём ImageView
                    Log.d("IMGCOUNT", String.valueOf(imgCount));

                    iv.add(new ImageView(HomeActivity.this));


                    LinearLayout lL = (LinearLayout) findViewById(R.id.linearL);
                    LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                            RelativeLayout.LayoutParams.WRAP_CONTENT,
                            RelativeLayout.LayoutParams.WRAP_CONTENT);

                    lL.addView(iv.get(imgCount), lp); //Вставляем его в нужный Layout


                    //Загружаем картинку из интернета в ImageView
                    /*Picasso.with(HomeActivity.this)
                            .load(picURL) //ссылка
                            .placeholder(R.drawable.loading) //картинка загрузки
                            .error(R.drawable.error) //картинка ошибки
                            .into(iv); //сюда мы грузим картинку*/
                    DisplayImageOptions options = new DisplayImageOptions.Builder()
                            .showImageOnLoading(R.drawable.loading) // resource or drawable
                            .showImageOnFail(R.drawable.error) // resource or drawable
                                    //.resetViewBeforeLoading(false)  // default
                            .cacheInMemory(true) // default
                            .cacheOnDisk(true) // default
                            .imageScaleType(ImageScaleType.EXACTLY) // default
                            .bitmapConfig(Bitmap.Config.RGB_565) // default
                            .build();
                    imageLoader.displayImage(picURL, iv.get(imgCount), options);
                    imgCount++;
                }
                Log.d("COUNTER", String.valueOf(Counter));
                Counter++;
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }
}

Answer:

Your problem is that you are using a ScrollView and displaying your images in it, adding them to the markup. The disadvantage of this approach is that ScrollView does not remove from memory the markup elements that are not visible on the screen. Hence the ever-increasing consumption of memory and the natural collapse.

The solution is to use RecyclerView instead ScrollView , because it removes undisplayed markup elements from memory and thus does not progressively increase the amount of memory consumed.

Scroll to Top