6315


Androidで画面回転したときに、Activity/Fragmentが再生成されますが、通信などの別スレッドが動いている途中に回転された場合にどのように別スレッドの処理結果を再生成後のFragmentなどで受け取ればよいのかで悩んでいます。  
（画面レイアウトが縦と横で異なるため、画面が再生成されない方法は使用していません。）

現在デバッグ中のコードの処理概要は下記のような感じです。

  * `ProgressDialogFragment`を表示
  * Volleyで通信
  * Volleyのコールバックで`ProgressDialog`を閉じてActivity/Fragmentに通信で得られた内容を表示

このコードは、通信中に画面を回転されない場合はうまく動作します。  
通信中に画面を回転させた場合、`getFragmentManager()`で`NullpointerException`が発生します。  
また、`ProgressDialog`関連コードを削除して実行してみましたが、通信結果の表示`mTextView.setText(response)`が画面に反映されません。

画面回転時にFragmentが再生成されるために、コールバックされたところが再生成以前の場所のために起こるのだと考えています。

通信中に画面を回転させた場合でも、`ProgressDialog`を消し、通信結果を画面に表示するにはどうすればよろしいでしょうか？

[code]

    public class MyFragment extends Fragment implements View.OnClickListener {
    
    ・・・省略・・・
    
        @Override
        public void onClick(View v) {
            MyProgressDialog dialog = MyProgressDialog.newInstance("メッセージ");
            dialog.show(getFragmentManager(), MyProgressDialog.FRAGMENT_TAG);
    
            String url = "http://～～～～～～～～～";
    
            StringRequest request = new StringRequest(url, new Response.Listener<String>() {
                @Override
                public void onResponse(String response) {
                    Log.v(TAG, response);
                    mTextView.setText(response);
                    MyProgressDialog progressDialog = (MyProgressDialog) getFragmentManager().findFragmentByTag(MyProgressDialog.FRAGMENT_TAG);
                    if (progressDialog != null) {
                        progressDialog.dismiss();
                    }
                }
            }, new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {
                }
            });
            MyApplication.getInstance().getRequestQueue().add(request);
        }
    }
    
[/code]


CC BY-SA 3.0
2015-02-11T08:00:36.843
6304
2015-02-11T10:37:07.663
2015-02-11T09:00:12.830
8216
8216
question
android
通信中に画面回転した場合に通信結果を受け取り、画面に表示する方法について


いろいろ方法はありますので、私の場合の指針です。

  * インターフェースを作成し、非同期操作の実行を`Activity`から行うようにする
  * 可能な限り`Fragment`から`Activity`（`getActivity()`や`getFragmentManager()`）へアクセスせず、インターフェースを経由する
  * `Activity`から`Fragment`へのアクセスは基本的に安全なので、結果の反映は`Activity`から行う
  * 画面回転に対しては、ブロードキャストレシーバを用いて対応する

以下、非常にざっくりしてますがコード例です。

### インターフェースの実装

まずこのようなインターフェースを作ります。

[code]

    public interface ConnectionListener {
       void onConnection();
    }
    
[/code]

そして、`Activity`に実装します。

[code]

    public class MainActivity extends Activity implements ConnectionListener {
    ....
        public final static String ACTION_CONNECTION_END = "yourpackage.connectionend";
    
        @Override
        public void onConnection() {
            MyDialogFragment dialog = MyDialogFragment.newInstance();
            dialog.show(getFragmentManager(), "dialog");
            final LocalBroadcastManager lbcr = LocalBroadcastManager.getInstance(this);
            // 以下はハンドラで遅延実行していますが、ネットワークに通信して結果を得るものとします
            new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                Intent i = new Intent(ACTION_CONNECTION_END);
                i.putExtra("returned-value", "通信結果");
                lbcr.sendBroadcast(i);
                }
            }, 3000);
        }
    ....
    
[/code]

通信結果は`Intent`に持たせて、`LocalBroadcastManager`で通知します。

そうすることで`Activity`が破棄されても、非同期処理の結果を受け取ることができます。

（※簡単のために`Handler`を使っていますが、`Activity`が一時的にリークしますので本当は上のコードはよくありません。`RequestQueue`を使う場合も、可能ならアウタークラスの参照を持たないようにする必要があります。）

### 通知の受信の実装

通信完了を受信するブロードキャストレシーバを作り、`onResume()`で購読、`onPause()`で購読を解除します。

[code]

    ....
        private BroadcastReceiver b = new BroadcastReceiver() {
        @Override
            public void onReceive(Context context, Intent intent) {
                if(!ACTION_CONNECTION_END.equals(intent.getAction()){
                    return;
                }
    
                //ダイアログを非表示
                MyDialogFragment progressDialog = (MyDialogFragment) getFragmentManager().findFragmentByTag("dialog");
                if (progressDialog != null) {
                     progressDialog.dismiss();
                }
    
                MyFragment fragment = (MyFragment) getFragmentManager().findFragmentByTag("myfragment-tag");
                fragment.setText(intent.getStringExtra("returned-value"));
            }
        };
    
        @Override
        protected void onResume() {
            super.onResume();
            IntentFilter f = new IntentFilter(ACTION_CONNECTION_END);
            LocalBroadcastManager.getInstance(this).registerReceiver(b, f);
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            LocalBroadcastManager.getInstance(this).unregisterReceiver(b);
        }
    }
    
[/code]

ブロードキャスト受信時にダイアログを非表示にし、`Fragment`に結果を渡します。

### フラグメント側の実装

[code]

    public class MyFragment extends Fragment implements View.OnClickListener {
        ...
        ConnectionListener listener;
    
        @Override
        public void onAttach(Activity activity) {
            super.onAttach(activity);
            // 本当は、ClassCastExceptionをcatchする定型処理が必要
            listener = (ConnectionListener) activity;
        }
    
        @Override
        public void onDetach() {
            super.onDetach();
            listener = null;
        }
    
        @Override
        public void onClick(View v) {
            // 通信処理をActivityへ委譲
            if(listener != null){
                listener.onConnection();
            }
        }
    
        // Activity側から結果を表示させるためのpublicメソッド
        public void setText(String text) {
            mTextView.setText(text);
        }
    }
    
[/code]

`Activity`から`Fragment`の表示を更新するためのメソッドを追加します。


CC BY-SA 3.0
2015-02-11T10:37:07.663
6315
2015-02-11T10:37:07.663
5337
6304
answer