36

Ich habe eine RecyclerView, die eine vertikale Scroll-Liste von Elementen ist. Jeder Listeneintrag enthält eine Google Maps V2 MapView in Lite Mode. Ich nutze diese neue Funktion, die Bitmaps statt einer vollwertigen Karte als Ersatz für die Google Static Maps API zurückgibt.Google Maps Lite-Modus verursacht Jank in RecyclerView

MapView erfordert, dass Sie rufen onCreate(), onResume(), onPause(), onDestroy() usw. von der übergeordneten Aktivität/Fragment des entsprechenden Verfahrens. Wo ist der richtige Ort, um diese aus der RecyclerView.Adapter und/oder RecyclerView.ViewHolder anrufen?

Wie kann ich recycelte MapViews aufräumen, so dass Speicher nicht ausläuft, während die Liste jank frei bleibt?

Google says Lite Mode can be used in lists:

... ‚Lite-Modus‘ Karte Option, ideal für Situationen, in denen Sie eine Reihe von kleineren Karten bieten wollen, oder eine Karte, die so klein ist, dass sinnvolle Interaktion ist nicht praktikabel ist, wie ein Thumbnail in einer Liste.

ListItem.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
       android:layout_width="fill_parent" 
       android:layout_height="fill_parent"> 

    <com.google.android.gms.maps.MapView 
     android:id="@+id/mapImageView" 
     xmlns:map="http://schemas.android.com/apk/res-auto" 
     android:layout_width="80dp" 
     android:layout_height="100dp" 
     map:liteMode="true" 
     map:mapType="normal" 
     map:cameraZoom="15"/> 

<!-- ... --> 

</RelativeLayout> 

RecyclerView.Adapter und ViewHolder

public class NearbyStopsAdapter extends RecyclerView.Adapter<NearbyStopsAdapter.ViewHolder> { 

    private final Context mContext; 

    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 

     MapView map; 

     public ViewHolder(View view) { 
      super(view); 
      map = (MapView) view.findViewById(R.id.mapImageView); 
      // Should this be created here? 
      map.onCreate(null); 
      map.onResume(); 
     } 
    } 

    public NearbyStopsAdapter(Context c) { 
     this.mContext = c; 
    } 

    @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) { 
     View itemView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_item_nearby_stop, viewGroup, false); 
     return new ViewHolder(itemView); 
    } 

    @Override public void onBindViewHolder(ViewHolder holder, int position) { 
     //Call Async Map here? 
     holder.map.getMapAsync(this); 
    } 

    @Override public void onViewRecycled(ViewHolder holder) { 
     // Cleanup MapView here? 
//  if (holder.map != null) { 
//   holder.map.onPause(); 
//   holder.map.onDestroy(); 
//  } 
    } 

    @Override public void onViewAttachedToWindow(ViewHolder holder) { 
     // Setup MapView here? 
//   holder.map.onCreate(null); 
//   holder.map.onResume(); 
    } 

    @Override public void onViewDetachedFromWindow(ViewHolder holder) { 
     // Cleanup MapView here? 
//  if (holder.map != null) { 
//   holder.map.onPause(); 
//   holder.map.onDestroy(); 
//  } 
    } 

    // ... 
} 

Logcat:

I/Google Maps Android API﹕ Google Play services package version: 659943 
W/Google Maps Android API﹕ Map Loaded callback is not supported in Lite Mode 
W/Google Maps Android API﹕ Buildings are not supported in Lite Mode 
W/Google Maps Android API﹕ Indoor is not supported in Lite Mode 
W/Google Maps Android API﹕ Toggling gestures is not supported in Lite Mode 
W/Google Maps Android API﹕ Toggling gestures is not supported in Lite Mode 
W/Google Maps Android API﹕ Toggling gestures is not supported in Lite Mode 
W/Google Maps Android API﹕ Toggling gestures is not supported in Lite Mode 

Update: (3. Februar 2016) Google hat ein Codebeispiel für die Verwendung von Lite Maps in einem ListView veröffentlicht. See here

+0

Ist diese Frage über Ihre RecyclerView nicht glatt oder Garbage Collection eines MapView? Ich glaube nicht, dass diese beiden Probleme eng miteinander verbunden sind. –

+0

Der Jank tritt beim Aufruf von 'getMapAsync (this)' in 'onBindViewHolder (...)' auf, nicht unbedingt auf einem GC. Allerdings wird es auch dort jank sein. –

+0

Verschieben von mapView.onCreate (null); und mapView.getMapAsync (this); von onBindViewHolder zum ViewHolder-Konstruktor hat definitiv eine Menge Jank für mich reduziert. – scottyab

Antwort

0

Sie benötigen eine separate View-Holder-Klasse. Die RecyclerView Adapter-Klasse hat nur onCreateViewHolder() und onBindViewHolder().

Ihr Layout-Datei sollte in etwa so aussehen:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    xmlns:tools="http://schemas.android.com/tools" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:paddingLeft="@dimen/activity_horizontal_margin" 
    android:paddingRight="@dimen/activity_horizontal_margin" 
    android:paddingTop="@dimen/activity_vertical_margin" 
    android:paddingBottom="@dimen/activity_vertical_margin" 
    tools:context=".MyActivity"> 

    <view 
    <com.google.android.gms.maps.MapView 
    android:id="@+id/mapImageView" 
    xmlns:map="http://schemas.android.com/apk/res-auto" 
    android:layout_width="80dp" 
    android:layout_height="100dp" 
    map:liteMode="true" 
    map:mapType="normal" 
    map:cameraZoom="15" /> 

</RelativeLayout> 

Und die onCreate(), onDestroy() wird in der Aktivitätsklasse wie gewohnt aufgerufen werden.

Bitte folgen Sie dieser tutorial, um eine vollständige Übersicht zu erhalten.

+1

Ich denke, Sie haben die Frage vielleicht verpasst. Es ist bereits ein ViewHolder im 'RecyclerView.Adapter' definiert. Die Frage ist, wo sollten 'MapView.onCreate()', 'MapView.onDestroy()' usw. aufgerufen werden? –

+0

Bitte beachten Sie die Zeile direkt unter dem Layout-Datei-Code "Und die onCreate(), onDestroy() wird in der Activity-Klasse wie gewohnt aufgerufen." – AniV

+0

Ihr Link enthält keine 'MapView'. Wie ist es relevant? –

18

Lösung wie folgt:

  1. Implementieren OnMapReadyCallback in ViewHolder Klasse.
  2. In onMapReady, rufen Sie MapsInitializer.initialize, Gaurantee Features können verwendet werden, bevor Sie eine Karte erhalten.

Verwenden Sie diese Klasse, um die Google Maps Android API initialisiert werden, wenn Funktionen verwendet werden müssen, bevor eine Karte zu erhalten.Es muss aufgerufen werden, da einige Klassen wie BitmapDescriptorFactory und CameraUpdateFactory initialisiert werden müssen.

  1. Recyclingkarte von onViewRecycled.


public class NearbyStopsAdapter extends RecyclerView.Adapter<NearbyStopsAdapter.ViewHolder> { 


     @Override 
     public void onBindViewHolder(ViewHolder holder, int position) 
     { 
      //get 'location' by 'position' from data list 
      //get GoogleMap 
      GoogleMap thisMap = holder.gMap; 
      //then move map to 'location' 
      if(thisMap != null) 
      //move map to the 'location' 
      thisMap.moveCamera(...);   
     } 


     //Recycling GoogleMap for list item 
     @Override 
     public void onViewRecycled(ViewHolder holder) 
     { 
      // Cleanup MapView here? 
      if (holder.gMap != null) 
      { 
       holder.gMap.clear(); 
       holder.gMap.setMapType(GoogleMap.MAP_TYPE_NONE); 
      } 
     } 



     public class ViewHolder extends RecyclerView.ViewHolder implements OnMapReadyCallback { 

      GoogleMap gMap; 
      MapView map; 
      ... ... 

      public ViewHolder(View view) { 
       super(view); 
       map = (MapView) view.findViewById(R.id.mapImageView); 

       if (map != null) 
       { 
       map.onCreate(null); 
       map.onResume(); 
       map.getMapAsync(this); 
       } 

      } 


      @Override 
      public void onMapReady(GoogleMap googleMap) { 
       //initialize the Google Maps Android API if features need to be used before obtaining a map 
       MapsInitializer.initialize(getApplicationContext()); 
       gMap = googleMap; 

       //you can move map here to item specific 'location' 
       int pos = getPosition(); 
       //get 'location' by 'pos' from data list 
       //then move to 'location' 
       gMap.moveCamera(...); 

        ... ... 
     } 

     } 
    } 
+0

Ja, das macht es wirklich sehr schnell, dachte leider, es gibt keine Garantie, dass die Karte bereit ist, bevor 'onBindViewHolder (...)' aufgerufen wird, und wenn sie nicht bereit ist, werden alle von Ihnen gesetzten Karteneigenschaften angewendet. das) holt auf. Wenn 'getMapAsync (this)' aufgerufen wird, bevor 'onBindViewHolder (...)' aufgerufen wird, müssen alle Änderungen, die Sie auf der Karte durchführen möchten, wie das Bewegen der Kamera oder das Setzen eines Markers, erneut auf der Karte ausgeführt werden jank auf der Liste. –

+0

'MapsInitializer.initialize (getApplicationContext());' scheint kann es lösen, schaue meine modifizierte Antwort. – Xcihnegn

+0

@AniV Wie geht es mit Ihrem Problem? Noch ein Problem? Bitte sagen Sie – Xcihnegn

0

Google sagt:

Wenn in vollständig interaktive Modus mithilfe der API, den Benutzern der MapView-Klasse alle Aktivität Lebenszyklus Methoden mit den entsprechenden Methoden weiterleiten müssen in die MapView-Klasse. Beispiele für die Lebenszyklusmethoden sind onCreate(), onDestroy(), onResume() und onPause().

Wenn die MapView-Klasse im Lite-Modus verwenden, Lifecycle-Ereignissen Weiterleitung ist optional, mit Ausnahme der folgenden Situationen:

Es ist obligatorisch onCreate() aufrufen, da sonst keine Karte angezeigt wird. Wenn Sie den Punkt "Mein Standort" in Ihrer Lite-Modus-Map anzeigen und die Standardpositionsquelle verwenden möchten, müssen Sie onResume() und onPause() aufrufen, da die Standortquelle nur zwischen diesen Aufrufen aktualisiert wird. Wenn Sie Ihre eigene Standortquelle verwenden, müssen diese beiden Methoden nicht aufgerufen werden.

So auf Lite-Modus Sie müssen nicht über onDestroy Sorge(), onResume() und onPause()

0

I entfernt Diese Override-Methode, weil jedes Mal wenn leere Karte beim Testen gibt und es funktioniert perfekt in meinem RecyclerView.

@Override 
public void onViewRecycled(ViewHolder holder) 
{ 
    // Cleanup MapView here? 
    if (holder.gMap != null) 
    { 
     holder.gMap.clear(); 
     holder.gMap.setMapType(GoogleMap.MAP_TYPE_NONE); 
    } 
} 

Sie können es versuchen, wenn der obige Code auch in Ihrem Fall nicht funktioniert.