Страницы

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

вторник, 16 октября 2018 г.

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

Итак. Есть задача, есть отдельная активити для ленты новостей. Т.е. пользователь прокручивает ленту и на экране выводятся посты с картинками и текстом. Всё это грузится с определённого сайта и пользователь может листать ленту бесконечно прокрутив 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(); } } }


Ответ

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

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

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