メディアビューアのロード進捗表示の改善。クラッシュレポート対応

This commit is contained in:
tateisu 2017-12-27 22:02:58 +09:00
parent 93f40e08a5
commit 2d559a84c0
3 changed files with 202 additions and 104 deletions

View File

@ -1,16 +1,13 @@
<component name="InspectionProjectProfileManager"> <component name="InspectionProjectProfileManager">
<profile version="1.0"> <profile version="1.0">
<option name="myName" value="Project Default" /> <option name="myName" value="Project Default" />
<inspection_tool class="AndroidLintButtonStyle" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintButtonStyle" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="AndroidLintSetTextI18n" enabled="false" level="WARNING" enabled_by_default="false" /> <inspection_tool class="AndroidLintSetTextI18n" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="EmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false"> <inspection_tool class="EmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false">
<option name="m_reportEmptyBlocks" value="true" /> <option name="m_reportEmptyBlocks" value="true" />
</inspection_tool> </inspection_tool>
<inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" level="WARNING" enabled_by_default="false"> <inspection_tool class="FieldCanBeLocal" enabled="false" level="WARNING" enabled_by_default="false" />
<option name="loggerClassName" value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger" /> <inspection_tool class="NumericOverflow" enabled="false" level="WARNING" enabled_by_default="false" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" /> <inspection_tool class="TryFinallyCanBeTryWithResources" enabled="false" level="WARNING" enabled_by_default="false" />
</inspection_tool> </profile>
<inspection_tool class="NumericOverflow" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="TryFinallyCanBeTryWithResources" enabled="false" level="WARNING" enabled_by_default="false" />
</profile>
</component> </component>

View File

@ -4,12 +4,16 @@ import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Handler;
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 android.text.TextUtils; import android.text.TextUtils;
import java.lang.ref.WeakReference;
import java.text.NumberFormat;
import jp.juggler.subwaytooter.table.SavedAccount; import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.Utils;
/* /*
AsyncTask customized version: AsyncTask customized version:
@ -18,47 +22,83 @@ import jp.juggler.subwaytooter.util.Utils;
- has TootApiClient. - has TootApiClient.
- pass progress message from TootApiClient to ProgressDialog. - pass progress message from TootApiClient to ProgressDialog.
*/ */
@SuppressWarnings("FieldCanBeLocal")
public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult > implements TootApiClient.Callback { public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult > implements TootApiClient.Callback {
@NonNull protected final TootApiClient client;
@SuppressWarnings("WeakerAccess") public static final int PROGRESS_NONE = - 1; @SuppressWarnings("WeakerAccess") public static final int PROGRESS_NONE = - 1;
@SuppressWarnings("WeakerAccess") public static final int PROGRESS_SPINNER = ProgressDialog.STYLE_SPINNER; @SuppressWarnings("WeakerAccess") public static final int PROGRESS_SPINNER = ProgressDialog.STYLE_SPINNER;
@SuppressWarnings("WeakerAccess") public static final int PROGRESS_HORIZONTAL = ProgressDialog.STYLE_HORIZONTAL; @SuppressWarnings("WeakerAccess") public static final int PROGRESS_HORIZONTAL = ProgressDialog.STYLE_HORIZONTAL;
@SuppressWarnings("WeakerAccess") private static class ProgressInfo {
public TootApiTask( Activity _activity, SavedAccount access_info, boolean bShowProgress ){
this( _activity, access_info, bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE ); // HORIZONTALスタイルの場合初期メッセージがないと後からメッセージを指定しても表示されない
@NonNull String message = " ";
boolean isIndeterminate = true;
int value = 0;
int max = 1;
}
@NonNull private WeakReference< Activity > refActivity;
@NonNull protected Handler handler;
@NonNull protected final TootApiClient client;
@NonNull private final ProgressInfo info = new ProgressInfo();
@Nullable private ProgressDialog progress;
@Nullable private String progress_prefix;
private final int progress_style;
private boolean isAlive = true;
private long last_message_shown;
private static final NumberFormat percent_format;
static{
percent_format = NumberFormat.getPercentInstance();
percent_format.setMaximumFractionDigits( 0 );
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, SavedAccount access_info, int progress_style ){ public TootApiTask( @NonNull Activity _activity, int progress_style ){
this.refActivity = new WeakReference<>( _activity );
this.handler = new Handler();
this.client = new TootApiClient( _activity, this ); this.client = new TootApiClient( _activity, this );
this.progress_style = progress_style;
if( progress_style != PROGRESS_NONE ){
// ダイアログの遅延表示を実装したけどすぐにダイアログを出した方が下のUIのタッチ判定を隠せて良いので使わないんだ
// handler.postDelayed( proc_progress_opener ,1000L );
proc_progress_opener.run();
}
}
@SuppressWarnings("WeakerAccess")
public TootApiTask( @NonNull Activity _activity, SavedAccount access_info, int progress_style ){
this( _activity, progress_style );
client.setAccount( access_info ); client.setAccount( access_info );
showProgress( _activity, progress_style );
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, String instance, boolean bShowProgress ){ public TootApiTask( @NonNull Activity _activity, String instance, int progress_style ){
this( _activity, instance, bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE ); this( _activity, progress_style );
}
@SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, String instance, int progress_style ){
this.client = new TootApiClient( _activity, this );
client.setInstance( instance ); client.setInstance( instance );
showProgress( _activity, progress_style ); }
private static int getDefaultProgressStyle( boolean bShowProgress ){
return bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE;
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, boolean bShowProgress ){ public TootApiTask( @NonNull Activity _activity, boolean bShowProgress ){
this( _activity, bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE ); this( _activity, getDefaultProgressStyle( bShowProgress ) );
} }
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, int progress_style ){ public TootApiTask( @NonNull Activity _activity, SavedAccount access_info, boolean bShowProgress ){
this.client = new TootApiClient( _activity, this ); this( _activity, access_info, getDefaultProgressStyle( bShowProgress ) );
showProgress( _activity, progress_style ); }
@SuppressWarnings("WeakerAccess")
public TootApiTask( @NonNull Activity _activity, String instance, boolean bShowProgress ){
this( _activity, instance, getDefaultProgressStyle( bShowProgress ) );
} }
public TootApiTask setProgressPrefix( String s ){ public TootApiTask setProgressPrefix( String s ){
@ -67,39 +107,7 @@ public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult >
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// implements AsyncTask
@Override public boolean isApiCancelled(){
return isCancelled();
}
@Override public void publishApiProgress( final String s ){
if( progress != null ){
Utils.runOnMainThread( new Runnable() {
@Override public void run(){
progress.setIndeterminate( true );
if( ! TextUtils.isEmpty( progress_prefix ) ){
progress.setMessage( progress_prefix + "\n" + s );
}else{
progress.setMessage( s );
}
}
} );
}
}
public void publishApiProgressRatio( final int value, final int max ){
if( progress != null ){
Utils.runOnMainThread( new Runnable() {
@Override public void run(){
progress.setIndeterminate( false );
progress.setProgress( value );
progress.setMax( max );
}
} );
}
}
//////////////////////////////////////////////////////
@Override protected abstract TootApiResult doInBackground( Void... voids ); @Override protected abstract TootApiResult doInBackground( Void... voids );
@ -115,36 +123,39 @@ public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult >
} }
////////////////////////////////////////////////////// //////////////////////////////////////////////////////
// implements TootApiClient.Callback
private ProgressDialog progress; @Override public boolean isApiCancelled(){
private String progress_prefix; return isCancelled();
private void showProgress( Activity activity, int progressStyle ){
if( progressStyle == PROGRESS_NONE ) return;
//noinspection deprecation
this.progress = new ProgressDialog( activity );
progress.setCancelable( true );
progress.setProgressStyle( progressStyle );
progress.setIndeterminate( true );
progress.setMax( 1 );
if( ! TextUtils.isEmpty( progress_prefix ) ){
progress.setMessage( progress_prefix );
}else{
// HORIZONTALスタイルの場合初期メッセージがないと後からメッセージを指定しても表示されない
progress.setMessage( " " );
}
progress.setOnCancelListener( new DialogInterface.OnCancelListener() {
@Override
public void onCancel( DialogInterface dialog ){
TootApiTask.this.cancel( true );
}
} );
progress.show();
} }
@Override public void publishApiProgress( final String s ){
synchronized( this ){
info.message = s;
info.isIndeterminate = true;
}
requestShowMessage();
}
//////////////////////////////////////////////////////
// 内蔵メディアビューアのローディング進捗の表示に使う
public void publishApiProgressRatio( final int value, final int max ){
synchronized( this ){
info.isIndeterminate = false;
info.value = value;
info.max = max;
}
requestShowMessage();
}
//////////////////////////////////////////////////////
// ダイアログを閉じる
private void dismissProgress(){ private void dismissProgress(){
synchronized( this ){
isAlive = false;
}
if( progress != null ){ if( progress != null ){
try{ try{
progress.dismiss(); progress.dismiss();
@ -163,4 +174,73 @@ public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult >
} }
} }
// ダイアログを開く
private final Runnable proc_progress_opener = new Runnable() {
@Override public void run(){
synchronized( this ){
Activity activity = refActivity.get();
if( isAlive
&& activity != null
&& progress == null
&& progress_style != PROGRESS_NONE
){
//noinspection deprecation
progress = new ProgressDialog( activity );
progress.setCancelable( true );
progress.setOnCancelListener( new DialogInterface.OnCancelListener() {
@Override public void onCancel( DialogInterface dialog ){
TootApiTask.this.cancel( true );
}
} );
progress.setProgressStyle( progress_style );
showProgressMessage();
progress.show();
}
}
}
};
// ダイアログのメッセージを更新する
private void showProgressMessage(){
if( progress == null ) return;
if( ! TextUtils.isEmpty( progress_prefix ) ){
progress.setMessage( progress_prefix + ( TextUtils.isEmpty( info.message.trim() ) ? "" : "\n" + info.message ) );
}else{
progress.setMessage( info.message );
}
progress.setIndeterminate( info.isIndeterminate );
if( info.isIndeterminate ){
progress.setProgressNumberFormat( null );
progress.setProgressPercentFormat( null );
}else{
progress.setProgress( info.value );
progress.setMax( info.max );
progress.setProgressNumberFormat( "%1$,d / %2$,d" );
progress.setProgressPercentFormat( percent_format );
}
last_message_shown = SystemClock.elapsedRealtime();
}
// 少し後にダイアログのメッセージを更新する
// あまり頻繁に更新せずしかし繰り返し呼ばれ続けても時々は更新したい
private void requestShowMessage(){
long wait = 100L + last_message_shown - SystemClock.elapsedRealtime();
wait = wait < 0L ? 0L : wait > 100L ? 100L : wait;
handler.removeCallbacks( proc_progress_message );
handler.postDelayed( proc_progress_message, wait );
}
private final Runnable proc_progress_message = new Runnable() {
@Override public void run(){
synchronized( this ){
if( progress != null && progress.isShowing() ){
showProgressMessage();
}
}
}
};
} }

View File

@ -63,7 +63,7 @@ public class CustomEmojiCache {
} }
} }
@NonNull final ConcurrentHashMap< String, CacheItem > cache = new ConcurrentHashMap<>(); @NonNull final ConcurrentHashMap< String, CacheItem > cache;
//////////////////////////////// ////////////////////////////////
// リクエスト // リクエスト
@ -106,6 +106,14 @@ public class CustomEmojiCache {
cancelRequest( target_tag ); cancelRequest( target_tag );
//noinspection ConstantConditions
if( cache == null ){
// java.lang.NullPointerException:
// at java.util.concurrent.ConcurrentHashMap.get (ConcurrentHashMap.java:915)
// at jp.juggler.subwaytooter.util.CustomEmojiCache.get (CustomEmojiCache.java:113)
return null;
}
synchronized( cache ){ synchronized( cache ){
long now = getNow(); long now = getNow();
@ -122,10 +130,10 @@ public class CustomEmojiCache {
if( time_error != null && now < time_error + ERROR_EXPIRE ){ if( time_error != null && now < time_error + ERROR_EXPIRE ){
return null; return null;
} }
}catch(Throwable ex){ }catch( Throwable ex ){
// NullPointerException at java.util.concurrent.ConcurrentHashMap.get (ConcurrentHashMap.java:915) // NullPointerException at java.util.concurrent.ConcurrentHashMap.get (ConcurrentHashMap.java:915)
log.trace(ex); log.trace( ex );
} }
} }
synchronized( queue ){ synchronized( queue ){
@ -143,6 +151,7 @@ public class CustomEmojiCache {
public CustomEmojiCache( Context context ){ public CustomEmojiCache( Context context ){
this.context = context; this.context = context;
this.handler = new Handler( context.getMainLooper() ); this.handler = new Handler( context.getMainLooper() );
this.cache = new ConcurrentHashMap<>();
this.worker = new Worker(); this.worker = new Worker();
worker.start(); worker.start();
} }
@ -166,7 +175,7 @@ public class CustomEmojiCache {
} }
if( request == null ){ if( request == null ){
if(DEBUG) log.d( "wait. req_size=%d", req_size ); if( DEBUG ) log.d( "wait. req_size=%d", req_size );
waitEx( 86400000L ); waitEx( 86400000L );
continue; continue;
} }
@ -175,8 +184,19 @@ public class CustomEmojiCache {
continue; continue;
} }
//noinspection ConstantConditions
if( cache == null ){
// Fujitsu F-01HF01H, 2048MB RAM, Android 6.0
// java.lang.NullPointerException:
// at java.util.concurrent.ConcurrentHashMap.get (ConcurrentHashMap.java:772)
// at jp.juggler.subwaytooter.util.CustomEmojiCache$Worker.run (CustomEmojiCache.java:183)
waitEx( 1000L );
continue;
}
long now = getNow(); long now = getNow();
int cache_size; int cache_size;
synchronized( cache ){ synchronized( cache ){
// 成功キャッシュ // 成功キャッシュ
@ -197,13 +217,14 @@ public class CustomEmojiCache {
//noinspection UnusedAssignment //noinspection UnusedAssignment
cache_size = cache.size(); cache_size = cache.size();
} }
if(DEBUG) log.d( "start get image. req_size=%d, cache_size=%d url=%s", req_size, cache_size, request.url ); if( DEBUG )
log.d( "start get image. req_size=%d, cache_size=%d url=%s", req_size, cache_size, request.url );
APNGFrames frames = null; APNGFrames frames = null;
try{ try{
byte[] data = App1.getHttpCached( request.url ); byte[] data = App1.getHttpCached( request.url );
if( data == null ){ if( data == null ){
log.e("get failed. url=%s",request.url ); log.e( "get failed. url=%s", request.url );
}else{ }else{
frames = decodeAPNG( data, request.url ); frames = decodeAPNG( data, request.url );
} }
@ -269,10 +290,10 @@ public class CustomEmojiCache {
try{ try{
APNGFrames frames = APNGFrames.parseAPNG( new ByteArrayInputStream( data ), 64 ); APNGFrames frames = APNGFrames.parseAPNG( new ByteArrayInputStream( data ), 64 );
if( frames == null ){ if( frames == null ){
if(DEBUG) log.d("parseAPNG returns null."); if( DEBUG ) log.d( "parseAPNG returns null." );
// fall thru // fall thru
}else if( frames.isSingleFrame() ){ }else if( frames.isSingleFrame() ){
if(DEBUG) log.d( "parseAPNG returns single frame." ); if( DEBUG ) log.d( "parseAPNG returns single frame." );
// mastodonのstatic_urlが返すPNG画像はAPNGだと透明になってる場合があるBitmapFactoryでデコードしなおすべき // mastodonのstatic_urlが返すPNG画像はAPNGだと透明になってる場合があるBitmapFactoryでデコードしなおすべき
frames.dispose(); frames.dispose();
// fall thru // fall thru
@ -288,7 +309,7 @@ public class CustomEmojiCache {
try{ try{
Bitmap b = decodeBitmap( data, 128 ); Bitmap b = decodeBitmap( data, 128 );
if( b != null ){ if( b != null ){
if(DEBUG) log.d("bitmap decoded."); if( DEBUG ) log.d( "bitmap decoded." );
return new APNGFrames( b ); return new APNGFrames( b );
}else{ }else{
log.e( "Bitmap decode returns null. %s", url ); log.e( "Bitmap decode returns null. %s", url );