Страницы

Поиск по вопросам

среда, 11 декабря 2019 г.

Проблема с переполнением памяти при загрузке картинок с интернета Universal Image Loader

#java #android


Итак. Есть задача, есть отдельная активити для ленты новостей. Т.е. пользователь
прокручивает ленту и на экране выводятся посты с картинками и текстом. Всё это грузится
с определённого сайта и пользователь может листать ленту бесконечно прокрутив 100-200
или даже 1000 постов с картинками.

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

Но проблема в том, что после загрузки 30-40 картинок дальше ничего не грузилось (просто
вместо картинок была заглушка "Ошибка". А в логе писалось про outOfMemory. Я решил
попробовать другие библиотеки, в том числе и Picasso, Fresco. Но проблема оставалась.

Изначально алгоритм был таков - посты загружались пачками по 10 штук сразу в контейнер
(разметку для каждого поста я брал из отдельного лайоута и подключал при помощи LayoutInflater).
Потом подумав, что может проблема в этом способе, я решил использовать RecycleView
с адаптером. Но это тоже не помогло. Теперь я вообще переписал всё с нуля и для чистоты
эксперимента решил загружать на экран только картинки, без лишней разметки, адаптеров,
инфлаттеров и прочего. Только картинки, которые грузятся в динамически создаваемые
ImageView.

И всё равно проблема та же. Попробовав настройки UIL, я вроде бы добился более менее
приемлемого результата — теперь уже грузится не 30-40 картинок, а 100-130. Но этого
мало, надо чтобы можно было грузить бесконечное число картинок.

Если я правильно понимаю, то картинки просто не удаляются из памяти, когда я дальше
прокручиваю ленту и из-за этого память переполняется. На одном форуме мне сказали,
что подобные библиотеки (я правда в тот момент спрашивал про Picasso, но суть та же)
не предназначены для решения подобных задач и надо реализовывать выгрузку и загрузку
картинок из памяти самостоятельно.

Скажите, что я делаю не так?

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 iv = new ArrayList();

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

    


Ответы

Ответ 1



Ваша проблема в том, что вы используете ScrollView и в нём отображаете ваши картинки, добавляя их в разметку. Минус этого подхода в том, что ScrollView не убирает из памяти элементы разметки, которые не видны на экране. Отсюда и постоянно растущее потребление памяти и закономерный крах. Выход - использовать RecyclerView вместо ScrollView, ибо он убирает из памяти неотображаемые элементы разметки и, таким образом, не увеличивает в прогрессии объём потребляемой памяти.

Комментариев нет:

Отправить комментарий