Skip to content

Commit 6232e35

Browse files
authored
Merge pull request #627 from dbrant/master
Clean up image loading code, and switch to using Fresco.
2 parents 2bc15c5 + a9d58d4 commit 6232e35

File tree

7 files changed

+76
-251
lines changed

7 files changed

+76
-251
lines changed

app/build.gradle

Lines changed: 1 addition & 0 deletions
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

Lines changed: 12 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@
88
import android.content.Context;
99
import android.content.SharedPreferences;
1010
import android.content.pm.PackageManager;
11-
import android.database.sqlite.SQLiteDatabase;
12-
import android.graphics.Bitmap;
1311
import android.os.Build;
12+
import android.database.sqlite.SQLiteDatabase;
1413
import android.preference.PreferenceManager;
15-
import android.support.v4.util.LruCache;
1614

1715
import com.android.volley.RequestQueue;
1816
import com.android.volley.toolbox.BasicNetwork;
1917
import com.android.volley.toolbox.DiskBasedCache;
2018
import com.android.volley.toolbox.HurlStack;
19+
import com.facebook.drawee.backends.pipeline.Fresco;
2120
import com.facebook.stetho.Stetho;
2221
import com.nostra13.universalimageloader.cache.disc.impl.TotalSizeLimitedDiscCache;
2322
import com.nostra13.universalimageloader.core.ImageLoader;
@@ -188,51 +187,21 @@ public void onCreate() {
188187
.build();
189188
ImageLoader.getInstance().init(imageLoaderConfiguration);
190189

190+
Fresco.initialize(this);
191+
191192
// Initialize EventLogging
192193
EventLog.setApp(this);
193194

194-
// based off https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html
195-
// Cache for 1/8th of available VM memory
196-
long maxMem = Runtime.getRuntime().maxMemory();
197-
if (maxMem < 48L * 1024L * 1024L) {
198-
// Cache only one bitmap if VM memory is too small (such as Nexus One);
199-
Timber.d("Skipping bitmap cache; max mem is: %d", maxMem);
200-
imageCache = new LruCache<>(1);
201-
} else {
202-
int cacheSize = (int) (maxMem / (1024 * 8));
203-
Timber.d("Bitmap cache size %d from max mem %d", cacheSize, maxMem);
204-
imageCache = new LruCache<String, Bitmap>(cacheSize) {
205-
@Override
206-
protected int sizeOf(String key, Bitmap bitmap) {
207-
int bitmapSize;
208-
bitmapSize = bitmap.getByteCount();
209-
210-
// The cache size will be measured in kilobytes rather than number of items.
211-
return bitmapSize / 1024;
212-
}
213-
};
214-
}
215-
}
216-
217-
private com.android.volley.toolbox.ImageLoader imageLoader;
218-
private LruCache<String, Bitmap> imageCache;
195+
//For caching area -> categories
196+
cacheData = new CacheController();
219197

220-
public com.android.volley.toolbox.ImageLoader getImageLoader() {
221-
if(imageLoader == null) {
222-
imageLoader = new com.android.volley.toolbox.ImageLoader(getVolleyQueue(), new com.android.volley.toolbox.ImageLoader.ImageCache() {
223-
@Override
224-
public Bitmap getBitmap(String key) {
225-
return imageCache.get(key);
226-
}
198+
DiskBasedCache cache = new DiskBasedCache(getCacheDir(), 16 * 1024 * 1024);
199+
volleyQueue = new RequestQueue(cache, new BasicNetwork(new HurlStack()));
200+
volleyQueue.start();
201+
}
227202

228-
@Override
229-
public void putBitmap(String key, Bitmap bitmap) {
230-
imageCache.put(key, bitmap);
231-
}
232-
});
233-
imageLoader.setBatchedResponseDelay(0);
234-
}
235-
return imageLoader;
203+
public MWApi getApi() {
204+
return api;
236205
}
237206

238207
/**
Lines changed: 27 additions & 0 deletions
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.getInstance().getMWApi();
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+
}
Lines changed: 29 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,226 +1,64 @@
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;
247

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

30-
import fr.free.nrw.commons.contributions.Contribution;
31-
import fr.free.nrw.commons.contributions.ContributionsContentProvider;
32-
33-
public class MediaWikiImageView extends android.support.v7.widget.AppCompatImageView {
34-
35-
private Media mMedia;
36-
37-
private ImageLoader mImageLoader;
38-
39-
private ImageContainer mImageContainer;
40-
41-
private View loadingView;
42-
43-
private boolean isThumbnail;
10+
public class MediaWikiImageView extends SimpleDraweeView {
11+
private ThumbnailFetchTask currentThumbnailTask;
12+
LruCache<String, String> thumbnailUrlCache = new LruCache<>(1024);
4413

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

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

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

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

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

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

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

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

Lines changed: 3 additions & 1 deletion
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)