tipos - Android parentActivity no se vuelve a crear después de que startActivityForResult regresa
startactivityforresult android (2)
Tengo una actividad principal y dentro de ella, estoy cargando un fragmento A. Desde FragmentA, estoy llamando a la actividad de google placepicker usando startActivityforResult de la siguiente manera.
PlacePicker.IntentBuilder builder = new PlacePicker.IntentBuilder();
Intent intent = builder.build(getActivity());
getActivity().startActivityForResult(intent,PLACE_PICKER_REQUEST);
Pero cuando selecciono el lugar, onActivityResult (ya sea en FragmentA o MainActivity) no se está llamando. De hecho, mi aplicación se destruye después de la llamada a startActivityForResult.
Según mi entendimiento, Android debería recrear la actividad de llamadas si no está disponible en la memoria. Pero no está sucediendo. Incluso en Create no se llama dentro de MainActivity.
¿Alguien podría decirme la razón detrás de este tipo de comportamiento o me falta algo?
Ahora, en lugar de PlacePicker Activity, he intentado utilizar otra actividad en la misma aplicación.
Digamos que tengo MainActivity
con FragmentA
cargado. Estoy llamando SubActivity
con startActivityForResult
desde FragmentA
. Ahora, al regresar de SubActivity
, la aplicación sale. He habilitado Dont keep activities
en mi dispositivo para probar este escenario específico. Puedo ver que MainActivity
se destruye cuando me paso a SubActivity
Pero al regresar de SubActivity
, Android no está recreando MainActivity
(incluso onCreate
no se está llamando. La aplicación simplemente sale).
Esto puede suceder debido a muchas razones, pero con suerte es algo raro. El sistema operativo destruirá la actividad cuando esté en segundo plano si necesita reclamar recursos, lo cual es más probable que ocurra en dispositivos con menos memoria y potencia de procesamiento.
Usar la configuración Do not keep Activities
es una buena manera de probar este escenario, y en este caso hay otros problemas incluso si la Actividad / Fragmento se vuelve a crear. Con esta configuración habilitada, la Actividad y el Fragmento se destruyen cuando se muestra el PlacePicker, y cuando onActivityResult()
no hay un Contexto válido porque la Actividad y el Fragmento aún están en proceso de ser recreados.
Descubrí esto realizando una prueba controlada con la configuración deshabilitada, y luego con la configuración habilitada, y luego mirando los resultados
Puse el registro en cada devolución de llamada del ciclo de vida para la Actividad y el Fragmento para tener una idea de lo que está sucediendo durante estas llamadas.
Aquí está la clase completa que utilicé, que incluye tanto la Actividad como el Fragmento:
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)));
}
}
}
Aquí están los registros resultantes que dan una idea de las llamadas de ciclo de vida que se llaman durante el proceso en circunstancias normales:
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
Entonces, como puede ver en onDestroy()
nunca se llama, y onPause()
y onResume()
se onResume()
tanto en la Actividad como en el Fragmento.
Aquí está el resultado visualmente:
Luego, después de elegir un lugar:
Luego, habilité Do not keep Activities
en Developer Options en Settings, y ejecuté la misma prueba.
Estos son los registros resultantes:
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
Por lo tanto, puede ver que tanto la Actividad como el Fragmento se destruyen cuando se muestra el PlacePicker, y una vez que se selecciona el PlacePicker, la ejecución del código nunca llegó a la entrada del registro Fragment processActivityResult
, y la aplicación nunca mostró el lugar escogido en el mapa.
Esto se debe a la verificación de contexto nula:
if (getActivity() == null) return;
Log.d("PlacePickerTest", "Fragment processActivityResult");
//process Intent......
Place place = PlacePicker.getPlace(data, getActivity());
Entonces, la llamada a onActivityResult()
sí aparece, pero al mismo tiempo que la actividad y el fragmento se vuelven a crear, y necesita un contexto válido para hacer la llamada a PlacePicker.getPlace(data, getActivity());
.
La buena noticia es que la mayoría de los usuarios finales no tendrán habilitada la configuración Do not keep Activities
, y la mayoría de las veces su actividad no será destruida por el sistema operativo.
Parece bastante inusual para Android limpiar una actividad de la manera que describió, pero si ese fuera el caso, entonces su actividad aún debería restaurarse. Android no debe destruir la actividad, a menos que llame específicamente a finish()
o algo cause que la actividad termine prematuramente.
Si se refiere al diagrama del ciclo de vida de la actividad:
En el escenario que describió, la primera actividad debería invocar aStop, pero no aDestroy, luego, cuando regrese de la segunda actividad, debería invocarInicio de nuevo.
Creé una aplicación muy simple para probar el escenario que describiste, que contenía lo siguiente:
- Hay 2 actividades, FirstActivity y SecondActivity
- FirstActivity tiene un botón, cuando se hace clic en el botón, se inicia SecondActivity con
startActivityForResult()
- Los eventos del ciclo de vida de la actividad se registran usando
ActivityLifecycleCallbacks
en una clase de aplicación personalizada - En FirstActivity
onActivityResult
además, las salidas al registro cuando se llama
Aquí está lo que se produce:
La aplicación se inicia (FirstActivity se crea, inicia y es visible):
FirstActivity onCreate
FirstActivity onStart
FirstActivity onResume
Presiono el botón para iniciar SecondActivity:
FirstActivity onPause
SecondActivity onCreate
SecondActivity onStart
SecondActivity onResume
FirstActivity onSaveInstanceState
FirstActivity onStop
Tenga en cuenta que onDestroy no se llama.
Ahora presiono el botón Atrás y regreso a la primera actividad:
SecondActivity onPause
FirstActivity onStart
FirstActivity onActivityResult
FirstActivity onResume
SecondActivity onStop
SecondActivity onDestroy
El botón Atrás finish
en SecondActivity, por lo que se destruye
Ahora si presiono nuevamente, FirstActivity también estará terminado, haciendo que se onDestroy
a onDestroy
.
FirstActivity onPause
FirstActivity onStop
FirstActivity onDestroy
Puede ver que este ejemplo se ha adherido exactamente al diagrama del ciclo de vida. Las actividades solo se destruyen una vez que se presiona el botón Atrás, lo que hace que la actividad llame a finish()
.
Mencionó que intentó activar "No guardar actividades" en las opciones de desarrollador, podemos repetir el experimento anterior que esta opción habilitó y ver qué sucede. Acabo de agregar los eventos relevantes del ciclo de vida para guardar la repetición de todo lo que está arriba:
Después de presionar el botón en la primera actividad para comenzar la segunda actividad:
...
SecondActivity onResume
FirstActivity onSaveInstanceState
FirstActivity onStop
FirstActivity onDestroy
Como se esperaba, la actividad ha sido destruida esta vez. Esto es lo que sucede cuando vuelves a la primera actividad:
SecondActivity onPause
FirstActivity onCreate
FirstActivity onStart
FirstActivity onActivityResult
FirstActivity onResume
...
Esta vez se llamó de nuevo a onCreate porque el sistema no tenía una versión detenida de la primera actividad para reiniciar. También onActivityResult()
todavía se llamaba, independientemente del hecho de que la actividad tuviera que volver a crearse.
Esto también respalda que algo en su primera actividad debe estar llamando a finish()
o haciendo que se cuelgue. Sin embargo, sin ver su código real, esto es conjetura.
Finalmente, para mantener el estado si su actividad por algún motivo necesita ser recreada, puede anular onSaveInstanceState()
y agregar cualquier información de estado al paquete:
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(MY_STRING_KEY, "my string value");
}
Cuando vuelva a crear la actividad, volverá a obtener un paquete en Crear que debería contener todo lo que guardó:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
if (savedInstanceState != null) {
// Restore previous state
}
}