web-dev-qa-db-ja.com

Android startActivityForResultが返された後、parentActivityが再作成されない

MainActivityがあり、その中にフラグメントAをロードしています。FragmentAから、次のようにstartActivityforResultを使用してgoogleplacepickerアクティビティを呼び出しています。

PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
Intent intent = builder.build(getActivity());
getActivity().startActivityForResult(intent,PLACE_PICKER_REQUEST);

しかし、場所を選択すると、onActivityResult(FragmentAまたはMainActivityのいずれか)が呼び出されません。実際、startActivityForResultの呼び出し後にアプリケーションが破棄されています。

私の理解によると、Androidは、呼び出しアクティビティがメモリで利用できない場合、それを再作成する必要があります。しかし、それは発生していません。onCreateでさえMainActivity内で呼び出されていません。

誰かがこの種の行動の背後にある理由を教えてもらえますか、それとも私は何かを逃していますか?

PlacePickerアクティビティの代わりに、同じアプリケーションで別のアクティビティを使用してみました。

MainActivityがロードされたFragmentAがあるとします。SubActivityからstartActivityForResultを使用してFragmentAを呼び出しています。これで、SubActivityから戻るときに、アプリケーションが終了します。この特定のシナリオをテストするために、デバイスでDont keep activitiesを有効にしました。 MainActivityに移動すると、SubActivityが破壊されているのがわかりますが、SubActivityから戻ると、AndroidはMainActivityを再作成していません(onCreateも呼び出されていません。アプリケーションは終了するだけです)。

23
Renjith

Androidが説明した方法でアクティビティをクリーンアップすることは非常に珍しいようですが、その場合でも、アクティビティを復元する必要があります。Android特にfinish()を呼び出すか、何かがアクティビティを途中で終了させない限り、アクティビティを破棄しないでください。

アクティビティのライフサイクル図を参照する場合:

説明したシナリオでは、最初のアクティビティはonStopを呼び出す必要がありますが、onDestroyは呼び出さないでください。次に、2番目のアクティビティから戻ると、onStartを再度呼び出す必要があります。

説明したシナリオをテストするための非常にシンプルなアプリを作成しました。これには次のものが含まれています。

  • FirstActivityとSecondActivityの2つのアクティビティがあります
  • FirstActivityにはボタンがあり、ボタンをクリックすると、SecondActivityがstartActivityForResult()で開始されます。
  • アクティビティライフサイクルイベントは、カスタムアプリケーションクラスでActivityLifecycleCallbacksを使用してログに記録されます
  • FirstActivityでは、onActivityResultは、呼び出されたときにログに追加で出力します

出力されるものは次のとおりです。

アプリケーションが開始されます(FirstActivityが作成され、開始されて表示されます):

_FirstActivity onCreate
FirstActivity onStart
FirstActivity onResume
_

ボタンを押してSecondActivityを開始します。

_FirstActivity onPause
SecondActivity onCreate
SecondActivity onStart
SecondActivity onResume
FirstActivity onSaveInstanceState
FirstActivity onStop
_

OnDestroyは呼び出されないことに注意してください。

ここで、戻るボタンを押して、最初のアクティビティに戻ります。

_SecondActivity onPause
FirstActivity onStart
FirstActivity onActivityResult
FirstActivity onResume
SecondActivity onStop
SecondActivity onDestroy
_

戻るボタンはSecondActivityでfinishを呼び出すため、破棄されます

ここでもう一度押すと、FirstActivityも終了し、onDestroyが呼び出されます。

_FirstActivity onPause
FirstActivity onStop
FirstActivity onDestroy
_

この例がライフサイクル図に正確に準拠していることがわかります。アクティビティは、戻るボタンが押されたときにのみ破棄されます。これにより、アクティビティはfinish()を呼び出します。

開発者向けオプションで「アクティビティを保持しない」をオンにしようとしたとのことですが、このオプションで有効にした上記の実験を繰り返して、何が起こるかを確認できます。上記のすべてを繰り返すのを防ぐために、関連するライフサイクルイベントを追加しました。

最初のアクティビティでボタンを押して2番目のアクティビティを開始した後:

_...
SecondActivity onResume
FirstActivity onSaveInstanceState
FirstActivity onStop
FirstActivity onDestroy
_

予想通り、今回は活動が破壊されました。これは、最初のアクティビティに戻ったときに発生することです。

_SecondActivity onPause
FirstActivity onCreate
FirstActivity onStart
FirstActivity onActivityResult
FirstActivity onResume
...
_

今回は、システムに再起動する最初のアクティビティの停止バージョンがなかったため、onCreateが再度呼び出されました。また、アクティビティを再作成する必要があるという事実に関係なく、onActivityResult()は引き続き呼び出されました。

これはさらに、最初のアクティビティの何かがfinish()を呼び出すか、クラッシュさせる必要があることをサポートします。ただし、実際のコードを見ずに、これは推測です。

最後に、アクティビティが何らかの理由で再作成する必要がある場合に状態を維持するには、onSaveInstanceState()をオーバーライドして、状態情報をバンドルに追加します。

_protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString(MY_STRING_KEY, "my string value");
}
_

アクティビティが再作成されると、保存したすべてのものが含まれているはずのバンドルがonCreateに戻されます。

_protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    if (savedInstanceState != null) {
        // Restore previous state
    }
}
_
8
Philio

これは多くの理由で発生する可能性がありますが、まれに発生することを願っています。 OSは、リソースを再利用する必要がある場合、バックグラウンドでアクティビティを破棄します。これは、メモリと処理能力が少ないデバイスで発生する可能性が高くなります。

Do not keep Activities設定を使用することは、このシナリオをテストするための良い方法です。この場合、アクティビティ/フラグメントが再作成されたとしても、他の問題があります。この設定を有効にすると、PlacePickerが表示されたときにアクティビティとフラグメントが破棄され、onActivityResult()が入ったときに、アクティビティとフラグメントがまだ再作成中であるため、有効なコンテキストがありません。

これは、設定を無効にして、次に設定を有効にして制御テストを実行し、結果を確認することでわかりました。

これらの呼び出し中に何が起こっているかを把握するために、アクティビティとフラグメントの各ライフサイクルコールバックにログインします。

アクティビティとフラグメントの両方を含む、私が使用した完全なクラスは次のとおりです。

public class MainActivity extends AppCompatActivity {

    MyFragment myFrag;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Log.d("PlacePickerTest", "Activity onCreate");

        myFrag = new MyFragment();

        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.container, myFrag)
                    .commit();
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        Log.d("PlacePickerTest", "Activity onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();

        Log.d("PlacePickerTest", "Activity onPause");
    }

    @Override
    protected void onDestroy() {
        Log.d("PlacePickerTest", "Activity onDestroy");
        super.onDestroy();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public void onActivityResult (int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        Log.d("PlacePickerTest", "Activity onActivityResult requestCode:" + requestCode);

        if (requestCode == 199){
            //process result of PlacePicker in the Fragment
            myFrag.processActivityResult(data);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            //open PlacePicker from menu item
            myFrag.startPlacePicker();
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * Fragment containing a map and PlacePicker functionality
     */
    public static class MyFragment extends Fragment {

        private GoogleMap mMap;
        Marker marker;

        public MyFragment() {
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            Log.d("PlacePickerTest", "Fragment onCreateView");

            return rootView;
        }


        @Override
        public void onResume() {
            super.onResume();

            Log.d("PlacePickerTest", "Fragment onResume");

            setUpMapIfNeeded();
        }

        @Override
          public void onPause() {
            super.onPause();

            Log.d("PlacePickerTest", "Fragment onPause");
        }

        @Override
        public void onDestroy() {
            Log.d("PlacePickerTest", "Fragment onDestroy");
            super.onDestroy();
        }

        private void setUpMapIfNeeded() {
            // Do a null check to confirm that we have not already instantiated the map.
            if (mMap == null) {
                // Try to obtain the map from the SupportMapFragment.
                mMap = ((SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map))
                        .getMap();
                // Check if we were successful in obtaining the map.
                if (mMap != null) {
                    setUpMap();
                }
            }
        }

        private void setUpMap() {

            // Enable MyLocation Layer of Google Map
            mMap.setMyLocationEnabled(true);
            mMap.setMapType(GoogleMap.MAP_TYPE_HYBRID);
            mMap.getUiSettings().setZoomControlsEnabled(true);
            mMap.getUiSettings().setMyLocationButtonEnabled(true);
            mMap.getUiSettings().setCompassEnabled(true);
            mMap.getUiSettings().setRotateGesturesEnabled(true);
            mMap.getUiSettings().setZoomGesturesEnabled(true);

        }

        public void startPlacePicker(){
            int PLACE_PICKER_REQUEST = 199;
            PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
            //Context context = getActivity();
            try {
                Log.d("PlacePickerTest", "Fragment startActivityForResult");
                getActivity().startActivityForResult(builder.build(getActivity()), PLACE_PICKER_REQUEST);
            } catch (GooglePlayServicesRepairableException e) {
                e.printStackTrace();
            } catch (GooglePlayServicesNotAvailableException e) {
                e.printStackTrace();
            }
        }

        public void processActivityResult ( Intent data) {

            if (getActivity() == null) return;

            Log.d("PlacePickerTest", "Fragment processActivityResult");


            //process Intent......
            Place place = PlacePicker.getPlace(data, getActivity());
            String placeName = String.format("Place: %s", place.getName());
            String placeAddress =  String.format("Address: %s", place.getAddress());

            LatLng toLatLng = place.getLatLng();

            // Show the place location in Google Map
            mMap.moveCamera(CameraUpdateFactory.newLatLng(toLatLng));
            mMap.animateCamera(CameraUpdateFactory.zoomTo(15));

            if (marker != null) {
                marker.remove();
            }
            marker = mMap.addMarker(new MarkerOptions().position(toLatLng)
                    .title(placeName).snippet(placeAddress)
                    .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_Magenta)));

        }
    }
}

結果のログは、通常の状況でプロセス中に呼び出されるライフサイクルコールバックについての洞察を提供します。

 D/PlacePickerTest﹕ Activity onCreate
 D/PlacePickerTest﹕ Fragment onCreateView
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume
 D/PlacePickerTest﹕ Fragment startActivityForResult
 D/PlacePickerTest﹕ Fragment onPause
 D/PlacePickerTest﹕ Activity onPause
 D/PlacePickerTest﹕ Activity onActivityResult requestCode:199
 D/PlacePickerTest﹕ Fragment processActivityResult
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume

したがって、ご覧のとおり、onDestroy()は呼び出されず、onPause()onResume()はアクティビティとフラグメントの両方で呼び出されます。

視覚的な結果は次のとおりです。

PlacePicker

次に、場所を選んだ後:

place shown on map

次に、[設定]の[開発者向けオプション]でDo not keep Activitiesを有効にして、同じテストを実行しました。

結果のログは次のとおりです。

 D/PlacePickerTest﹕ Activity onCreate
 D/PlacePickerTest﹕ Fragment onCreateView
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume
 D/PlacePickerTest﹕ Fragment startActivityForResult
 D/PlacePickerTest﹕ Fragment onPause
 D/PlacePickerTest﹕ Activity onPause
 D/PlacePickerTest﹕ Activity onDestroy
 D/PlacePickerTest﹕ Fragment onDestroy
 D/PlacePickerTest﹕ Activity onCreate
 D/PlacePickerTest﹕ Fragment onCreateView
 D/PlacePickerTest﹕ Activity onActivityResult requestCode:199
 D/PlacePickerTest﹕ Activity onResume
 D/PlacePickerTest﹕ Fragment onResume

したがって、PlacePickerが表示されると、アクティビティとフラグメントの両方が破棄され、PlacePickerで場所が選択された後、コード実行がFragment processActivityResultログエントリに到達することはなく、アプリが選択されたものを表示することはありません。地図上に配置します。

これは、コンテキストチェックがnullであるためです。

 if (getActivity() == null) return;

 Log.d("PlacePickerTest", "Fragment processActivityResult");

 //process Intent......
 Place place = PlacePicker.getPlace(data, getActivity());

したがって、onActivityResult()の呼び出しは行われますが、アクティビティとフラグメントが再作成されるのと同時に行われます。PlacePicker.getPlace(data, getActivity());を呼び出すには有効なコンテキストが必要です。

幸いなことに、ほとんどのエンドユーザーはDo not keep Activities設定を有効にしておらず、ほとんどの場合、アクティビティはOSによって破棄されません。

19
Daniel Nugent