Google Maps v2 lag after popping their fragment back from stack
Asked Answered
F

3

7

I have an Activity with a MapFragment that I add to the Activity programmatically using a FragmentTransaction:

private static final String MAP_FRAGMENT_TAG = "map";
private MapFragment mapFragment = null;

...

protected void onCreate(Bundle savedInstanceState) {

    ...

    mapFragment = (MapFragment) getFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG);
    if (mapFragment == null) {
        mapFragment = MapFragment.newInstance();
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.fragment_wrapper, mapFragment, MAP_FRAGMENT_TAG);
        fragmentTransaction.commit();
    }

    ...

}

Standard way. Then I get the GoogleMap instance from the mapFragment and set its settings, set the listeners, do stuff with it. Everything works fine.

Then when the user is done with the map, an AsyncTask gets triggered to show a ProgressDialog, perform some operation, put a different fragment into the fragment_wrapper and dismiss the ProgressDialog again:

private class GetFlightsTask extends AsyncTask<Double, Void, String> {

@Override
protected void onPreExecute() {
    super.onPreExecute();
    // the activity context has been passed to the AsyncTask through its constructor
    loadingFlightsSpinner = new ProgressDialog(context);
    // setting the dialog up
    loadingFlightsSpinner.show();
}

@Override
protected String doInBackground(Double... params) {
    // some pretty long remote API call
    // (loading a JSON file from http://some.website.com/...)
}

@Override
protected void onPostExecute(String flightsJSON) {
    super.onPostExecute(flightsJSON);
    // here I do stuff with the JSON and then I swtich the fragments like this
    FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
    FlightsFragment fragment = new FlightsFragment();
    fragmentTransaction.replace(R.id.fragment_wrapper, fragment);
    fragmentTransaction.addToBackStack(null);
    fragmentTransaction.commit();
    loadingFlightsSpinner.dismiss();
}

Everything still works fine. The user does something in the FlightsFragment and then maybe decides to go back to the map. Presses the back button and the map pops up again. And this is when the map gets laggy. The countries/cities names on it load really slowly, it lags heavily on moving the map... And I have no idea why, I don't do anything on popping the MapFragment back.

What's interesting is that it gets fixed for example on pressing the home button and then returning to the app again...

What am I doing wrong?

Thank you for any ideas.

Fortieth answered 28/4, 2013 at 17:0 Comment(4)
Nothing wrong with the code after edit. The issue is probably in AsyncTask execution.Balderas
Please check the new edit, thank you.Fortieth
Btw, moving the ProgressDialog completely into the AsyncTask doesn't help either.Fortieth
I would really appreciate the person who gave the question a downvote to tell me what's wrong with it so I can edit it...Fortieth
F
1

I have fixed it by dismissing the ProgressDialog already at the end of the AsyncTask's doInBackground() method rather than at the beginning of the onPostExecute() method.

Which is a bit weird because I actually thought I shouldn't touch things from the UI in the doInBackground() method... If someone wants to elaborate on it a bit, I would be glad to learn why it works like this.

Fortieth answered 8/5, 2013 at 14:28 Comment(0)
P
1

it only lag if you press the back button?

if thats the problem try to block the back button or make it exit the app try this code:

    @Override
public void onBackPressed(){
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("You wanna leave the aplication?").setPositiveButton("Yes", dialogClickListener)
            .setNegativeButton("No", dialogClickListener).show();

}

or try this code its a way to put a map fragment inside another fragment (nested Map Fragment) it worked for me a weeks ago:

Java Class:

public class Yourfragment extends Fragment {

    private MapView mMapView;
    private GoogleMap mMap;
    private Bundle mBundle;

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

        try {
            MapsInitializer.initialize(getActivity());
        } catch (GooglePlayServicesNotAvailableException e) {
            // TODO handle this situation
        }

        mMapView = (MapView) inflatedView.findViewById(R.id.map);
        mMapView.onCreate(mBundle);
        setUpMapIfNeeded(inflatedView);

        return inflatedView;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBundle = savedInstanceState;
    }

    private void setUpMapIfNeeded(View inflatedView) {
        if (mMap == null) {
            mMap = ((MapView) inflatedView.findViewById(R.id.map)).getMap();
            if (mMap != null) {
                setUpMap();
            }
        }
    }

    private void setUpMap() {
        mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
    }

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

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

    @Override
    public void onDestroy() {
        mMapView.onDestroy();
        super.onDestroy();
    }
}

XML:

<com.google.android.gms.maps.MapView
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent" />

put this code on post execute:

View inflatedView = inflater.inflate(R.layout.map_fragment, container, false);

try {
    MapsInitializer.initialize(getActivity());
} catch (GooglePlayServicesNotAvailableException e) {
    // TODO handle this situation
}

mMapView = (MapView) inflatedView.findViewById(R.id.map);
mMapView.onCreate(mBundle);
setUpMapIfNeeded(inflatedView);

return inflatedView;

and call the assynctask on oncreateview

Try this:

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    //Call assyncTask
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBundle = savedInstanceState;
}

private void setUpMapIfNeeded(View inflatedView) {
    if (mMap == null) {
        mMap = ((MapView) inflatedView.findViewById(R.id.map)).getMap();
        if (mMap != null) {
            setUpMap();
        }
    }
}

private void setUpMap() {
    mMap.addMarker(new MarkerOptions().position(new LatLng(0, 0)).title("Marker"));
}

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

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

@Override
public void onDestroy() {
    mMapView.onDestroy();
    super.onDestroy();
}
private class GetFlightsTask extends AsyncTask<Double, Void, String> {

@Override
protected void onPreExecute() {
    super.onPreExecute();
    // if I remove the next line, everything gets fixed
    loadingFlightsSpinner.show();
}

@Override
protected String doInBackground(Double... params) {
    // some pretty long remote API call
    // (loading a JSON file from http://some.website.com/flights?...)
    // works fine
    String flightsJSON = loadJSON("flights?flyFrom=CZ&to=...");
}

@Override
protected void onPostExecute(String flightsJSON) {
    super.onPostExecute(flightsJSON);
    loadingFlightsSpinner.dismiss();
    // here I do stuff with the JSON and then replace the fragment
    dohardwork()
}
public view dohardwork(){
    View inflatedView = inflater.inflate(R.layout.map_fragment, container, false);

    try {
        MapsInitializer.initialize(getActivity());
    } catch (GooglePlayServicesNotAvailableException e) {
        // TODO handle this situation
    }

    mMapView = (MapView) inflatedView.findViewById(R.id.map);
    mMapView.onCreate(mBundle);
    setUpMapIfNeeded(inflatedView);

    return inflatedView;
}
Plaintive answered 6/5, 2013 at 11:33 Comment(6)
First of all, thank you for your answer. Ending the application on pressing the back button is not an option. When the FlightsFragment is displayed and the user presses the back button, I need to go back to the map.Fortieth
As to the second suggested solution - I have quickly tried it and it seems to work. The map reloads everytime the fragment is popped back from stack and it doesn't lag anymore. But it loses the camera position, zoom, markers and such. I think that if I implemented saving those things and restoring them when the fragment comes back to the front, it would work nicely. The problem is that my thesis' deadline is approaching and now I don't have time to implement it anymore. Anyway, unless some better answer comes up (such that would fix it with one line of code or something), the bounty is yours.Fortieth
Do some tests to the code maybe you can get it working like you wantPlaintive
If you want to, check the comments under Maciej's answer. The problem seems to be somewhere else. Just so that the competition for the bounty is fair. Thank you.Fortieth
I call the AsyncTask from the Activity, then I replace the fragments in the onPostExecute() method. Your code seems somehow unrelated...Fortieth
I have already resolved the problem. The problem was in the ProgressDialog as I state in my own answer. I would actually like to split the bounty between both of you, but since I can't do that, I have to grant it to Maciej as his answer pushed me in the right direction. I really thank you anyway and at least give you an upvote as a tiny consolation.Fortieth
S
1

I have run a simple test:

public class MapFragmentOnBackStackExample extends FragmentActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.map_fragment_on_back_stack_example);

        FragmentManager fm = getSupportFragmentManager();
        Fragment f = fm.findFragmentById(R.id.fragment_container);
        if (f == null) {
            f = SupportMapFragment.newInstance();
            FragmentTransaction transaction = fm.beginTransaction();
            transaction.add(R.id.fragment_container, f);
            transaction.commit();
        }
    }

    public void onAddFragmentClick(View view) {
        FragmentManager fm = getSupportFragmentManager();
        FragmentTransaction transaction = fm.beginTransaction();
        transaction.replace(R.id.fragment_container, new MyFragment());
        transaction.addToBackStack(null);
        transaction.commit();
    }

    public static class MyFragment extends Fragment {

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            TextView textView = new TextView(getActivity());
            textView.setText("MyFragment: " + hashCode());
            return textView;
        }
    }
}

and can't see any problems.

I could see the problem when commented if (f == null) { leaving it to always create new fragment on rotation, which is obviously wrong, but that brings some suspicions.

Can you see more than 1 MapFragment in memory at the same time? Try using Eclipse Memory Analyzer (MAT).

Samy answered 7/5, 2013 at 22:15 Comment(4)
Well, I have never worked with MAT before, so I don't know what the best way to get the answer to your question is. But I have tried to dump the HPROF file and then look for SupportMapFragment in the MAT's Histogram view and it found one SupportMapFragment and then one SupportMapFragment$a and one SupportMapFragment$b which I really don't know what is. I did this three times - when the app has started, when I switched the fragments and when I got back to the map. Always with the same result.Fortieth
Please notice that because of compatibility reasons, I am using a SupportMapFragment instead of a MapFragment now, but not much of the code has changed since asking the question and it still does the same thing. And also thank you for your time and answer.Fortieth
@Zabri I'm not sure this can be answered without more info in the question. Can you create a simple example that reproduces the problem or try using my code? I would not want you to paste code from your application, because I often ignore questions with more than 100 lines of code. Do you use any additional libs apart from Maps API v2?Balderas
I have been trying to come up with a simple example that reproduces the problem and I have found out that the problem is probably somewhere else than I thought in the first place. Please check the edit I made to the question. Thank you.Fortieth
F
1

I have fixed it by dismissing the ProgressDialog already at the end of the AsyncTask's doInBackground() method rather than at the beginning of the onPostExecute() method.

Which is a bit weird because I actually thought I shouldn't touch things from the UI in the doInBackground() method... If someone wants to elaborate on it a bit, I would be glad to learn why it works like this.

Fortieth answered 8/5, 2013 at 14:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.