Contexte
J'ai une application plus grande dans laquelle j'ai eu/j'ai plusieurs problèmes avec la nouvelle API Google Maps. J'ai essayé de le décrire dans une question différente mais comme cela me paraissait trop complexe, j'ai décidé de lancer un nouveau projet, aussi simple que possible, et d'essayer de reproduire les problèmes. Alors le voici.
La situation
J'utilise Fragments
et je veux mettre MapView
à l'intérieur. Je ne veux pas utiliser MapFragment
. L'exemple de projet que j'ai préparé n'est peut-être pas très beau, mais j'ai essayé de le rendre aussi simple que possible et il devait contenir quelques éléments (encore simplifiés) de l'application d'origine . J'ai un Activity
et mon habit Fragment
avec MapView
, ajouté par programme. La Map
contient des points/Markers
. Après avoir cliqué sur un Marker
, le InfoWindow
s'affiche et un clic dessus provoque l'affichage du prochain Fragment
(avec la fonction replace()
) dans le contenu.
Les problèmes
J'ai deux problèmes:
Lorsque la Map
avec Markers
est affichée, la rotation de l'écran provoque une erreur Class not found when unmarshalling
avec ma classe personnalisée MyMapPoint
- je ne sais pas pourquoi ni ce que cela signifie.
Je clique sur Marker
puis sur InfoWindow
. Après cela, j'appuie sur le bouton de retour matériel. Maintenant, je peux voir le Map
mais sans Markers
et centré dans le point 0,0
.
Le code
Activité principale
public class MainActivity extends FragmentActivity {
private ArrayList<MyMapPoint> mPoints = new ArrayList<MyMapPoint>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
mPoints.add(new MyMapPoint(1, new LatLng(20, 10),
"test point", "description", null));
mPoints.add(new MyMapPoint(2, new LatLng(10, 20),
"test point 2", "second description", null));
Fragment fragment = MyMapFragment.newInstance(mPoints);
getSupportFragmentManager().beginTransaction()
.add(R.id.contentPane, fragment).commit();
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:id="@+id/contentPane"
Android:layout_width="fill_parent"
Android:layout_height="fill_parent" />
map_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:orientation="vertical" >
<com.google.Android.gms.maps.MapView
xmlns:map="http://schemas.Android.com/apk/res-auto"
Android:id="@+id/map"
Android:layout_width="match_parent"
Android:layout_height="match_parent" />
</LinearLayout>
MyMapFragment
public class MyMapFragment extends Fragment implements
OnInfoWindowClickListener {
public static final String KEY_POINTS = "points";
private MapView mMapView;
private GoogleMap mMap;
private HashMap<MyMapPoint, Marker> mPoints =
new HashMap<MyMapPoint, Marker>();
public static MyMapFragment newInstance(ArrayList<MyMapPoint> points) {
MyMapFragment fragment = new MyMapFragment();
Bundle args = new Bundle();
args.putParcelableArrayList(KEY_POINTS, points);
fragment.setArguments(args);
return fragment;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
mMapView.onSaveInstanceState(outState);
MyMapPoint[] points = mPoints.keySet().toArray(
new MyMapPoint[mPoints.size()]);
outState.putParcelableArray(KEY_POINTS, points);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Bundle extras = getArguments();
if ((extras != null) && extras.containsKey(KEY_POINTS)) {
for (Parcelable pointP : extras.getParcelableArrayList(KEY_POINTS)) {
mPoints.put((MyMapPoint) pointP, null);
}
}
} else {
MyMapPoint[] points = (MyMapPoint[]) savedInstanceState
.getParcelableArray(KEY_POINTS);
for (MyMapPoint point : points) {
mPoints.put(point, null);
}
}
}
@Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.map_fragment, container, false);
mMapView = (MapView) layout.findViewById(R.id.map);
return layout;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mMapView.onCreate(savedInstanceState);
setUpMapIfNeeded();
addMapPoints();
}
@Override
public void onPause() {
mMapView.onPause();
super.onPause();
}
@Override
public void onResume() {
super.onResume();
setUpMapIfNeeded();
mMapView.onResume();
}
@Override
public void onDestroy() {
mMapView.onDestroy();
super.onDestroy();
}
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
};
private void setUpMapIfNeeded() {
if (mMap == null) {
mMap = ((MapView) getView().findViewById(R.id.map)).getMap();
if (mMap != null) {
setUpMap();
}
}
}
private void setUpMap() {
mMap.setOnInfoWindowClickListener(this);
addMapPoints();
}
private void addMapPoints() {
if (mMap != null) {
HashMap<MyMapPoint, Marker> toAdd =
new HashMap<MyMapPoint, Marker>();
for (Entry<MyMapPoint, Marker> entry : mPoints.entrySet()) {
Marker marker = entry.getValue();
if (marker == null) {
MyMapPoint point = entry.getKey();
marker = mMap.addMarker(point.getMarkerOptions());
toAdd.put(point, marker);
}
}
mPoints.putAll(toAdd);
}
}
@Override
public void onInfoWindowClick(Marker marker) {
Fragment fragment = DetailsFragment.newInstance();
getActivity().getSupportFragmentManager().beginTransaction()
.replace(R.id.contentPane, fragment)
.addToBackStack(null).commit();
}
public static class MyMapPoint implements Parcelable {
private static final int CONTENTS_DESCR = 1;
public int objectId;
public LatLng latLng;
public String title;
public String snippet;
public MyMapPoint(int oId, LatLng point,
String infoTitle, String infoSnippet, String infoImageUrl) {
objectId = oId;
latLng = point;
title = infoTitle;
snippet = infoSnippet;
}
public MyMapPoint(Parcel in) {
objectId = in.readInt();
latLng = in.readParcelable(LatLng.class.getClassLoader());
title = in.readString();
snippet = in.readString();
}
public MarkerOptions getMarkerOptions() {
return new MarkerOptions().position(latLng)
.title(title).snippet(snippet);
}
@Override
public int describeContents() {
return CONTENTS_DESCR;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(objectId);
dest.writeParcelable(latLng, 0);
dest.writeString(title);
dest.writeString(snippet);
}
public static final Parcelable.Creator<MyMapPoint> CREATOR =
new Parcelable.Creator<MyMapPoint>() {
public MyMapPoint createFromParcel(Parcel in) {
return new MyMapPoint(in);
}
public MyMapPoint[] newArray(int size) {
return new MyMapPoint[size];
}
};
}
}
Si vous avez besoin de regarder un autre fichier - faites le moi savoir. Ici vous pouvez trouver un projet complet, il vous suffit de mettre votre propre clé API Maps dans le fichier AndroidManifest.xml
.
MODIFIER
J'ai réussi à rendre l'exemple encore plus simple et à mettre à jour le code ci-dessus.
Voici ma solution au premier problème:
Il semble que Map
essaie de supprimer tout le paquet, pas seulement ses propres informations lorsque j'appelle mMap.onCreate(savedInstanceState)
. Cela pose un problème si j'utilise ma classe Parcelable
personnalisée. La solution qui a fonctionné pour moi a été de retirer mes suppléments de savedInstanceState
dès que je les ai utilisés, avant que j'appelle le onCreate()
de Map. Je le fais avec savedInstanceState.remove(MY_KEY)
. Une autre chose que je devais faire était d'appeler mMap.onSaveInstanceState()
avant d'ajouter mes propres informations à outState
dans la fonction Fragment's
onSaveInstanceState(Bundle outState)
.
Et voici comment j'ai géré le second:
J'ai simplifié l'exemple du projet au strict minimum. J'ajoutais une Markers
brute à la carte et si je remplaçais la Fragment
par une autre, puis après avoir cliqué sur "retour", la carte "annulée" restait active ... J'ai donc fait deux choses:
CameraPosition
dans la fonction onPause()
pour le restaurer dans onResume()
mMap
sur null dans onPause()
afin que lorsque Fragment
revienne, les Markers
soient ajoutés à nouveau par la fonction addMapPoints()
(je devais le modifier un peu puisque je sauvegardais et vérifiais les Markers
id).Voici des exemples de code:
private CameraPosition cp;
...
public void onPause() {
mMapView.onPause();
super.onPause();
cp = mMap.getCameraPosition();
mMap = null;
}
...
public void onResume() {
super.onResume();
setUpMapIfNeeded();
mMapView.onResume();
if (cp != null) {
mMap.moveCamera(CameraUpdateFactory.newCameraPosition(cp));
cp = null;
}
}
Et pour mettre à jour la position de la caméra dans onResume()
, je devais initialiser manuellement les cartes. Je l'ai fait dans setUpMap()
:
private void setUpMap() {
try {
MapsInitializer.initialize(getActivity());
} catch (GooglePlayServicesNotAvailableException e) {
}
mMap.setOnInfoWindowClickListener(this);
mMap.setOnMapLongClickListener(this);
addMapPoints();
}
Je me rends compte que ce ne sont pas de vraies solutions - juste des substitutions mais c'est le mieux que je puisse faire pour l'instant et le projet doit continuer. Si quelqu'un trouve des solutions plus propres, je serai reconnaissant de me le faire savoir.
Je suis venu ici à la recherche d'indices concernant onSaveInstance
et NullPointerException
. J'ai essayé diverses solutions, dont celle mentionnée par @Izydorr, mais je n'ai pas eu de chance. Le problème était avec FragmentPagerAdapter
- l'adaptateur de la ViewPager
qui hébergeait mes fragments qui intégraient les MapView
s. Après l'avoir changé en FragmentStatePagerAdapter
, les NPE sont partis. Ouf!
Implémente votre fragment/activité avecGoogleMap.OnCameraChangeListener
et la méthode de substitution onCameraChange
dans laquelle vous pouvez enregistrer la nouvelle position de la caméra et utiliser onResume
googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPositionSaved));