サムネイル表示でVolleyを捨ててGlide+okhttp3にした

This commit is contained in:
tateisu 2017-05-19 08:20:42 +09:00
parent c17e5631a1
commit fc45ce60b7
7 changed files with 206 additions and 246 deletions

View File

@ -53,10 +53,14 @@ dependencies {
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:customtabs:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'com.android.support:support-v4:25.3.1'
// compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
compile 'com.android.volley:volley:1.0.0'
compile 'com.squareup.okhttp3:okhttp:3.7.0'
// compile 'com.android.volley:volley:1.0.0'
compile 'com.squareup.okhttp3:okhttp:3.8.0'
// compile 'com.squareup.okhttp3:okhttp-urlconnection:3.8.0' // Vollery 使
compile 'commons-io:commons-io:2.4'
compile 'uk.co.chrisjenx:calligraphy:2.2.0'
compile 'com.github.woxthebox:draglistview:1.4.3'
@ -65,4 +69,8 @@ dependencies {
// compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
compile 'com.github.kenglxn.QRGen:android:2.2.0'
compile project(':exif')
compile 'com.github.bumptech.glide:glide:3.8.0'
// annotationProcessor 'com.github.bumptech.glide:compiler:3.8.0'
compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
}

View File

@ -172,6 +172,11 @@
android:resource="@xml/file_provider_path" />
</provider>
<!--<meta-data-->
<!--android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"-->
<!--android:value="GlideModule" />-->
</application>
</manifest>

View File

@ -779,9 +779,9 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
iv.setCornerRadius( pref, 16f );
PostAttachment a = attachment_list.get( idx );
if( a.attachment != null && a.status == PostAttachment.ATTACHMENT_UPLOADED ){
iv.setImageUrl( a.attachment.preview_url, App1.getImageLoader() );
iv.setImageUrl( a.attachment.preview_url );
}else{
iv.setImageUrl( null, App1.getImageLoader() );
iv.setImageUrl( null );
}
}
}
@ -1469,7 +1469,7 @@ public class ActPost extends AppCompatActivity implements View.OnClickListener,
llReply.setVisibility( View.VISIBLE );
tvReplyTo.setText( HTMLDecoder.decodeHTML( account, in_reply_to_text ) );
ivReply.setCornerRadius( pref, 16f );
ivReply.setImageUrl( in_reply_to_image, App1.getImageLoader() );
ivReply.setImageUrl( in_reply_to_image );
}
}

View File

@ -2,27 +2,24 @@ package jp.juggler.subwaytooter;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.support.annotation.NonNull;
import android.support.v4.util.LruCache;
import android.widget.ImageView;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.Volley;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
import com.bumptech.glide.load.model.GlideUrl;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
@ -166,94 +163,35 @@ public class App1 extends Application {
CipherSuite.TLS_DHE_RSA_WITH_AES_256_CBC_SHA, // m.sighash.info デフォルトにはない
};
static ImageLoader image_loader;
public static ImageLoader getImageLoader(){
return image_loader;
}
private static class MyImageLoader extends ImageLoader {
/**
* Constructs a new ImageLoader.
*
* @param queue The RequestQueue to use for making image requests.
* @param imageCache The cache to use as an L1 cache.
*/
MyImageLoader( RequestQueue queue, ImageCache imageCache ){
super( queue, imageCache );
}
@Override
protected Request< Bitmap > makeImageRequest( String requestUrl, int maxWidth, int maxHeight, ImageView.ScaleType scaleType, String cacheKey ){
Request< Bitmap > req = super.makeImageRequest( requestUrl, maxWidth, maxHeight, scaleType, cacheKey );
req.setRetryPolicy( new DefaultRetryPolicy(
30000 // SOCKET_TIMEOUT_MS
, 3 // DefaultRetryPolicy.DEFAULT_MAX_RETRIES
, DefaultRetryPolicy.DEFAULT_BACKOFF_MULT
) );
return req;
}
}
private static class BitmapCache implements ImageLoader.ImageCache {
private LruCache< String, Bitmap > mCache;
BitmapCache(Context context){
ActivityManager am = ((ActivityManager)context.getSystemService(Activity.ACTIVITY_SERVICE));
int memory = am.getMemoryClass();
int largeMemory = am.getLargeMemoryClass();
// どちらも単位はMB
log.d("MemoryClass=%d, LargeMemoryClass = %d",memory,largeMemory);
int maxSize;
if( am.isLowRamDevice() ){
maxSize = 5 * 1024; // 単位はKiB
}else if( largeMemory >= 512 ){
maxSize = 128 * 1024; // 単位はKiB
}else if( largeMemory >= 256 ){
maxSize = 64 * 1024; // 単位はKiB
}else{
maxSize = 10 * 1024; // 単位はKiB
}
mCache = new LruCache< String, Bitmap >( maxSize ) {
@Override
protected int sizeOf( String key, Bitmap value ){
int size = value.getRowBytes() * value.getHeight();
size = ((size + 1023) >> 10); // 単位はKiB
size = 1+(size>>10);
return size <= 0 ? 1 : size;
}
};
}
@Override public Bitmap getBitmap( String url ){
return mCache.get( url );
}
@Override public void putBitmap( String url, Bitmap bitmap ){
mCache.put( url, bitmap );
}
}
// private int getBitmapPoolSize( Context context ){
// ActivityManager am = ((ActivityManager)context.getSystemService(Activity.ACTIVITY_SERVICE));
// int memory = am.getMemoryClass();
// int largeMemory = am.getLargeMemoryClass();
// // どちらも単位はMB
// log.d("MemoryClass=%d, LargeMemoryClass = %d",memory,largeMemory);
//
// int maxSize;
// if( am.isLowRamDevice() ){
// maxSize = 5 * 1024; // 単位はKiB
// }else if( largeMemory >= 512 ){
// maxSize = 128 * 1024; // 単位はKiB
// }else if( largeMemory >= 256 ){
// maxSize = 64 * 1024; // 単位はKiB
// }else{
// maxSize = 10 * 1024; // 単位はKiB
// }
// return maxSize * 1024;
// }
public static OkHttpClient ok_http_client;
public static Typeface typeface_emoji;
// public static final RelationshipMap relationship_map = new RelationshipMap();
public static SharedPreferences pref;
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static ThreadPoolExecutor task_executor;
static OkHttpUrlLoader.Factory glide_okhttp3_factory;
@Override
public void onCreate(){
@ -271,20 +209,21 @@ public class App1 extends Application {
// the CPU with background work
int CPU_COUNT = Runtime.getRuntime().availableProcessors();
int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
int CORE_POOL_SIZE = Math.max( 2, Math.min( CPU_COUNT - 1, 4 ) );
int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
int KEEP_ALIVE_SECONDS = 30;
// デフォルトだとキューはmax128で溢れることがある
BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(999);
BlockingQueue< Runnable > sPoolWorkQueue = new LinkedBlockingQueue<>( 999 );
ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread( @NonNull Runnable r) {
return new Thread(r, "SubwayTooterTask #" + mCount.getAndIncrement());
private final AtomicInteger mCount = new AtomicInteger( 1 );
public Thread newThread( @NonNull Runnable r ){
return new Thread( r, "SubwayTooterTask #" + mCount.getAndIncrement() );
}
};
task_executor = new ThreadPoolExecutor(
CORE_POOL_SIZE // pool size
, MAXIMUM_POOL_SIZE // max pool size
@ -293,11 +232,10 @@ public class App1 extends Application {
, sPoolWorkQueue
, sThreadFactory
);
task_executor.allowCoreThreadTimeOut(true);
task_executor.allowCoreThreadTimeOut( true );
}
if( pref == null ){
pref = Pref.pref( getApplicationContext() );
}
@ -317,12 +255,12 @@ public class App1 extends Application {
AcctSet.deleteOld( System.currentTimeMillis() );
}
if( image_loader == null ){
image_loader = new MyImageLoader(
Volley.newRequestQueue( getApplicationContext() )
, new BitmapCache( getApplicationContext() )
);
}
// if( image_loader == null ){
// image_loader = new MyImageLoader(
// Volley.newRequestQueue( getApplicationContext() )
// , new BitmapCache( getApplicationContext() )
// );
// }
if( ok_http_client == null ){
@ -333,17 +271,55 @@ public class App1 extends Application {
ArrayList< ConnectionSpec > spec_list = new ArrayList<>();
spec_list.add( spec );
spec_list.add( ConnectionSpec.CLEARTEXT );
OkHttpClient.Builder builder = new OkHttpClient.Builder()
.connectTimeout( 30, TimeUnit.SECONDS )
.readTimeout( 30, TimeUnit.SECONDS )
.writeTimeout( 30, TimeUnit.SECONDS )
.connectionSpecs( spec_list )
;
.connectionSpecs( spec_list );
ok_http_client = builder.build();
}
// Glide.isSetup Glide 4.0 で廃止になるらしいが俺が使ってるのは3.xだ
//noinspection deprecation
if( ! Glide.isSetup() ){
Context context = getApplicationContext();
GlideBuilder builder = new GlideBuilder( context );
builder.setDiskCache( new InternalCacheDiskCacheFactory( context, 10 * 1024 * 1024 ) );
// 割とGlide任せで十分いけるっぽい
// MemorySizeCalculator calculator = new MemorySizeCalculator(context);
// int defaultMemoryCacheSize = calculator.getMemoryCacheSize();
// int defaultBitmapPoolSize = calculator.getBitmapPoolSize();
//
// ActivityManager am = ((ActivityManager)context.getSystemService(Activity.ACTIVITY_SERVICE));
// int class_memory = am.getMemoryClass(); // 単位はMB
// int class_large = am.getLargeMemoryClass(); // 単位はMB
//
// int maxSize;
// if( am.isLowRamDevice() ){
// maxSize = 5 * 1024; // 単位はKiB
// }else if( largeMemory >= 512 ){
// maxSize = 128 * 1024; // 単位はKiB
// }else if( largeMemory >= 256 ){
// maxSize = 64 * 1024; // 単位はKiB
// }else{
// maxSize = 10 * 1024; // 単位はKiB
// }
// return maxSize * 1024;
// }
// builder.setMemoryCache(new LruResourceCache(getMemoryCacheSize(getApplicationContext())));
// builder.setBitmapPool(new LruBitmapPool(getBitmapPoolSize(getApplicationContext())));
// Glide.setupはGLide 4.0 で廃止になるらしいが俺が使ってるのは3.xだ
//noinspection deprecation
Glide.setup( builder );
glide_okhttp3_factory = new OkHttpUrlLoader.Factory( ok_http_client );
Glide.get( getApplicationContext() ).register( GlideUrl.class, InputStream.class, glide_okhttp3_factory );
}
}
@Override
@ -355,7 +331,6 @@ public class App1 extends Application {
private static AppState app_state;
static AppState getAppState( Context context ){
// これは最後loadColumnListでDBが必要になる
if( app_state == null ){
app_state = new AppState( context.getApplicationContext(), pref );
}

View File

@ -102,9 +102,9 @@ class HeaderViewHolder implements View.OnClickListener, View.OnLongClickListener
btnFollow.setImageDrawable( null );
}else{
tvCreated.setText( TootStatus.formatTime( who.time_created_at ) );
ivBackground.setImageUrl( access_info.supplyBaseUrl( who.header_static ), App1.getImageLoader() );
ivBackground.setImageUrl( access_info.supplyBaseUrl( who.header_static ) );
ivAvatar.setCornerRadius( activity.pref,16f );
ivAvatar.setImageUrl( access_info.supplyBaseUrl( who.avatar_static ), App1.getImageLoader() );
ivAvatar.setImageUrl( access_info.supplyBaseUrl( who.avatar_static ) );
tvDisplayName.setText( who.display_name );
String s = "@" + access_info.getFullAcct( who );

View File

@ -291,7 +291,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
account_follow = who;
llFollow.setVisibility( View.VISIBLE );
ivFollow.setCornerRadius( activity.pref, 16f );
ivFollow.setImageUrl( access_info.supplyBaseUrl( who.avatar_static ), App1.getImageLoader() );
ivFollow.setImageUrl( access_info.supplyBaseUrl( who.avatar_static ) );
tvFollowerName.setText( who.display_name );
setAcct( tvFollowerAcct, access_info.getFullAcct( who ), R.attr.colorAcctSmall );
@ -309,7 +309,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
tvName.setText( status.account.display_name );
ivThumbnail.setCornerRadius( activity.pref, 16f );
ivThumbnail.setImageUrl( access_info.supplyBaseUrl( status.account.avatar_static ), App1.getImageLoader() );
ivThumbnail.setImageUrl( access_info.supplyBaseUrl( status.account.avatar_static ) );
tvContent.setText( status.decoded_content );
// if( status.decoded_tags == null ){
@ -409,7 +409,7 @@ class ItemViewHolder implements View.OnClickListener, View.OnLongClickListener {
String url = ta.preview_url;
if( TextUtils.isEmpty( url ) ) url = ta.remote_url;
iv.setCornerRadius( activity.pref, 16f ); // 正方形じゃないせいかうまく動かない activity.density * 4f );
iv.setImageUrl( access_info.supplyBaseUrl( url ), App1.getImageLoader() );
iv.setImageUrl( access_info.supplyBaseUrl( url ) );
}
}

View File

@ -2,6 +2,8 @@ package jp.juggler.subwaytooter.view;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.text.TextUtils;
@ -9,36 +11,18 @@ import android.util.AttributeSet;
import android.view.ViewGroup;
import android.support.v7.widget.AppCompatImageView;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.ImageLoader;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.target.Target;
import java.util.concurrent.atomic.AtomicBoolean;
import jp.juggler.subwaytooter.Pref;
public class MyNetworkImageView extends AppCompatImageView {
/**
* The URL of the network image to load
*/
private String mUrl;
/**
* Resource ID of the image to be used as a placeholder until the network image is loaded.
*/
private int mDefaultImageId;
/**
* Resource ID of the image to be used if the network response fails.
*/
private int mErrorImageId;
/**
* Local copy of the ImageLoader.
*/
private ImageLoader mImageLoader;
/**
* Current ImageContainer. (either in-flight or finished)
*/
private ImageLoader.ImageContainer mImageContainer;
public MyNetworkImageView( Context context ){
this( context, null );
@ -53,36 +37,19 @@ public class MyNetworkImageView extends AppCompatImageView {
}
/**
* Sets URL of the image that should be loaded into this view. Note that calling this will
* immediately either set the cached image (if available) or the default image specified by
* {@link com.android.volley.toolbox.NetworkImageView#setDefaultImageResId(int)} on the view.
* <p>
* NOTE: If applicable, {@link com.android.volley.toolbox.NetworkImageView#setDefaultImageResId(int)} and
* {@link com.android.volley.toolbox.NetworkImageView#setErrorImageResId(int)} should be called prior to calling
* this function.
*
* @param url The URL that should be loaded into this ImageView.
* @param imageLoader ImageLoader that will be used to make the request.
* Resource ID of the image to be used as a placeholder until the network image is loaded.
*/
public void setImageUrl( String url, ImageLoader imageLoader ){
mUrl = url;
mImageLoader = imageLoader;
// The URL has potentially changed. See if we need to load it.
loadImageIfNecessary( false );
}
private int mDefaultImageId;
/**
* Sets the default image resource ID to be used for this view until the attempt to load it
* completes.
*/
public void setDefaultImageResId( int defaultImage ){
mDefaultImageId = defaultImage;
}
/**
* Sets the error image resource ID to be used for this view in the event that the image
* requested fails to load.
* Resource ID of the image to be used if the network response fails.
*/
private int mErrorImageId;
public void setErrorImageResId( int errorImage ){
mErrorImageId = errorImage;
}
@ -91,20 +58,49 @@ public class MyNetworkImageView extends AppCompatImageView {
// 元画像の短辺に対する割合を指定するらしい
public void setCornerRadius( SharedPreferences pref, float r ){
if( ! pref.getBoolean( Pref.KEY_DONT_ROUND,false ) ){
if( ! pref.getBoolean( Pref.KEY_DONT_ROUND, false ) ){
mCornerRadius = r;
}
}
/**
* The URL of the network image to load
*/
private String mUrl;
public void setImageUrl( String url ){
mUrl = url;
// The URL has potentially changed. See if we need to load it.
loadImageIfNecessary(false);
}
Target< ? > mTarget;
String mTargetUrl;
private void cancelLoading(){
if( mTarget != null ){
setImageDrawable( null );
Glide.clear( mTarget );
mTarget = null;
mTargetUrl = null;
}
}
private void setDefaultImageOrNull(){
if( mDefaultImageId != 0 ){
setImageResource( mDefaultImageId );
}else{
setImageDrawable( null );
}
}
/**
* Loads the image for the view if it isn't already loaded.
*
* @param isInLayoutPass True if this was invoked from a layout pass, false otherwise.
*/
void loadImageIfNecessary( final boolean isInLayoutPass ){
void loadImageIfNecessary(final boolean isInLayoutPass){
int width = getWidth();
int height = getHeight();
ScaleType scaleType = getScaleType();
boolean wrapWidth = false, wrapHeight = false;
if( getLayoutParams() != null ){
@ -122,106 +118,82 @@ public class MyNetworkImageView extends AppCompatImageView {
// if the URL to be loaded in this view is empty, cancel any old requests and clear the
// currently loaded image.
if( TextUtils.isEmpty( mUrl ) ){
if( mImageContainer != null ){
mImageContainer.cancelRequest();
mImageContainer = null;
}
cancelLoading();
setDefaultImageOrNull();
return;
}else if( mTarget != null ){
// if there was an old request in this view, check if it needs to be canceled.
// if the request is from the same URL, return.
if( mUrl.equals( mTargetUrl ) ) return;
// if there is a pre-existing request, cancel it if it's fetching a different URL.
cancelLoading();
}
// if there was an old request in this view, check if it needs to be canceled.
if( mImageContainer != null && mImageContainer.getRequestUrl() != null ){
if( mImageContainer.getRequestUrl().equals( mUrl ) ){
// if the request is from the same URL, return.
return;
}else{
// if there is a pre-existing request, cancel it if it's fetching a different URL.
mImageContainer.cancelRequest();
setDefaultImageOrNull();
}
}
setDefaultImageOrNull();
// Calculate the max image width / height to use while ignoring WRAP_CONTENT dimens.
int maxWidth = wrapWidth ? 0 : width;
int maxHeight = wrapHeight ? 0 : height;
int desiredWidth = wrapWidth ? Target.SIZE_ORIGINAL : width;
int desiredHeight = wrapHeight ? Target.SIZE_ORIGINAL : height;
// The pre-existing content of this view didn't match the current URL. Load the new image
// from the network.
ImageLoader.ImageContainer newContainer = mImageLoader.get( mUrl,
new ImageLoader.ImageListener() {
@Override
public void onErrorResponse( VolleyError error ){
if( mErrorImageId != 0 ){
setImageResource( mErrorImageId );
}
final AtomicBoolean isImmediate = new AtomicBoolean(true);
mTargetUrl = mUrl;
mTarget = Glide.with( getContext() )
.load( mUrl )
.asBitmap()
.into(
new SimpleTarget< Bitmap >( desiredWidth, desiredHeight ) {
@Override public void onLoadFailed( Exception e, Drawable errorDrawable ){
e.printStackTrace();
if( mErrorImageId != 0 ) setImageResource( mErrorImageId );
}
@Override
public void onResponse( final ImageLoader.ImageContainer response, boolean isImmediate ){
// If this was an immediate response that was delivered inside of a layout
// pass do not set the image immediately as it will trigger a requestLayout
// inside of a layout. Instead, defer setting the image by posting back to
// the main thread.
if( isImmediate && isInLayoutPass ){
@Override public void onResourceReady(
final Bitmap bitmap
,final GlideAnimation< ? super Bitmap > glideAnimation
){
if( isImmediate.get() && isInLayoutPass ){
post( new Runnable() {
@Override
public void run(){
onResponse( response, false );
onResourceReady( bitmap, glideAnimation );
}
} );
return;
}
if( response.getBitmap() != null ){
if( mCornerRadius > 0f ){
RoundedBitmapDrawable d = RoundedBitmapDrawableFactory
.create( getResources(), response.getBitmap() );
d.setCornerRadius( mCornerRadius );
setImageDrawable( d );
}else{
setImageBitmap( response.getBitmap() );
}
}else if( mDefaultImageId != 0 ){
setImageResource( mDefaultImageId );
if( bitmap == null ){
setDefaultImageOrNull();
}else if( mCornerRadius <= 0f ){
setImageBitmap( bitmap );
}else{
RoundedBitmapDrawable d = RoundedBitmapDrawableFactory
.create( getResources(), bitmap );
d.setCornerRadius( mCornerRadius );
setImageDrawable( d );
}
}
}, maxWidth, maxHeight, scaleType );
// update the ImageContainer to be the new bitmap container.
mImageContainer = newContainer;
}
);
isImmediate.set(false);
}
private void setDefaultImageOrNull(){
if( mDefaultImageId != 0 ){
setImageResource( mDefaultImageId );
}else{
setImageBitmap( null );
}
}
@Override
protected void onLayout( boolean changed, int left, int top, int right, int bottom ){
@Override protected void onLayout( boolean changed, int left, int top, int right, int bottom ){
super.onLayout( changed, left, top, right, bottom );
loadImageIfNecessary( true );
loadImageIfNecessary(true);
}
@Override
protected void onDetachedFromWindow(){
if( mImageContainer != null ){
// If the view was bound to an image request, cancel it and clear
// out the image from the view.
mImageContainer.cancelRequest();
setImageBitmap( null );
// also clear out the container so we can reload the image if necessary.
mImageContainer = null;
}
@Override protected void onDetachedFromWindow(){
cancelLoading();
super.onDetachedFromWindow();
}
@Override
protected void drawableStateChanged(){
@Override protected void onAttachedToWindow(){
super.onAttachedToWindow();
loadImageIfNecessary(true);
}
@Override protected void drawableStateChanged(){
super.drawableStateChanged();
invalidate();
}