Pull to refresh

Comments 28

Попробую в следующем проекте, пока в закладки закину. Но описание звучит интересно.
Насколько я вижу — сделано достаточно много, но не хватает двух важных вещей:
1) Использования опций кеширования протокола HTTP
2) поддержка HTTP persistent connection
Проекту есть куда развиваться, поэтому это возможно его будущие фичи :) Спасибо, надо будет подумать над их реализацией.
Кстати любые предложения по улучшению функциональности ImageLoader'а приветствуются. Возможно кто-то имеет свои use-case'ы, для которых ImageLoader можно адаптировать.
под какой лицензией (BSD, Apache, GPL) вы это выложили? на github ничего не написано
Под лицензией «юзай, изменяй, распространяй без ограничений» :) Но для формальности пусть будет Apache License v2.0.
Хотя для совместмости с GPL пожалуй BSD будет лучше.
Неплохо. Для универсализации на мой взгляд было бы круто:
1. Возможность переопределять размер картинки динамически (а не целиком для всех, соответственно сделать хеш урл + размер). Это полезно когда, например, дисплеим картинки разного размера для планшетников/телефонов
2. Возможность скалить картинку не только кратно 2-ке но и абсолютно и относительно (если размер задан как в dip'ах)
Да наверно пожалуй и всё.

Немного смутила только работа с сетью:
InputStream is = new URL(imageUrl).openStream();
Мне кажется тут могут быть накладки типа подвисаний при обрыве.

Я бы сделал с инитом синглтона клиента в аппликейшен, что то типа:
public static void setClient() {
HttpParams params = new BasicHttpParams();

// Turn off stale checking. Our connections break all the time anyway,
// and it's not worth it to pay the penalty of checking every time.
HttpConnectionParams.setStaleCheckingEnabled(params, false);

// Default connection and socket timeout of 10 seconds. Tweak to taste.
HttpConnectionParams.setConnectionTimeout(params, 10 * 1000);
HttpConnectionParams.setSoTimeout(params, 10 * 1000);
HttpConnectionParams.setSocketBufferSize(params, 8192);

// Don't handle redirects -- return them to the caller. Our code
// often wants to re-POST after a redirect, which we must do ourselves.
HttpClientParams.setRedirecting(params, false);
// Set the specified user agent and register standard protocols.
HttpProtocolParams.setUserAgent(params, "bigbuzzy business");
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));

ClientConnectionManager manager = new ThreadSafeClientConnManager(params, schemeRegistry);

AndroidApplication.client = new DefaultHttpClient(manager,params);
}

//И загрузку через него (нет сети - берем из кеша иначе грузим):

public static String retrieve(String url, boolean cache) {

StringBuilder sb = new StringBuilder();
sb.append(AndroidApplication.DOMEN);
sb.append(url);
url = sb.toString();
Log.d("retrieve", url);
String md5 = Md5.md5(url);
File casheDir = AndroidApplication.cacheDir;//context.getCacheDir();

File f = null;
if (casheDir!=null && cache) {
f = new File(casheDir, md5);
final long time = new Date().getTime() / 1000;
if (f.exists()) {
if ((f.lastModified()/1000+600)>time) {
return readFile(f);
}
}
}

HttpGet getRequest = new HttpGet(url);

try {
HttpResponse getResponse = AndroidApplication.getClient().execute(getRequest);
final int statusCode = getResponse.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
Log.e("statusCode",statusCode+"");
}

HttpEntity getResponseEntity = getResponse.getEntity();


if (getResponseEntity != null && statusCode == HttpStatus.SC_OK) {

String s = EntityUtils.toString(getResponseEntity);
if (statusCode != HttpStatus.SC_OK && f!=null) {
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(f));
os.write(s.getBytes());
os.close();
}
return s;
}

}
catch (IOException e) {
getRequest.abort();
if (f!=null) {
if (f.exists()) {
return readFile(f);
}
}
Log.e("NetUtils error",e.toString());
}
return null;
}

public static String readFile(File file) {
String data = null;
try {
FileInputStream fis = null;
InputStreamReader isr = null;

fis = new FileInputStream(file);
isr = new InputStreamReader(fis);
char[] inputBuffer;
inputBuffer = new char[fis.available()];
isr.read(inputBuffer);
data = new String(inputBuffer);
isr.close();
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
return data;
}

ой как расколбасило то тег сурс( Сожалею…
Если я правильно понял первый пункт — это для того если уменьшенная картинка уже есть в кэше в памяти, а мы хотим ее отобразить в ImageView больше предыдущего по размерам. Справедливо. В «дисковом» кэше хранятся полноразмерные картинки, если что.
Насчет второго пункта, гугло-доки гласят:
«Note: the decoder will try to fulfill this request, but the resulting bitmap may have different dimensions that precisely what has been requested. Also, powers of 2 are often faster/easier for the decoder to honor. „
И я боюсь что декодирование до точных размеров снизит производительность, может даже и существенно. Тут, пожалуй, можно отдать данное решение на выбор разработчику, что для него приоритетнее: память или процессорное время.
Придирки к Cache:

1. Зачем поле softMap объявлено protected? Даже если нужен доступ у наследников (в данном случае я думаю не нужен) лучше написать модификатор private и добавить protected метод getCache

2. Потенциальный NPE в методе get(): не сихронизованы методы clear() и get(). Более подробно: cointaisKey() возвращает true, вызывается метод clear(), get() от Map возвращает null.

Решение без synchronized: внутри метода Cache#get() использовать сразу Map#get() и проверять результат на null. В таком случае нужно поле Map нужно делать private и запрещать любые доступы к нему извне


Возможно в вашем случае это не критично, но для других пользователей этого кода может стать проблемой.
3. Фиговый Singleton из ImageLoader — возможна повторная инициализация (нет синхронизации)

4. PhotosQueue#clean(): гораздо красивее и более читабельно писать такие шутки через iterator:
Iterator<ImageView> it = photosToLoad.iterator();
while ( it.hasNext() ) {
   final ImageView imageView = it.next();
   if ( imageView == imageForRemove ) {
      it.remove();
   }
}

В вашем случае, по-моему, возможно падение с ConcurrentModificationException
5. Я не знаю как у вас используется FileUtils#copyStream(), но, что-то мне подсказывает, что скрывать IoException не правильно. Достаточно задать вопрос: будет ли приложение работать дальше нормально, если потоки не откопировались?
Работать будет, но не совсем нормально :)
Спасибо за замечания, все учту.
Хочется иметь возможность остановить загрузку во время прокрутки списка
Всмысле приостановить загрузку пока список мотается? Зачем?
Есть мысль, что так UI будет более отзывчивый.
Нигде не заметил bitmap.recycle() после превышения размера кеша, который держит картинки в памяти,
то что вы почистили ссылки на bitmap, даст работу для GC в вируальной машине.
Сами же bitmaps лежат вне основной памяти java-машины.
При превышении размера кэша удаляется «сильная» ссылка на Bitmap, но остается «слабая». Я не знаю, когда Bitmap будет собран сборщиком, а до этого делать recycle() ему нельзя, т.к. он может быть ещё переиспользован. В окончательном стирании Bitmap'ов полагаюсь на Android, ибо
"This is an advanced call, and normally need not be called, since the normal GC process will free up this memory when there are no more references to this bitmap."
Также советую взглянуть на опции декодирования картинки inPurgeable и inInputShareable — они имеют отношение к тому как Bitmap управляет ресурсами.
Ох, давно это было, но смотрели мы на эти опции. Почему-то не заюзали, возможно были причины. Но, пожалуй, ещё раз поизучаю этот вопрос. Спасибо.
Вот реализация, которой я пользовался. В ваш код особо не смотрел, но общее наверняка есть (это не намек на плагиат)http://stackoverflow.com/a/3068012/423868
Этот проект (LazyList), как и мой, базировался на LazyImageLoader'e (ссылку на который я давал в статье). Там (в LazyList) приведена в порядок работа с потоками, своя реализация кэша, и другие мелкие улучшения. Кое-что там действительно можно подсмотреть :) Но, на мой взгляд, UniversalImageLoader более гибкий, ибо с этой целью он разрабатывается.
Хотя нет, судя по всему этот проект и есть родоначальник всего. А индус (по упомянутой мной ссылке) просто скопипастил его в свой блог.
А не хотите сделать pom и залить на какой-нибудь maven-репозиторий?
Не особо дружу с Maven'ом, но не в этом дело.
На текущий момент, я считаю, проект не готов в качестве отдельной библиотеки. Сейчас многие параметры ImageLoader'а можно настраивать под себя (они у меня сейчас в Constants). А когда все задуманные фичи будут завершены, и я придумаю красивый способ предоставить настройку ImageLoader'а не меняя исходники — тогда, почему бы и нет, можно и в maven-репозиторий закинуть.
Вызываю
ImageView imageView = ...
String imageUrl = "http://site.com/image.png"; // or "file:///mnt/sdcard/images/image.jpg"

// Get singletone instance of ImageLoader
ImageLoader imageLoader = ImageLoader.getInstance();
// Initialize ImageLoader with configuration. Do it once.
imageLoader.init(ImageLoaderConfiguration.createDefault(context));
// Load and display image asynchronously
imageLoader.displayImage(imageUrl, imageView);


Из кастомного адаптера ListView… ругается на контекст, какой ему контекст нужен не понимаю.
И как вообще с этой кучей классов не сделать из своего простенького приложения помойку?

Здравствуйте, при создании ImageView в конструктор надо передавать текущий Activity, а не getApplicationContext().
Если у вас аллергия на большое количество классов — используйте jar-ку.
Sign up to leave a comment.

Articles