Merge branch 'v2.0.0' into jlatexmath
This commit is contained in:
commit
3cadd6c7e6
@ -77,6 +77,12 @@ AsyncDrawableLoader.builder()
|
|||||||
|
|
||||||
If not provided explicitly, default `new OkHttpClient()` will be used
|
If not provided explicitly, default `new OkHttpClient()` will be used
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
This configuration option is scheduled to be removed in `3.0.0` version,
|
||||||
|
use `NetworkSchemeHandler.create(OkHttpClient)` directly by calling
|
||||||
|
`build.addSchemeHandler()`
|
||||||
|
:::
|
||||||
|
|
||||||
### Resources
|
### Resources
|
||||||
|
|
||||||
`android.content.res.Resources` to be used when obtaining an image
|
`android.content.res.Resources` to be used when obtaining an image
|
||||||
@ -103,6 +109,12 @@ To quote Android documentation for `#getSystem` method:
|
|||||||
|
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
:::warning
|
||||||
|
This configuration option is scheduled to be removed in `3.0.0`. Construct
|
||||||
|
your `MediaDecoder`s and `SchemeHandler`s appropriately and add them via
|
||||||
|
`build.addMediaDecoder()` and `builder.addSchemeHandler`
|
||||||
|
:::
|
||||||
|
|
||||||
### Executor service
|
### Executor service
|
||||||
|
|
||||||
`ExecutorService` to be used to download images in background thread
|
`ExecutorService` to be used to download images in background thread
|
||||||
@ -113,7 +125,7 @@ AsyncDrawableLoader.builder()
|
|||||||
.build();
|
.build();
|
||||||
```
|
```
|
||||||
|
|
||||||
If not provided explicitly, default `okHttpClient.dispatcher().executorService()` will be used
|
If not provided explicitly, default `Executors.newCachedThreadPool()` will be used
|
||||||
|
|
||||||
### Error drawable
|
### Error drawable
|
||||||
|
|
||||||
@ -134,8 +146,9 @@ of a specific image type.
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
AsyncDrawableLoader.builder()
|
AsyncDrawableLoader.builder()
|
||||||
.mediaDecoders(MediaDecoder...)
|
.addMediaDecoder(MediaDecoder)
|
||||||
.mediaDecoders(List<MediaDecoder>)
|
.addMediaDecoders(MediaDecoder...)
|
||||||
|
.addMediaDecoders(Iterable<MediaDecoder>)
|
||||||
.build();
|
.build();
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -180,3 +193,51 @@ GifMediaDecoder.create(boolean)
|
|||||||
```java
|
```java
|
||||||
ImageMediaDecoder.create(Resources)
|
ImageMediaDecoder.create(Resources)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Scheme handler <Badge text="2.0.0" />
|
||||||
|
|
||||||
|
Starting with `2.0.0` `image-loader` module introduced
|
||||||
|
`SchemeHandler` abstraction
|
||||||
|
|
||||||
|
```java
|
||||||
|
AsyncDrawableLoader.builder()
|
||||||
|
.addSchemeHandler(SchemeHandler)
|
||||||
|
.build()
|
||||||
|
```
|
||||||
|
|
||||||
|
Currently there are 3 `SchemeHandler`s that are bundled with this module:
|
||||||
|
* `NetworkSchemeHandler` (`http` and `https`)
|
||||||
|
* `FileSchemeHandler` (`file`)
|
||||||
|
* `DataUriSchemeHandler` (`data`)
|
||||||
|
|
||||||
|
#### NetworkSchemeHandler <Badge text="2.0.0" />
|
||||||
|
|
||||||
|
```java
|
||||||
|
NetworkSchemeHandler.create(OkHttpClient);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### FileSchemeHandler <Badge text="2.0.0" />
|
||||||
|
|
||||||
|
Simple file handler
|
||||||
|
```java
|
||||||
|
FileSchemeHandler.create();
|
||||||
|
```
|
||||||
|
|
||||||
|
File handler that additionally allows access to Android `assets` folder
|
||||||
|
```java
|
||||||
|
FileSchemeHandler.createWithAssets(AssetManager);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### DataUriSchemeHandler <Badge text="2.0.0" />
|
||||||
|
|
||||||
|
```java
|
||||||
|
DataUriSchemeHandler.create();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
Note that currently if no `SchemeHandler`s were provided via `builder.addSchemeHandler()`
|
||||||
|
call then all 3 default scheme handlers will be added. The same goes for `MediaDecoder`s
|
||||||
|
(`builder.addMediaDecoder`). This behavior is scheduled to be removed in `3.0.0`
|
||||||
|
:::
|
@ -12,7 +12,6 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -79,7 +78,13 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
// todo: should we cancel pending request for the same destination?
|
// todo: should we cancel pending request for the same destination?
|
||||||
// we _could_ but there is possibility that one resource is request in multiple places
|
// we _could_ but there is possibility that one resource is request in multiple places
|
||||||
|
|
||||||
// todo, if not a link -> show placeholder
|
// todo: error handing (simply applying errorDrawable is not a good solution
|
||||||
|
// as reason for an error is unclear (no scheme handler, no input data, error decoding, etc)
|
||||||
|
|
||||||
|
// todo: more efficient ImageMediaDecoder... BitmapFactory.decodeStream is a bit not optimal
|
||||||
|
// for big images for sure. We _could_ introduce internal Drawable that will check for
|
||||||
|
// image bounds (but we will need to cache inputStream in order to inspect and optimize
|
||||||
|
// input image...)
|
||||||
|
|
||||||
return executorService.submit(new Runnable() {
|
return executorService.submit(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
@ -176,20 +181,36 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: as now we have different layers of abstraction (for scheme handling and media decoding)
|
||||||
|
// we no longer should add dependencies implicitly, it would be way better to allow adding
|
||||||
|
// multiple artifacts (file, data, network, svg, gif)... at least, maybe we can extract API
|
||||||
|
// for this module (without implementations), but keep _all-in_ (fat) artifact with all of these.
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 2.0.0 add {@link NetworkSchemeHandler} directly
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
private OkHttpClient client;
|
private OkHttpClient client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 2.0.0 construct {@link MediaDecoder} and {@link SchemeHandler} appropriately
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
private Resources resources;
|
private Resources resources;
|
||||||
|
|
||||||
private ExecutorService executorService;
|
private ExecutorService executorService;
|
||||||
private Drawable errorDrawable;
|
private Drawable errorDrawable;
|
||||||
|
|
||||||
// @since 2.0.0
|
|
||||||
private final Map<String, SchemeHandler> schemeHandlers = new HashMap<>(3);
|
|
||||||
|
|
||||||
// @since 1.1.0
|
// @since 1.1.0
|
||||||
private final List<MediaDecoder> mediaDecoders = new ArrayList<>(3);
|
private final List<MediaDecoder> mediaDecoders = new ArrayList<>(3);
|
||||||
|
|
||||||
|
// @since 2.0.0
|
||||||
|
private final Map<String, SchemeHandler> schemeHandlers = new HashMap<>(3);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated 2.0.0 add {@link NetworkSchemeHandler} directly
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Builder client(@NonNull OkHttpClient client) {
|
public Builder client(@NonNull OkHttpClient client) {
|
||||||
@ -224,6 +245,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
/**
|
/**
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
@NonNull
|
@NonNull
|
||||||
public Builder addSchemeHandler(@NonNull SchemeHandler schemeHandler) {
|
public Builder addSchemeHandler(@NonNull SchemeHandler schemeHandler) {
|
||||||
|
|
||||||
@ -240,20 +262,97 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #addMediaDecoder(MediaDecoder)
|
||||||
|
* @see #addMediaDecoders(MediaDecoder...)
|
||||||
|
* @see #addMediaDecoders(Iterable)
|
||||||
|
* @since 1.1.0
|
||||||
|
* @deprecated 2.0.0
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@NonNull
|
@NonNull
|
||||||
public Builder mediaDecoders(@NonNull List<MediaDecoder> mediaDecoders) {
|
public Builder mediaDecoders(@NonNull List<MediaDecoder> mediaDecoders) {
|
||||||
this.mediaDecoders.clear();
|
|
||||||
this.mediaDecoders.addAll(mediaDecoders);
|
// previously it was clearing before adding
|
||||||
|
|
||||||
|
for (MediaDecoder mediaDecoder : mediaDecoders) {
|
||||||
|
this.mediaDecoders.add(requireNonNull(mediaDecoder));
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see #addMediaDecoder(MediaDecoder)
|
||||||
|
* @see #addMediaDecoders(MediaDecoder...)
|
||||||
|
* @see #addMediaDecoders(Iterable)
|
||||||
|
* @since 1.1.0
|
||||||
|
* @deprecated 2.0.0
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
|
@Deprecated
|
||||||
public Builder mediaDecoders(MediaDecoder... mediaDecoders) {
|
public Builder mediaDecoders(MediaDecoder... mediaDecoders) {
|
||||||
this.mediaDecoders.clear();
|
|
||||||
if (mediaDecoders != null
|
// previously it was clearing before adding
|
||||||
&& mediaDecoders.length > 0) {
|
|
||||||
Collections.addAll(this.mediaDecoders, mediaDecoders);
|
final int length = mediaDecoders != null
|
||||||
|
? mediaDecoders.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if (length > 0) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
this.mediaDecoders.add(requireNonNull(mediaDecoders[i]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see SvgMediaDecoder
|
||||||
|
* @see GifMediaDecoder
|
||||||
|
* @see ImageMediaDecoder
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Builder addMediaDecoder(@NonNull MediaDecoder mediaDecoder) {
|
||||||
|
mediaDecoders.add(mediaDecoder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see SvgMediaDecoder
|
||||||
|
* @see GifMediaDecoder
|
||||||
|
* @see ImageMediaDecoder
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Builder addMediaDecoders(@NonNull Iterable<MediaDecoder> mediaDecoders) {
|
||||||
|
for (MediaDecoder mediaDecoder : mediaDecoders) {
|
||||||
|
this.mediaDecoders.add(requireNonNull(mediaDecoder));
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see SvgMediaDecoder
|
||||||
|
* @see GifMediaDecoder
|
||||||
|
* @see ImageMediaDecoder
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public Builder addMediaDecoders(MediaDecoder... mediaDecoders) {
|
||||||
|
|
||||||
|
final int length = mediaDecoders != null
|
||||||
|
? mediaDecoders.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
if (length > 0) {
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
this.mediaDecoders.add(requireNonNull(mediaDecoders[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,11 +365,14 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (executorService == null) {
|
if (executorService == null) {
|
||||||
|
// @since 2.0.0 we are using newCachedThreadPool instead
|
||||||
|
// of `okHttpClient.dispatcher().executorService()`
|
||||||
executorService = Executors.newCachedThreadPool();
|
executorService = Executors.newCachedThreadPool();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @since 2.0.0
|
// @since 2.0.0
|
||||||
// put default scheme handlers (to mimic previous behavior)
|
// put default scheme handlers (to mimic previous behavior)
|
||||||
|
// remove in 3.0.0 with plugins
|
||||||
if (schemeHandlers.size() == 0) {
|
if (schemeHandlers.size() == 0) {
|
||||||
if (client == null) {
|
if (client == null) {
|
||||||
client = new OkHttpClient();
|
client = new OkHttpClient();
|
||||||
@ -281,6 +383,7 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add default media decoders if not specified
|
// add default media decoders if not specified
|
||||||
|
// remove in 3.0.0 with plugins
|
||||||
if (mediaDecoders.size() == 0) {
|
if (mediaDecoders.size() == 0) {
|
||||||
mediaDecoders.add(SvgMediaDecoder.create(resources));
|
mediaDecoders.add(SvgMediaDecoder.create(resources));
|
||||||
mediaDecoders.add(GifMediaDecoder.create(true));
|
mediaDecoders.add(GifMediaDecoder.create(true));
|
||||||
@ -290,4 +393,13 @@ public class AsyncDrawableLoader implements AsyncDrawable.Loader {
|
|||||||
return new AsyncDrawableLoader(this);
|
return new AsyncDrawableLoader(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @since 2.0.0
|
||||||
|
@NonNull
|
||||||
|
private static <T> T requireNonNull(@Nullable T t) {
|
||||||
|
if (t == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
return t;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ public class DataUriSchemeHandler extends SchemeHandler {
|
|||||||
return new DataUriSchemeHandler(DataUriParser.create(), DataUriDecoder.create());
|
return new DataUriSchemeHandler(DataUriParser.create(), DataUriDecoder.create());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final String START = "data://";
|
||||||
|
|
||||||
private final DataUriParser uriParser;
|
private final DataUriParser uriParser;
|
||||||
private final DataUriDecoder uriDecoder;
|
private final DataUriDecoder uriDecoder;
|
||||||
|
|
||||||
@ -32,12 +34,12 @@ public class DataUriSchemeHandler extends SchemeHandler {
|
|||||||
@Override
|
@Override
|
||||||
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
public ImageItem handle(@NonNull String raw, @NonNull Uri uri) {
|
||||||
|
|
||||||
final String part = uri.getSchemeSpecificPart();
|
if (!raw.startsWith(START)) {
|
||||||
|
|
||||||
if (TextUtils.isEmpty(part)) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String part = raw.substring(START.length());
|
||||||
|
|
||||||
final DataUri dataUri = uriParser.parse(part);
|
final DataUri dataUri = uriParser.parse(part);
|
||||||
if (dataUri == null) {
|
if (dataUri == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -16,6 +16,10 @@ public abstract class SchemeHandler {
|
|||||||
|
|
||||||
public abstract void cancel(@NonNull String raw);
|
public abstract void cancel(@NonNull String raw);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will be called only once during initialization, should return schemes that are
|
||||||
|
* handled by this handler
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public abstract Collection<String> schemes();
|
public abstract Collection<String> schemes();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,85 @@
|
|||||||
|
package ru.noties.markwon.il;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.RobolectricTestRunner;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
@Config(manifest = Config.NONE)
|
||||||
|
public class DataUriSchemeHandlerTest {
|
||||||
|
|
||||||
|
private DataUriSchemeHandler handler;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
handler = DataUriSchemeHandler.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void scheme_specific_part_is_empty() {
|
||||||
|
assertNull(handler.handle("data:", Uri.parse("data:")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void data_uri_is_empty() {
|
||||||
|
assertNull(handler.handle("data://whatever", Uri.parse("data://whatever")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void no_data() {
|
||||||
|
assertNull(handler.handle("data://,", Uri.parse("data://,")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void correct() {
|
||||||
|
|
||||||
|
final class Item {
|
||||||
|
|
||||||
|
final String contentType;
|
||||||
|
final String data;
|
||||||
|
|
||||||
|
Item(String contentType, String data) {
|
||||||
|
this.contentType = contentType;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, Item> expected = new HashMap<String, Item>() {{
|
||||||
|
put("data://text/plain;,123", new Item("text/plain", "123"));
|
||||||
|
put("data://image/svg+xml;base64,MTIz", new Item("image/svg+xml", "123"));
|
||||||
|
}};
|
||||||
|
|
||||||
|
for (Map.Entry<String, Item> entry : expected.entrySet()) {
|
||||||
|
final ImageItem item = handler.handle(entry.getKey(), Uri.parse(entry.getKey()));
|
||||||
|
assertNotNull(entry.getKey(), item);
|
||||||
|
assertEquals(entry.getKey(), entry.getValue().contentType, item.contentType());
|
||||||
|
assertEquals(entry.getKey(), entry.getValue().data, readStream(item.inputStream()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
private static String readStream(@NonNull InputStream stream) {
|
||||||
|
try {
|
||||||
|
final Scanner scanner = new Scanner(stream, "UTF-8").useDelimiter("\\A");
|
||||||
|
return scanner.hasNext()
|
||||||
|
? scanner.next()
|
||||||
|
: "";
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new RuntimeException(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user