目標は:
- 長い操作を実行するときにProgressDialogを表示します。メッセージテキストはタスクの進行状況を通知できます。
- アプリケーションによる方向変更の正しいサポート。
実装
1.非同期プロセスからのメッセージを、AsyncTaskLoaderの後継であるAbstractTaskLoader抽象クラスに送信します。
public abstract class AbstractTaskLoader extends AsyncTaskLoader<Object> { //type of the published values public static int MSGCODE_PROGRESS = 1; public static int MSGCODE_MESSAGE = 2; private Handler handler; protected AbstractTaskLoader(Context context) { super(context); } protected void setHandler(Handler handler){ this.handler = handler; } /** * Helper to publish string value * @param value */ protected void publishMessage(String value){ if(handler!=null){ Bundle data = new Bundle(); data.putString("message", value); /* Creating a message */ Message msg = new Message(); msg.setData(data); msg.what = MSGCODE_MESSAGE; /* Sending the message */ handler.sendMessage(msg); } } …
さらに、いくつかの長い操作を実行するクラスは、この抽象クラスから既に継承されており、タスクを実行するプロセスについて通知できます。
@Override public Object loadInBackground() { … publishMessage(result); … }
2.次のステップは、メッセージを表示するProgressDialogの実装であり、画面の向きを変更するときにローダーを正しく起動/再起動します。 このクラスには、ローダーとダイアログのコールバックハンドラも含まれています。
public abstract class AbstractTaskProgressDialogFragment extends DialogFragment implements LoaderCallbacks<Object>, OnCancelListener { private ProgressDialog progressDialog; private final AbstractTaskLoader loader; protected abstract void onLoadComplete(Object data); protected abstract void onCancelLoad(); protected AbstractTaskProgressDialogFragment(AbstractTaskLoader loader,String message){ loader.setHandler(handler); this.loader = loader; Bundle args = new Bundle(); args.putString("message", message); setArguments(args); } @Override public ProgressDialog onCreateDialog(Bundle savedInstanceState) { String message = getArguments().getString("message"); progressDialog = new ProgressDialog(getActivity()); progressDialog.setMessage(message); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressDialog.setOnCancelListener(this); progressDialog.show(); return progressDialog; } private final Handler handler = new Handler() { public void handleMessage(Message msg) { if(msg.what == AbstractTaskLoader.MSGCODE_MESSAGE){ String message = AbstractTaskLoader.getMessageValue(msg); if(message!=null){ //update progress bar message if(progressDialog!=null){ progressDialog.setMessage(message); } } } } }; ...
クラスも抽象化されますが、これは必須ではなく、最小限のバインディングを実装するためのオプションの1つにすぎません。 作業の結果のみが重要です。
onLoadComplete(オブジェクトデータ);
onCancelLoad();
そして、これらのメソッドを後継機に実装します(インターフェースを介して行いました。以下を参照)。
ここでは、ローダーがコンストラクターに渡され、コンストラクターが実行され、起動時にテストメッセージがすぐに表示されます。 ハンドラーは、進行状況に関する情報メッセージをローダーから送信するために使用されます。 onCreateDialogで、ダイアログを作成して表示します。
次に、画面の向きを変更しても、このフラグメントはその状態を保持することを示す必要があります。 これはsetRetainInstance(true)メソッドを使用して行われます。この場合、画面の向きが変更されたときにonDestroyは呼び出されず、ダイアログは閉じません。
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setRetainInstance(true); // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, loader.getArguments(), this); }
onActivityCreatedメソッドを再定義します。このメソッドでは、最初の起動時にローダーを起動するか、既存のローダーに再接続します(画面の向きを変更する場合)。
小さな余談:
setRetainInstanceメソッドは、「 compatibility-library 」とペアになったDialogFragmentに対して正確に動作しません。つまり、onDestroyメソッドは呼び出されますが、実行されるべきではありません。 ただし、回避策があります。ダイアログに追加します。
@Override public void onDestroyView() { if (getDialog() != null && getRetainInstance()) getDialog().setOnDismissListener(null); super.onDestroyView(); }
詳細はこちら 。
ローダーの起動およびシャットダウン処理:
@Override public Loader<Object> onCreateLoader(int id, Bundle args) { loader.forceLoad(); return loader; } @Override public void onLoadFinished(Loader<Object> loader, Object data) { onLoadComplete(data); ((AbstractTaskLoader) loader).setHandler(null); hideDialog(); }
ユーザーキャンセル処理-「戻る」ボタンを押す:
@Override public void onCancel(DialogInterface dialog) { super.onCancel(dialog); loader.cancelLoad(); //base metod loader.setCanseled(true); //custom flag onCancelLoad(); //do not invoke dismiss for this dialog }
3.ベースフレームが作成されました。これで使用します。
完了通知用のインターフェースを作成します。
public interface ITaskLoaderListener { void onLoadFinished(Object data); void onCancelLoad(); }
作成した抽象クラスに基づいてProgressDialogの実装を作成します。
public class TaskProgressDialogFragment extends AbstractTaskProgressDialogFragment { private ITaskLoaderListener taskLoaderListener; private final Handler handler = new Handler(); protected TaskProgressDialogFragment(AbstractTaskLoader loader, String message) { super(loader, message); } protected void setTaskLoaderListener(ITaskLoaderListener taskLoaderListener){ this.taskLoaderListener = taskLoaderListener; } @Override protected void onLoadComplete(final Object data) { if(taskLoaderListener!=null){ taskLoaderListener.onLoadFinished(data); } } @Override protected void onCancelLoad() { if (taskLoaderListener != null) { taskLoaderListener.onCancelLoad(); } } ...
Loaderを便利に構築して実行するBuilder:
public static class Builder { FragmentActivity fa; AbstractTaskLoader loader; ITaskLoaderListener taskLoaderListener; Boolean cancelable; String progressMsg; public Builder(FragmentActivity fa, AbstractTaskLoader loader, String progressMsg) { this.fa = fa; this.loader = loader; this.progressMsg = progressMsg; } public Builder setTaskLoaderListener( ITaskLoaderListener taskLoaderListener) { this.taskLoaderListener = taskLoaderListener; return this; } public Builder setCancelable(Boolean cancelable) { this.cancelable = cancelable; return this; } public void show() { String TAG_FRAGMENT = UUID.randomUUID().toString(); // remove prev if exists FragmentManager fm = fa.getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); Fragment prev = fm.findFragmentByTag(TAG_FRAGMENT); if (prev != null) { ft.remove(prev); } // create dlg fragment TaskProgressDialogFragment fragment = new TaskProgressDialogFragment( loader, progressMsg); fragment.setTaskLoaderListener(taskLoaderListener); fragment.setCancelable(cancelable); Bundle args = new Bundle(); args.putString("message", progressMsg); fragment.setArguments(args); // show the dialog. fragment.show(ft, TAG_FRAGMENT); } }
4.最後に、これがローダーです。 たとえば、ProgreesDialog-eのメッセージテストを毎秒更新するだけです。
public class SampleTask extends AbstractTaskLoader { @Override public Object loadInBackground() { String result = ""; for(int i=0; i<10;i++){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } result = "Wait: " + String.valueOf(i); publishMessage(result); if(isCanselled()){ break; } Log.d(TAG, result); } return result; }
開始に便利な静的メソッドを追加します。
public static void execute(FragmentActivity fa, ITaskLoaderListener taskLoaderListener) { SampleTask loader = new SampleTask(fa); new TaskProgressDialogFragment.Builder(fa, loader, "Wait...") .setCancelable(true) .setTaskLoaderListener(taskLoaderListener) .show(); }
5.チャレンジ
public class SampleActivity extends FragmentActivity implements ITaskLoaderListener{ … SampleTask.execute(this, this); @Override public void onCancelLoad() { Log.d(TAG, "task canceled"); } @Override public void onLoadFinished(Object data) { if(data!=null && data instanceof String){ Log.d(TAG, "task result: " + data); } }
サンプルのソースコードをダウンロードしてください 。建設的な批判やコメントを歓迎します。
ソース:
1. Evelina Vrabieブログ、記事「Androidフラグメント、保存状態と画面回転」
2.スタックオーバーフロー