Out of memory error

0

Czesc,
mam rozbudowana aplikacje na androida. Mam tam ekran z mapa na ktora uzytkownicy moga dodawac rozne znaczniki (roznia sie grafika i danymi jakie zawieraja). Obecnie jest tam okolo 50 znacznikow, jest dodana funkcja clusterowania. Pewnego razu wywalilo mi apke z tytulowym bledem przegladajac ta mape. Mialem juz takie problemy przy wyswietlaniu zdjec przez nie-picasso - ale udalo mi sie znalezc rozwiazanie. Pytanie jak zadbac o to w tym przypadku oraz jakie jeszcze podjac dzialania, aby apka sie nie wywalala przez ten blad w innych miejscach (jeszcze sie to nie zdarzylo). Aplikacja ma byc niedlugo wypuszczona do ludzi, ale obawiam sie, ze moze wyjsc jeszcze wiele takich niespodzianek i czy moglibyscie mnie pokierowac jakies linki, hasla czego szukac, nastepnie zaimplementowac, aby apka sie nie wywalala. Na chwile obecna jedyne przez co mi sie wywala to out of memory. Ma ona okolo 8 tys linii kodu bez testow - nie ma testow ;s. Okolo 40 ekranow i laczy sie z 2 bazami (restowe api) przy pomocy okolo 40-50 endpointow. Backend jest pokryty testami.

2

Jak kod jest napisany tak jak to pytanie, to współczuję bo nawet testy by nie pomogły.

Z tego pytania niewiele wynika, więc mogę doradzić tylko tyle (kolejność ważna):

  • zintegrować Firebase (Google Analitics dla aplikacji mobilnych) do aplikacji (crashe i logi będą łatwiejsze w analizie), nie musisz oznaczać wydarzeń w aplikacji ważniejsze, żebyś miał crash logi i logi aplikacji tuż przed crashem
  • przetestować manualnie bardzo intensywnienie. Jak nie masz testerów to rozdać za free apkę znajomym, niech jej nieco poużywają.
  • włączyć jakiś profiler i przeanalizować co marnuję pamięć (i to naprawić)
0

Dzieki za odpowiedz, zabieram sie za to!
P.S Kod jest lepiej napisany niz pytanie, ciezki tydzien, zmeczenie i stad slabo uporzadkowane pytanie, ale zinterpretowales dobrze. O taka odpowiedz mi chodzilo :)

1

Kojarzę podobny problem z mapami Google u siebie. Jak było dużo markerów generowanych dynamicznie (u mnie: nakładany tekst na grafikę, z tego obiekt Bitmap i to ustawiane jako marker), to wywalał out of memory przy rysowaniu mapy - przy renderowaniu któregoś markera z kolei. Działo się to tylko na niektórych telefonach, np na moim telefonie było ok, na emulatorze też było ok, a na Samsungu wywalał się. Ostatecznie, tematu nie rozwiązałem i musiałem przejść na na statyczne markery (tzn, bitmapa osadzona w zasobach i rezygnacja z nakładania różnych tekstów na różne markery) i problem zniknął. Kombinowałem na wiele sposobów, próbowałem zwalniać bitmapy, nic to nie dało, wydaje sie jakby problem leżał w tym, jak mapa rysuje markery.

Może i ty trafiłeś na to samo, sam chętnie poznam rozwiązanie. Moje konkluzje były takie, że to albo jakiś bug w romach niektórych producentów, albo w samym api map (ale na czystym Androidzie od Google problemów nie było z tym nigdy).

P.S. jeśli chodzi o testy, to zainteresuj się testowaniem w chmurze Google. W Firebase (espresso), dodatkowo jeśli wypuszczasz aplikację jako beta, to Google automatycznie przeprowadza testy twojej aplikacji na emulatorach wielu urządzeń - generalnie, jest to automatyczne przeklikanie całej aplikacji, jak poleci jakiś wyjątek to dostaniesz staktrace, a jeszcze dostajesz nagrany w film z całego procesu "klikania" + zrzuty ekranu i statystyki obciążenia pamięci, cpu itd (w zasadzie, jest to raport z profilera) z linią czasu zsynchronizowaną z filmem z testu. To nie wymaga pisania żadnych testów, bo proces odbywa się automatycznie, a usługa jest darmowa.

0

Odpalilem Android profiler i przeklikalem nieco aplikacje, wychodzi na to, ze po przejsciu z jednej aktywnosci do drugiej GC nie zwalnia pamieci RAM. Wiec odswiezajac parenascie razy mape google (kazde odswiezenie to 50-60mb - przy czym mowiac odswiezenie mowie o odpaleniu od nowa calej aktywnosci) dochodze do momentu w ktorym aplikacja zajmuje 1.2 GB i wywala OOM. Przy czym mape odpala z przyblizeniem 13, wiec nie sciaga calej mapy. Poza tym nie ma znaczenia czy na mapie sciagniete sa markery czy nie poniewaz sa to grafiki wektorowe. Dla pewnosci porownywalem zuzycie pamieci przy 100 markerach i 0 markerach i nie ma znaczacej roznicy

Pytanie jak najlepiej rozwiazac ten problem. Probowalem nastepujacego kodu w aktywnosci z mapa, ale bez rezultatow.

@Override
 protected void onPause() {
     super.onPause();
     mMap.clear();
     System.gc();
 }

 @Override
 protected void onStop() {
     super.onStop();
     mMap.clear();
     System.gc();
 }

<activity
         android:name=".Maps.StartActivity"
         android:label="@string/title_activity_start"
         android:largeHeap="true"/>

Wklejam tez kod aktywnosci z mapa:

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);
        topPanelImageFragment();
        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
        apiServiceStart= ApiUtils.getAPIService();

        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);

    }

    @Override
    public void onResume(){
        super.onResume();
        if( Login.id_restore(getBaseContext()) != 0)
            currentUserId= Login.id_restore(getBaseContext());
    }



    public void HereStart (View view){
        AddActivityFragmentMap dialog = new AddActivityFragmentMap();
        dialog.show(getSupportFragmentManager(),"AddActivityFragmentMap");
    }
    public void SearchActivity (View view) {
        topPanelSearchFragment();
    }



    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        if (ActivityCompat.checkSelfPermission(getApplicationContext(), ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(getApplicationContext(), ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {

        } else {
            mMap.setMyLocationEnabled(true);
        }
        try {
            Location locationGPS = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
            //here what you need:
            double latitude = locationGPS.getLatitude();
            double longitude = locationGPS.getLongitude();
            //create marker
            myGPSPosition = new LatLng(latitude, longitude);
            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(myGPSPosition, 13));

        }catch (NullPointerException | IllegalArgumentException e){
            e.printStackTrace();
            Toast.makeText(this,"Unable to get GPS data",Toast.LENGTH_SHORT).show();
        }


        mClusterManager = new ClusterManager<MyItem>(this, mMap);
        mMap.setOnCameraIdleListener(mClusterManager);
        mMap.setOnMarkerClickListener(mClusterManager);



        Intent intent = getIntent();
        int fragmentAddActivityFlag = intent.getIntExtra("fragmentAddActivityFlag",0);
        if(fragmentAddActivityFlag==1) {
            rodzaj = intent.getExtras().getString("kindOfActivity");
            opis = intent.getExtras().getString("descriptionOfActivity");
            data = intent.getExtras().getString("startDate");
            godzina = intent.getExtras().getString("startHour");
            adres = intent.getExtras().getString("adres");
            lat = intent.getExtras().getDouble("lat", 0);
            lng = intent.getExtras().getDouble("lng", 0);

            Log.i("sdgjt44ddtr3", "sukces0" );


                    sendMarkerCall(currentUserId ,lat,lng,rodzaj,opis,"nazwa1", data, godzina, adres);




        }
        fragmentAddActivityFlag=0;


         intent = getIntent();
        int EditActivityFlag = intent.getIntExtra("EditActivityFlag",0);
        if(EditActivityFlag==1) {
            Double lat = intent.getExtras().getDouble("EditActivityLat",0);
            Double lng = intent.getExtras().getDouble("EditActivityLng",0);
            LatLng latLng = new LatLng(lat, lng);
            mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15));
        }
        EditActivityFlag=0;


        intent = getIntent();
        int filtrMarkerListFlag = intent.getIntExtra("filtrMarkerListFlag",0);
        if(filtrMarkerListFlag==1) {
         

            List<Post> filtrMarkerList = this.getIntent().getExtras().getParcelableArrayList("ArrayList");
            Log.i("asdwertyisd345", "lista:  " + filtrMarkerList);
            int liczba  = filtrMarkerList.size();
            for(int i=0;i<liczba;i++) {
                int activityIdFiltredMarkers = filtrMarkerList.get(i).getActivity_id();
                String kindOfActivity = filtrMarkerList.get(i).getKind_of_activity();
                double lat = filtrMarkerList.get(i).getLat();
                double lng = filtrMarkerList.get(i).getLng();
                Log.i("asdh523ukas", "lista:  " + kindOfActivity + "  " + lat +"  "+ lng + "  "+ activityIdFiltredMarkers);
                setMarker(kindOfActivity, lat, lng, activityIdFiltredMarkers, false);
            }
        }
        else {
      getAllMarkers(Login.currentUserId, "all");
        }
        filtrMarkerListFlag=0;





    }


    public void sendMarkerCall(int user_id, double lat, double lng,  String kind_of_activity, String description_of_activity, String nameOfActivity, String date, String hour,String adress) {
        Login.compositeDisposable
                .add(apiServiceStart.markerSendInterface(user_id,lat,lng, kind_of_activity,description_of_activity,nameOfActivity, date, hour, adress)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        new io.reactivex.functions.Consumer<List <Post>>() {
                            @Override
                            public void accept(List <Post> response)  {

                                markerList=response;

                                int  activityIdSendMarkerCall = markerList.get(0).getActivity_id();
                                Double lat = markerList.get(0).getLat();
                                Double lng = markerList.get(0).getLng();
                                String type = markerList.get(0).getKind_of_activity();

                                    setMarker(type ,lat,lng, activityIdSendMarkerCall, true);




                            }
                        }


                ));
    }



public void setMarker(String kind, Double lat, Double lng, int markerId, boolean zoom ){

    Log.i("asdh523ukas2", "lista:  " + kind + "  " + lat +"  "+ lng + "  "+ markerId);

    LatLng latLng = new LatLng(lat, lng);
    your_variable=getDrawableId(kind);
    offsetItem = new MyItem(latLng, markerId,
            bitmapDescriptorFromVector(getBaseContext(),your_variable));
    

    mClusterManager.addItem(offsetItem);


    CustomClusterRenderer renderer = new CustomClusterRenderer(getBaseContext(), mMap, mClusterManager);
    mClusterManager.setRenderer(renderer);

    if(zoom)
    mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15));

    mClusterManager.setOnClusterItemClickListener(
            new ClusterManager.OnClusterItemClickListener<MyItem>() {
                @Override public boolean onClusterItemClick(MyItem clusterItem) {

                    DialogFragmentJoin dialog = new DialogFragmentJoin();
                    dialog.show(getSupportFragmentManager(),"DialogFragmentJoin");

                    //PACK DATA IN A BUNDLE
                    int i = (int)  clusterItem.getIdOfMarker();
                    Bundle bundle = new Bundle();
                    bundle.putInt("ActivityId", i);

                    //PASS OVER THE BUNDLE TO OUR FRAGMENT
                    dialog.setArguments(bundle);
                    return true;


                }
            });
}


    public void getAllMarkers(int user_id, String mode) {
        Login.compositeDisposable.add( apiServiceStart.getActivitiesInterface(user_id, mode)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(
                        new Consumer<List <Post>>() {
                            @Override
                            public void accept(List <Post> response) throws Exception {

                                markerList=response;
                                int liczba  = markerList.size();
                                for(int i=0;i<liczba;i++){

                                    int  activityIdAllMarkers = markerList.get(i).getActivity_id();
                                    String kindOfActivity = markerList.get(i).getKind_of_activity();
                                    Double lat = markerList.get(i).getLat();
                                    Double lng = markerList.get(i).getLng();
                                    setMarker(kindOfActivity, lat, lng, activityIdAllMarkers, false);
                                    Log.i("sad323tr76ikessd5s", "id: " + your_variable);
                                }

                            }
                        }
                ));
    }



    public static int getDrawableId(String name){
        try {
            Field fld = R.drawable.class.getField(name);
            return fld.getInt(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return -1;
    }

    public static BitmapDescriptor bitmapDescriptorFromVector(Context context, int vectorResId) {
        Drawable vectorDrawable = ContextCompat.getDrawable(context, vectorResId);
        vectorDrawable.setBounds(0, 0, vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight());
        Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        vectorDrawable.draw(canvas);
        return BitmapDescriptorFactory.fromBitmap(bitmap);
    }

    public  void topPanelImageFragment(){

        TopPanelFragmentImage myFragment = new TopPanelFragmentImage();
        FragmentManager manager = getFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();


        transaction.addToBackStack(null);
        transaction.replace(R.id.frame_map, myFragment);
        transaction.commit();
    }

    public void topPanelSearchFragment(){

        TopPanelFragmentSearch myFragment = new TopPanelFragmentSearch();
        FragmentManager manager = getFragmentManager();
        FragmentTransaction transaction = manager.beginTransaction();


        transaction.addToBackStack(null);
        transaction.replace(R.id.frame_map, myFragment);
        transaction.commit();
    }

    @Override
    protected void onPause() {
        super.onPause();
        mMap.clear();
        System.gc();
    }

    @Override
    protected void onStop() {
        super.onStop();
        mMap.clear();
        System.gc();
    }


}


Dzieki za informacje o testowaniu w chmurze google, przerobie temat.

0

Nie chodzi o to, czy masz grafiki wektorowe, czy nie a o to, że masz gdzieś w kodzie dynamicznie utworzona bitmapę i ustawiasz ją jako wygląd markera. Spróbuj do testów zakomentować ten cały kod i użyć wprost standardowych markerów. Jak przestanie się wywalać, to masz odpowiedź.

P.S. Masz tam rxjava, włącz Javę 8 i użyj lambd, ciężko to nawet czytać

0

Zrobilem jak opisales, ale niestety nie podzialalo. U Ciebie byl inny problem bo juz w momencie rysowania wywalalo Ci OOM. U mnie rysuje i wszystko dziala, tylko po odswiezeniu mapy kilkanascie razy pamiec ram sie zapycha z kazdym odswiezeniem. Chcialbym jakos ten element wyeliminowac.

0

No może i inny. U mnie jednak też nie wywalał za pierwszym razem, tylko za którymś kolejnym

0

Zrobilem nastepujaca rzecz:

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

i teraz przy kazdym odswiezeniu zajmuje tylko +-15mb wiecej ramu, ale nadal sie wywala aplikacja z OOM. Wczesniej gdy odswiezenie zajmowalo 50-60mb moglem to zrobic kilkanascie razy az dochodzilo do 1.2GB i wywalalo OOM. Obecnie moge to zrobic tez przyblizona ilosc razy az dojdzie do okolo 0.4GB. Testowalem tez bez nakladanie grafik na markery - rowniez bez rezultatu.

0

Odpowiedź razem z małym PR:

  1. W onCreate dajesz mapFragment.getMapAsync(this);
    Po pierwsze: Czy obsługujesz przypadek gdy użytkownik wyjdzie z aktywności zanim mapa będzie gotowa (zanim wywoła się ten callback)? Druga sprawa: jeśli podajesz this czyli referencję do aktywności do SupportMapFragment czy gdzieś w kodzie jest usunięcie tej referencji? Jeśli nie ma to tutaj może być wyciek pamięci.

  2. Czy w onResume korzystasz ze statycznej metody Login.id_restore? Jeśli tak to: złe miejsce sprawdzenia, zła konwencja nazwy metody, całe podejście jest błędne.

  3. public void HereStart (View view) / *public void SearchActivity(View view) *- domyślam się że w taki sposób robisz komunikację pomiędzy aktywnościami - do zmiany. Tutaj też może być wyciek pamięci, gdzieś w kodzie robisz new MapActivity().SearchActivity(...)

  4. Usuń polskie nazwy zmiennych, popraw formatowanie:
    if(fragmentAddActivityFlag==1) {
    rodzaj = intent.getExtras().getString("kindOfActivity");

  5. Trzy razy wywołane Intent intent = getIntent();?

  6. sendMarkerCall(int user_id, double lat, double lng, String kind_of_activity, String description_of_activity, String nameOfActivity, String date, String hour,String adress) Dwie różne notacje, niepotrzebny skrót i dwie literówki w jednej linii - nie mówiąc już o liczbie parametrów.

public static int getDrawableId(String name){
try {
Field fld = R.drawable.class.getField(name);
return fld.getInt(null);
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}

Publiczna statyczna metoda do pobierania id drawable po nazwie w klasie ~MapActivity? Co będzie jak jednak takiego pliku nie będzie w aplikacji? Brak weryfikacji podczas kompilacji. Refleksje to zło.

8. *bitmapDescriptorFromVector* Tutaj też może być źródło Twojego problemu, zamieniasz w statycznej (sic!) metodzie id vectora na bitmape. Pytanie: jaki rozmiar będzie mieć ta bitmapa? Ustawiasz tam 

vectorDrawable.setBounds(0, 0, vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight());
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);

Co na pierwszy rzut oka mówi: ustaw rozmiar taki jak vector a przecież obraz w postaci vectorowej ~nie ma rozmiaru.

9. Usuń System.gc();
0

proponuję zacząć od tego
https://github.com/square/leakcanary

Ale pierwsze co bym zrobił to powywalał wszystkie statyczne metody z kodu, poczytał na temat komunikacji pomiędzy aktywnościami etc. Jak na kod produkcyjny to jeszcze sporo brakuje. Ale od razu Cie pocieszę, że widziałem gorsze

1 użytkowników online, w tym zalogowanych: 0, gości: 1