Compare commits

...

8 Commits

Author SHA1 Message Date
Dimitry Ivanov
fc01899d8e Prepare 3.1.0 release 2019-07-01 15:16:53 +03:00
Dimitry Ivanov
4f979ea4c6 Remove weak-reference from async-drawable-loader 2019-06-28 21:31:28 +03:00
Dimitry Ivanov
c210dc56ca Cherry pick async-drawable changes from 3.1.0 2019-06-28 21:10:08 +03:00
Dimitry Ivanov
30294782cb Update travis configuration 2019-06-19 10:37:54 +03:00
Dimitry Ivanov
e6b283e243 Better fix for latex plugin 2019-06-19 10:37:09 +03:00
Dimitry Ivanov
c20f1520ab Fix recursive factory call in OkHttpImagesPlugin 2019-06-19 00:40:09 +03:00
Zac Sweers
b30a1e10a8 Switch OkHttpImagesPlugin to be Call.Factory-based
This is a binary-compatible change that changes the underlying semantics to use a Call.Factory rather than OkHttpClient directly. This allows for a few benefits:

- Not tightly coupled to OkHttpClient
- Allows for lazy/delegating/deferred initialization
- Matches other OkHttpClient-dependent libraries' APIs, namely Retrofit and Apollo GraphQL
2019-06-19 00:39:22 +03:00
Dimitry Ivanov
08507c8a92 Fix latex plugin 2019-06-19 00:39:17 +03:00
9 changed files with 131 additions and 70 deletions

View File

@ -1,5 +1,6 @@
# https://docs.travis-ci.com/user/languages/android/ # https://docs.travis-ci.com/user/languages/android/
language: android language: android
dist: trusty
jdk: openjdk8 jdk: openjdk8
sudo: false sudo: false

View File

@ -6,7 +6,7 @@ org.gradle.configureondemand=true
android.enableBuildCache=true android.enableBuildCache=true
android.buildCacheDir=build/pre-dex-cache android.buildCacheDir=build/pre-dex-cache
VERSION_NAME=3.0.1 VERSION_NAME=3.1.0
GROUP=ru.noties.markwon GROUP=ru.noties.markwon
POM_DESCRIPTION=Markwon markdown for Android POM_DESCRIPTION=Markwon markdown for Android

View File

@ -32,7 +32,7 @@ public class AsyncDrawable extends Drawable {
public AsyncDrawable( public AsyncDrawable(
@NonNull String destination, @NonNull String destination,
@NonNull AsyncDrawableLoader loader, @NonNull AsyncDrawableLoader loader,
@Nullable ImageSizeResolver imageSizeResolver, @NonNull ImageSizeResolver imageSizeResolver,
@Nullable ImageSize imageSize @Nullable ImageSize imageSize
) { ) {
this.destination = destination; this.destination = destination;
@ -51,6 +51,45 @@ public class AsyncDrawable extends Drawable {
return destination; return destination;
} }
/**
* @since 3.1.0
*/
@Nullable
public ImageSize getImageSize() {
return imageSize;
}
/**
* @since 3.1.0
*/
@NonNull
public ImageSizeResolver getImageSizeResolver() {
return imageSizeResolver;
}
/**
* @since 3.1.0
*/
public boolean hasKnownDimentions() {
return canvasWidth > 0;
}
/**
* @see #hasKnownDimentions()
* @since 3.1.0
*/
public int getLastKnownCanvasWidth() {
return canvasWidth;
}
/**
* @see #hasKnownDimentions()
* @since 3.1.0
*/
public float getLastKnowTextSize() {
return textSize;
}
public Drawable getResult() { public Drawable getResult() {
return result; return result;
} }
@ -80,7 +119,7 @@ public class AsyncDrawable extends Drawable {
result.setCallback(callback); result.setCallback(callback);
} }
loader.load(destination, this); loader.load(this);
} else { } else {
if (result != null) { if (result != null) {
@ -91,7 +130,7 @@ public class AsyncDrawable extends Drawable {
((Animatable) result).stop(); ((Animatable) result).stop();
} }
} }
loader.cancel(destination); loader.cancel(this);
} }
} }

View File

@ -3,6 +3,7 @@ package ru.noties.markwon.image;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Log;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -36,10 +37,37 @@ public abstract class AsyncDrawableLoader {
return new AsyncDrawableLoaderNoOp(); return new AsyncDrawableLoaderNoOp();
} }
/**
* @since 3.1.0
*/
public abstract void load(@NonNull AsyncDrawable drawable);
public abstract void load(@NonNull String destination, @NonNull AsyncDrawable drawable); /**
* @since 3.1.0
*/
public abstract void cancel(@NonNull AsyncDrawable drawable);
public abstract void cancel(@NonNull String destination); /**
* @see #load(AsyncDrawable)
* @deprecated 3.1.0
*/
@Deprecated
public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) {
load(drawable);
}
/**
* Method is deprecated because cancellation won\'t work for markdown input
* with multiple images with the same source
*
* @deprecated 3.1.0
*/
@Deprecated
public void cancel(@NonNull String destination) {
Log.e("MARKWON-IL", "Image loading cancellation must be triggered " +
"by AsyncDrawable, please use #cancel(AsyncDrawable) method instead. " +
"No op, nothing is cancelled for destination: " + destination);
}
@Nullable @Nullable
public abstract Drawable placeholder(); public abstract Drawable placeholder();

View File

@ -4,12 +4,12 @@ import android.graphics.drawable.Drawable;
import android.net.Uri; import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.SystemClock;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -23,10 +23,12 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
private final MediaDecoder defaultMediaDecoder; private final MediaDecoder defaultMediaDecoder;
private final DrawableProvider placeholderDrawableProvider; private final DrawableProvider placeholderDrawableProvider;
private final DrawableProvider errorDrawableProvider; private final DrawableProvider errorDrawableProvider;
private final Handler handler = new Handler(Looper.getMainLooper());
private final Handler mainThread;
private final Map<String, Future<?>> requests = new HashMap<>(2); // @since 3.1.0 use a hash-map with an AsyncDrawable (allows multiple drawables
// referencing the same source)
private final Map<AsyncDrawable, Future<?>> requests = new HashMap<>(2);
AsyncDrawableLoaderImpl(@NonNull Builder builder) { AsyncDrawableLoaderImpl(@NonNull Builder builder) {
this.executorService = builder.executorService; this.executorService = builder.executorService;
@ -35,21 +37,25 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
this.defaultMediaDecoder = builder.defaultMediaDecoder; this.defaultMediaDecoder = builder.defaultMediaDecoder;
this.placeholderDrawableProvider = builder.placeholderDrawableProvider; this.placeholderDrawableProvider = builder.placeholderDrawableProvider;
this.errorDrawableProvider = builder.errorDrawableProvider; this.errorDrawableProvider = builder.errorDrawableProvider;
this.mainThread = new Handler(Looper.getMainLooper());
} }
@Override @Override
public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { public void load(@NonNull final AsyncDrawable drawable) {
// if drawable is not a link -> show loading placeholder... final Future<?> future = requests.get(drawable);
requests.put(destination, execute(destination, drawable)); if (future == null) {
requests.put(drawable, execute(drawable));
}
} }
@Override @Override
public void cancel(@NonNull String destination) { public void cancel(@NonNull final AsyncDrawable drawable) {
final Future<?> request = requests.remove(destination);
if (request != null) { final Future<?> future = requests.remove(drawable);
request.cancel(true); if (future != null) {
future.cancel(true);
} }
handler.removeCallbacksAndMessages(drawable);
} }
@Nullable @Nullable
@ -60,20 +66,10 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
: null; : null;
} }
private Future<?> execute(@NonNull final String destination, @NonNull AsyncDrawable drawable) { @NonNull
private Future<?> execute(@NonNull final AsyncDrawable drawable) {
final WeakReference<AsyncDrawable> reference = new WeakReference<AsyncDrawable>(drawable); final String destination = drawable.getDestination();
// todo: should we cancel pending request for the same destination?
// we _could_ but there is possibility that one resource is request in multiple places
// 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
@ -125,23 +121,18 @@ class AsyncDrawableLoaderImpl extends AsyncDrawableLoader {
: null; : null;
} }
if (result != null) {
final Drawable out = result; final Drawable out = result;
mainThread.post(new Runnable() {
handler.postAtTime(new Runnable() {
@Override @Override
public void run() { public void run() {
final boolean canDeliver = requests.remove(destination) != null; if (requests.remove(drawable) != null) {
if (canDeliver) { if (out != null && drawable.isAttached()) {
final AsyncDrawable asyncDrawable = reference.get(); drawable.setResult(out);
if (asyncDrawable != null && asyncDrawable.isAttached()) {
asyncDrawable.setResult(out);
} }
} }
} }
}); }, drawable, SystemClock.uptimeMillis());
} else {
requests.remove(destination);
}
} }
}); });
} }

View File

@ -6,12 +6,12 @@ import android.support.annotation.Nullable;
class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader { class AsyncDrawableLoaderNoOp extends AsyncDrawableLoader {
@Override @Override
public void load(@NonNull String destination, @NonNull AsyncDrawable drawable) { public void load(@NonNull AsyncDrawable drawable) {
} }
@Override @Override
public void cancel(@NonNull String destination) { public void cancel(@NonNull AsyncDrawable drawable) {
} }

View File

@ -80,12 +80,8 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
} }
} }
@NonNull
public static String makeDestination(@NonNull String latex) {
return SCHEME + "://" + latex;
}
private static final String SCHEME = "jlatexmath"; private static final String SCHEME = "jlatexmath";
private static final String SCHEME_START = SCHEME + "://";
private static final String CONTENT_TYPE = "text/jlatexmath"; private static final String CONTENT_TYPE = "text/jlatexmath";
private final Config config; private final Config config;
@ -112,9 +108,11 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
final RenderProps renderProps = visitor.renderProps(); final RenderProps renderProps = visitor.renderProps();
ImageProps.DESTINATION.set(renderProps, makeDestination(latex)); ImageProps.DESTINATION.set(renderProps, SCHEME_START + latex);
ImageProps.REPLACEMENT_TEXT_IS_LINK.set(renderProps, false); ImageProps.REPLACEMENT_TEXT_IS_LINK.set(renderProps, false);
ImageProps.IMAGE_SIZE.set(renderProps, new ImageSize(new ImageSize.Dimension(100, "%"), null)); ImageProps.IMAGE_SIZE.set(
renderProps,
new ImageSize(new ImageSize.Dimension(100, "%"), null));
visitor.setSpansForNode(Image.class, length); visitor.setSpansForNode(Image.class, length);
} }
@ -132,7 +130,7 @@ public class JLatexMathPlugin extends AbstractMarkwonPlugin {
ImageItem item = null; ImageItem item = null;
try { try {
final byte[] bytes = raw.substring(SCHEME.length()).getBytes("UTF-8"); final byte[] bytes = raw.substring(SCHEME_START.length()).getBytes("UTF-8");
item = new ImageItem( item = new ImageItem(
CONTENT_TYPE, CONTENT_TYPE,
new ByteArrayInputStream(bytes)); new ByteArrayInputStream(bytes));

View File

@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
import java.util.Arrays; import java.util.Arrays;
import okhttp3.Call;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import ru.noties.markwon.AbstractMarkwonPlugin; import ru.noties.markwon.AbstractMarkwonPlugin;
import ru.noties.markwon.image.AsyncDrawableLoader; import ru.noties.markwon.image.AsyncDrawableLoader;
@ -28,20 +29,25 @@ public class OkHttpImagesPlugin extends AbstractMarkwonPlugin {
@NonNull @NonNull
public static OkHttpImagesPlugin create(@NonNull OkHttpClient okHttpClient) { public static OkHttpImagesPlugin create(@NonNull OkHttpClient okHttpClient) {
return new OkHttpImagesPlugin(okHttpClient); return create((Call.Factory) okHttpClient);
} }
private final OkHttpClient client; @NonNull
public static OkHttpImagesPlugin create(@NonNull Call.Factory callFactory) {
return new OkHttpImagesPlugin(callFactory);
}
OkHttpImagesPlugin(@NonNull OkHttpClient client) { private final Call.Factory callFactory;
this.client = client;
OkHttpImagesPlugin(@NonNull Call.Factory callFactory) {
this.callFactory = callFactory;
} }
@Override @Override
public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) { public void configureImages(@NonNull AsyncDrawableLoader.Builder builder) {
builder.addSchemeHandler( builder.addSchemeHandler(
Arrays.asList(NetworkSchemeHandler.SCHEME_HTTP, NetworkSchemeHandler.SCHEME_HTTPS), Arrays.asList(NetworkSchemeHandler.SCHEME_HTTP, NetworkSchemeHandler.SCHEME_HTTPS),
new OkHttpSchemeHandler(client) new OkHttpSchemeHandler(callFactory)
); );
} }

View File

@ -3,11 +3,9 @@ package ru.noties.markwon.image.okhttp;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request; import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
@ -18,10 +16,10 @@ class OkHttpSchemeHandler extends SchemeHandler {
private static final String HEADER_CONTENT_TYPE = "Content-Type"; private static final String HEADER_CONTENT_TYPE = "Content-Type";
private final OkHttpClient client; private final Call.Factory callFactory;
OkHttpSchemeHandler(@NonNull OkHttpClient client) { OkHttpSchemeHandler(@NonNull Call.Factory callFactory) {
this.client = client; this.callFactory = callFactory;
} }
@Nullable @Nullable
@ -36,7 +34,7 @@ class OkHttpSchemeHandler extends SchemeHandler {
Response response = null; Response response = null;
try { try {
response = client.newCall(request).execute(); response = callFactory.newCall(request).execute();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }