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

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">
<profile version="1.0">
<option name="myName" value="Project Default" />
<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="EmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false">
<option name="m_reportEmptyBlocks" value="true" />
</inspection_tool>
<inspection_tool class="LoggerInitializedWithForeignClass" 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" />
<option name="loggerFactoryMethodName" value="getLogger,getLogger,getLog,getLogger" />
</inspection_tool>
<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 name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<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="EmptyStatementBody" enabled="false" level="WARNING" enabled_by_default="false">
<option name="m_reportEmptyBlocks" value="true" />
</inspection_tool>
<inspection_tool class="FieldCanBeLocal" enabled="false" level="WARNING" enabled_by_default="false" />
<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>

View File

@ -4,12 +4,16 @@ import android.app.Activity;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import java.lang.ref.WeakReference;
import java.text.NumberFormat;
import jp.juggler.subwaytooter.table.SavedAccount;
import jp.juggler.subwaytooter.util.Utils;
/*
AsyncTask customized version:
@ -18,47 +22,83 @@ import jp.juggler.subwaytooter.util.Utils;
- has TootApiClient.
- pass progress message from TootApiClient to ProgressDialog.
*/
@SuppressWarnings("FieldCanBeLocal")
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_SPINNER = ProgressDialog.STYLE_SPINNER;
@SuppressWarnings("WeakerAccess") public static final int PROGRESS_HORIZONTAL = ProgressDialog.STYLE_HORIZONTAL;
@SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, SavedAccount access_info, boolean bShowProgress ){
this( _activity, access_info, bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE );
private static class ProgressInfo {
// 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")
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.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 );
showProgress( _activity, progress_style );
}
@SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, String instance, boolean bShowProgress ){
this( _activity, instance, bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE );
}
@SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, String instance, int progress_style ){
this.client = new TootApiClient( _activity, this );
public TootApiTask( @NonNull Activity _activity, String instance, int progress_style ){
this( _activity, progress_style );
client.setInstance( instance );
showProgress( _activity, progress_style );
}
private static int getDefaultProgressStyle( boolean bShowProgress ){
return bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE;
}
@SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, boolean bShowProgress ){
this( _activity, bShowProgress ? PROGRESS_SPINNER : PROGRESS_NONE );
public TootApiTask( @NonNull Activity _activity, boolean bShowProgress ){
this( _activity, getDefaultProgressStyle( bShowProgress ) );
}
@SuppressWarnings("WeakerAccess")
public TootApiTask( Activity _activity, int progress_style ){
this.client = new TootApiClient( _activity, this );
showProgress( _activity, progress_style );
public TootApiTask( @NonNull Activity _activity, SavedAccount access_info, boolean bShowProgress ){
this( _activity, access_info, getDefaultProgressStyle( bShowProgress ) );
}
@SuppressWarnings("WeakerAccess")
public TootApiTask( @NonNull Activity _activity, String instance, boolean bShowProgress ){
this( _activity, instance, getDefaultProgressStyle( bShowProgress ) );
}
public TootApiTask setProgressPrefix( String s ){
@ -67,39 +107,7 @@ public abstract class TootApiTask extends AsyncTask< Void, Void, TootApiResult >
}
//////////////////////////////////////////////////////
@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 );
}
} );
}
}
//////////////////////////////////////////////////////
// implements AsyncTask
@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;
private String progress_prefix;
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 boolean isApiCancelled(){
return isCancelled();
}
@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(){
synchronized( this ){
isAlive = false;
}
if( progress != null ){
try{
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 );
//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 ){
long now = getNow();
@ -122,10 +130,10 @@ public class CustomEmojiCache {
if( time_error != null && now < time_error + ERROR_EXPIRE ){
return null;
}
}catch(Throwable ex){
}catch( Throwable ex ){
// NullPointerException at java.util.concurrent.ConcurrentHashMap.get (ConcurrentHashMap.java:915)
log.trace(ex);
log.trace( ex );
}
}
synchronized( queue ){
@ -143,6 +151,7 @@ public class CustomEmojiCache {
public CustomEmojiCache( Context context ){
this.context = context;
this.handler = new Handler( context.getMainLooper() );
this.cache = new ConcurrentHashMap<>();
this.worker = new Worker();
worker.start();
}
@ -166,7 +175,7 @@ public class CustomEmojiCache {
}
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 );
continue;
}
@ -175,8 +184,19 @@ public class CustomEmojiCache {
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();
int cache_size;
synchronized( cache ){
// 成功キャッシュ
@ -197,13 +217,14 @@ public class CustomEmojiCache {
//noinspection UnusedAssignment
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;
try{
byte[] data = App1.getHttpCached( request.url );
if( data == null ){
log.e("get failed. url=%s",request.url );
log.e( "get failed. url=%s", request.url );
}else{
frames = decodeAPNG( data, request.url );
}
@ -269,10 +290,10 @@ public class CustomEmojiCache {
try{
APNGFrames frames = APNGFrames.parseAPNG( new ByteArrayInputStream( data ), 64 );
if( frames == null ){
if(DEBUG) log.d("parseAPNG returns null.");
if( DEBUG ) log.d( "parseAPNG returns null." );
// fall thru
}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でデコードしなおすべき
frames.dispose();
// fall thru
@ -288,7 +309,7 @@ public class CustomEmojiCache {
try{
Bitmap b = decodeBitmap( data, 128 );
if( b != null ){
if(DEBUG) log.d("bitmap decoded.");
if( DEBUG ) log.d( "bitmap decoded." );
return new APNGFrames( b );
}else{
log.e( "Bitmap decode returns null. %s", url );