Skip to content

Commit bb49fb9

Browse files
committed
Clean up image loading code, and switch to using Fresco.
1 parent 1a1fc14 commit bb49fb9

File tree

7 files changed

+67
-253
lines changed

7 files changed

+67
-253
lines changed

app/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies {
2020
compile ('com.mapbox.mapboxsdk:mapbox-android-sdk:5.0.2@aar'){
2121
transitive=true
2222
}
23+
compile 'com.facebook.fresco:fresco:1.3.0'
2324
compile 'com.facebook.stetho:stetho:1.5.0'
2425

2526
testCompile 'junit:junit:4.12'

app/src/main/java/fr/free/nrw/commons/CommonsApplication.java

+3-45
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@
66
import android.accounts.OperationCanceledException;
77
import android.app.Application;
88
import android.content.pm.PackageManager;
9-
import android.graphics.Bitmap;
109
import android.os.Build;
11-
import android.support.v4.util.LruCache;
1210

1311
import com.android.volley.RequestQueue;
1412
import com.android.volley.toolbox.BasicNetwork;
1513
import com.android.volley.toolbox.DiskBasedCache;
1614
import com.android.volley.toolbox.HurlStack;
15+
import com.facebook.drawee.backends.pipeline.Fresco;
1716
import com.facebook.stetho.Stetho;
1817
import com.nostra13.universalimageloader.cache.disc.impl.TotalSizeLimitedDiscCache;
1918
import com.nostra13.universalimageloader.core.ImageLoader;
@@ -112,31 +111,11 @@ public void onCreate() {
112111
.build();
113112
ImageLoader.getInstance().init(imageLoaderConfiguration);
114113

114+
Fresco.initialize(this);
115+
115116
// Initialize EventLogging
116117
EventLog.setApp(this);
117118

118-
// based off https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
119-
// Cache for 1/8th of available VM memory
120-
long maxMem = Runtime.getRuntime().maxMemory();
121-
if (maxMem < 48L * 1024L * 1024L) {
122-
// Cache only one bitmap if VM memory is too small (such as Nexus One);
123-
Timber.d("Skipping bitmap cache; max mem is: %d", maxMem);
124-
imageCache = new LruCache<>(1);
125-
} else {
126-
int cacheSize = (int) (maxMem / (1024 * 8));
127-
Timber.d("Bitmap cache size %d from max mem %d", cacheSize, maxMem);
128-
imageCache = new LruCache<String, Bitmap>(cacheSize) {
129-
@Override
130-
protected int sizeOf(String key, Bitmap bitmap) {
131-
int bitmapSize;
132-
bitmapSize = bitmap.getByteCount();
133-
134-
// The cache size will be measured in kilobytes rather than number of items.
135-
return bitmapSize / 1024;
136-
}
137-
};
138-
}
139-
140119
//For caching area -> categories
141120
cacheData = new CacheController();
142121

@@ -145,27 +124,6 @@ protected int sizeOf(String key, Bitmap bitmap) {
145124
volleyQueue.start();
146125
}
147126

148-
private com.android.volley.toolbox.ImageLoader imageLoader;
149-
private LruCache<String, Bitmap> imageCache;
150-
151-
public com.android.volley.toolbox.ImageLoader getImageLoader() {
152-
if(imageLoader == null) {
153-
imageLoader = new com.android.volley.toolbox.ImageLoader(volleyQueue, new com.android.volley.toolbox.ImageLoader.ImageCache() {
154-
@Override
155-
public Bitmap getBitmap(String key) {
156-
return imageCache.get(key);
157-
}
158-
159-
@Override
160-
public void putBitmap(String key, Bitmap bitmap) {
161-
imageCache.put(key, bitmap);
162-
}
163-
});
164-
imageLoader.setBatchedResponseDelay(0);
165-
}
166-
return imageLoader;
167-
}
168-
169127
public MWApi getApi() {
170128
return api;
171129
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package fr.free.nrw.commons;
2+
3+
import android.os.AsyncTask;
4+
5+
import org.mediawiki.api.ApiResult;
6+
7+
class MediaThumbnailFetchTask extends AsyncTask<String, String, String> {
8+
private static final String THUMB_SIZE = "640";
9+
10+
@Override
11+
protected String doInBackground(String... params) {
12+
try {
13+
MWApi api = CommonsApplication.app.getApi();
14+
ApiResult result =api.action("query")
15+
.param("format", "xml")
16+
.param("prop", "imageinfo")
17+
.param("iiprop", "url")
18+
.param("iiurlwidth", THUMB_SIZE)
19+
.param("titles", params[0])
20+
.get();
21+
return result.getString("/api/query/pages/page/imageinfo/ii/@thumburl");
22+
} catch (Exception e) {
23+
// Do something better!
24+
}
25+
return null;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -1,227 +1,65 @@
1-
/**
2-
* Copyright (C) 2013 The Android Open Source Project
3-
*
4-
* Licensed under the Apache License, Version 2.0 (the "License");
5-
* you may not use this file except in compliance with the License.
6-
* You may obtain a copy of the License at
7-
*
8-
* http://www.apache.org/licenses/LICENSE-2.0
9-
*
10-
* Unless required by applicable law or agreed to in writing, software
11-
* distributed under the License is distributed on an "AS IS" BASIS,
12-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13-
* See the License for the specific language governing permissions and
14-
* limitations under the License.
15-
*/
161
package fr.free.nrw.commons;
172

183
import android.content.Context;
19-
import android.content.res.TypedArray;
20-
import android.graphics.drawable.BitmapDrawable;
21-
import android.text.TextUtils;
4+
import android.support.annotation.Nullable;
5+
import android.support.v4.util.LruCache;
226
import android.util.AttributeSet;
23-
import android.view.View;
24-
import android.widget.ImageView;
257

26-
import com.android.volley.VolleyError;
27-
import com.android.volley.toolbox.ImageLoader;
28-
import com.android.volley.toolbox.ImageLoader.ImageContainer;
29-
import com.android.volley.toolbox.ImageLoader.ImageListener;
8+
import com.facebook.drawee.view.SimpleDraweeView;
309

31-
import fr.free.nrw.commons.contributions.Contribution;
32-
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
10+
public class MediaWikiImageView extends SimpleDraweeView {
11+
private ThumbnailFetchTask currentThumbnailTask;
12+
LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024);
3313

34-
public class MediaWikiImageView extends ImageView {
35-
36-
private Media mMedia;
37-
38-
private ImageLoader mImageLoader;
39-
40-
private ImageContainer mImageContainer;
41-
42-
private View loadingView;
43-
44-
private boolean isThumbnail;
4514

4615
public MediaWikiImageView(Context context) {
4716
this(context, null);
4817
}
4918

5019
public MediaWikiImageView(Context context, AttributeSet attrs) {
5120
this(context, attrs, 0);
52-
TypedArray actualAttrs = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MediaWikiImageView, 0, 0);
53-
isThumbnail = actualAttrs.getBoolean(0, false);
54-
actualAttrs.recycle();
5521
}
5622

5723
public MediaWikiImageView(Context context, AttributeSet attrs, int defStyle) {
5824
super(context, attrs, defStyle);
5925
}
6026

61-
public void setMedia(Media media, ImageLoader imageLoader) {
62-
this.mMedia = media;
63-
mImageLoader = imageLoader;
64-
loadImageIfNecessary(false);
65-
}
66-
67-
public void setLoadingView(View loadingView) {
68-
this.loadingView = loadingView;
69-
}
70-
71-
public View getLoadingView() {
72-
return loadingView;
73-
}
74-
75-
private void loadImageIfNecessary(final boolean isInLayoutPass) {
76-
loadImageIfNecessary(isInLayoutPass, false);
77-
}
78-
79-
private void loadImageIfNecessary(final boolean isInLayoutPass, final boolean tryOriginal) {
80-
int width = getWidth();
81-
int height = getHeight();
82-
83-
// if the view's bounds aren't known yet, hold off on loading the image.
84-
if (width == 0 && height == 0) {
85-
return;
27+
public void setMedia(Media media) {
28+
if (currentThumbnailTask != null) {
29+
currentThumbnailTask.cancel(true);
8630
}
87-
88-
if(mMedia == null) {
31+
setImageURI((String) null);
32+
if(media == null) {
8933
return;
9034
}
9135

92-
// Do not count for density when loading thumbnails.
93-
// FIXME: Use another 'algorithm' that doesn't punish low res devices
94-
if(isThumbnail) {
95-
float dpFactor = Math.max(getResources().getDisplayMetrics().density, 1.0f);
96-
width = (int) (width / dpFactor);
97-
height = (int) (height / dpFactor);
98-
}
99-
100-
final String mUrl;
101-
if(tryOriginal) {
102-
mUrl = mMedia.getImageUrl();
36+
if (thumbnailUrlCache.get(media.getFilename()) != null) {
37+
setImageUrl(thumbnailUrlCache.get(media.getFilename()));
10338
} else {
104-
// Round it to the nearest 320
105-
// Possible a similar size image has already been generated.
106-
// Reduces Server cache fragmentation, also increases chance of cache hit
107-
// If width is less than 320, we round up to 320
108-
int bucketedWidth = width <= 320 ? 320 : Math.round((float)width / 320.0f) * 320;
109-
if(mMedia.getWidth() != 0 && mMedia.getWidth() < bucketedWidth) {
110-
// If we know that the width of the image is lesser than the required width
111-
// We don't even try to load the thumbnai, go directly to the source
112-
loadImageIfNecessary(isInLayoutPass, true);
113-
return;
114-
} else {
115-
mUrl = mMedia.getThumbnailUrl(bucketedWidth);
116-
}
39+
currentThumbnailTask = new ThumbnailFetchTask();
40+
currentThumbnailTask.execute(media.getFilename());
11741
}
118-
119-
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
120-
// currently loaded image.
121-
if (TextUtils.isEmpty(mUrl)) {
122-
if (mImageContainer != null) {
123-
mImageContainer.cancelRequest();
124-
mImageContainer = null;
125-
}
126-
setImageBitmap(null);
127-
return;
128-
}
129-
130-
// Don't repeat work. Prevents onLayout cascades
131-
// We ignore it if the image request was for either the current URL of for the full URL
132-
// Since the full URL is always the second, and
133-
if (mImageContainer != null && mImageContainer.getRequestUrl() != null) {
134-
if (mImageContainer.getRequestUrl().equals(mMedia.getImageUrl()) || mImageContainer.getRequestUrl().equals(mUrl)) {
135-
return;
136-
} else {
137-
// if there is a pre-existing request, cancel it if it's fetching a different URL.
138-
mImageContainer.cancelRequest();
139-
BitmapDrawable actualDrawable = (BitmapDrawable)getDrawable();
140-
if(actualDrawable != null && actualDrawable.getBitmap() != null) {
141-
setImageBitmap(null);
142-
if(loadingView != null) {
143-
loadingView.setVisibility(View.VISIBLE);
144-
}
145-
}
146-
}
147-
}
148-
149-
// The pre-existing content of this view didn't match the current URL. Load the new image
150-
// from the network.
151-
ImageContainer newContainer = mImageLoader.get(mUrl,
152-
new ImageListener() {
153-
@Override
154-
public void onErrorResponse(final VolleyError error) {
155-
if(!tryOriginal) {
156-
post(new Runnable() {
157-
@Override
158-
public void run() {
159-
loadImageIfNecessary(false, true);
160-
}
161-
});
162-
}
163-
}
164-
165-
@Override
166-
public void onResponse(final ImageContainer response, boolean isImmediate) {
167-
// If this was an immediate response that was delivered inside of a layout
168-
// pass do not set the image immediately as it will trigger a requestLayout
169-
// inside of a layout. Instead, defer setting the image by posting back to
170-
// the main thread.
171-
if (isImmediate && isInLayoutPass) {
172-
post(new Runnable() {
173-
@Override
174-
public void run() {
175-
onResponse(response, false);
176-
}
177-
});
178-
return;
179-
}
180-
181-
if (response.getBitmap() != null) {
182-
setImageBitmap(response.getBitmap());
183-
if(tryOriginal && mMedia instanceof Contribution && (response.getBitmap().getWidth() > mMedia.getWidth() || response.getBitmap().getHeight() > mMedia.getHeight())) {
184-
// If there is no width information for this image, save it. This speeds up image loading massively for smaller images
185-
mMedia.setHeight(response.getBitmap().getHeight());
186-
mMedia.setWidth(response.getBitmap().getWidth());
187-
((Contribution)mMedia).setContentProviderClient(MediaWikiImageView.this.getContext().getContentResolver().acquireContentProviderClient(ContributionsContentProvider.AUTHORITY));
188-
((Contribution)mMedia).save();
189-
}
190-
if(loadingView != null) {
191-
loadingView.setVisibility(View.GONE);
192-
}
193-
} else {
194-
// I'm not really sure where this would hit but not onError
195-
}
196-
}
197-
});
198-
199-
// update the ImageContainer to be the new bitmap container.
200-
mImageContainer = newContainer;
201-
}
202-
203-
@Override
204-
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
205-
super.onLayout(changed, left, top, right, bottom);
206-
loadImageIfNecessary(true);
20742
}
20843

20944
@Override
21045
protected void onDetachedFromWindow() {
211-
if (mImageContainer != null) {
212-
// If the view was bound to an image request, cancel it and clear
213-
// out the image from the view.
214-
mImageContainer.cancelRequest();
215-
setImageBitmap(null);
216-
// also clear out the container so we can reload the image if necessary.
217-
mImageContainer = null;
46+
if (currentThumbnailTask != null) {
47+
currentThumbnailTask.cancel(true);
21848
}
21949
super.onDetachedFromWindow();
22050
}
22151

222-
@Override
223-
protected void drawableStateChanged() {
224-
super.drawableStateChanged();
225-
invalidate();
52+
private void setImageUrl(@Nullable String url) {
53+
setImageURI(url);
54+
}
55+
56+
private class ThumbnailFetchTask extends MediaThumbnailFetchTask {
57+
@Override
58+
protected void onPostExecute(String result) {
59+
if (isCancelled()) {
60+
return;
61+
}
62+
setImageUrl(result);
63+
}
22664
}
22765
}

app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ public void onCreate(Bundle savedInstanceState) {
2020
super.onCreate(savedInstanceState);
2121
setContentView(R.layout.activity_welcome);
2222

23-
getSupportActionBar().hide();
23+
if (getSupportActionBar() != null) {
24+
getSupportActionBar().hide();
25+
}
2426
ButterKnife.bind(this);
2527

2628
setUpAdapter();

0 commit comments

Comments
 (0)