1

I really worked on this issue but cannot find a solution expect this unnatural way https://stackoverflow.com/a/2866717/2008040

I have an activity (WebViewActivityForHeadlines) which is started from my main activity, this activity contains a custom view pager (same behavior happens when I use standard view pager) and this view pager inflates custom zoomable image views (same behavior happens when I use standard image view).

You can find code of activity below, I just cut out some unrelated parts.

@Fullscreen
@EActivity(R.layout.activity_web_view_for_headlines)
public class WebViewActivityForHeadlines extends BaseActivity
{
    /** The log tag. */
    private static final String LOG_TAG = WebViewActivityForHeadlines.class.getSimpleName();

    private static int NUM_PAGES;

    private static String CURRENT_URL = "";
    private static String CURRENT_TITLE = "";

    HeadlineListAdapter headlineListAdapter = GazetelikApplication.headlineListAdapter;

    @ViewById(R.id.mPager)
    public static JazzyViewPager MPAGER;

    PagerAdapter mPagerAdapter;

    @Bean
    GazetelikSharingHelper gazetelikSharingHelper;

    @ViewById
    Button webViewButtonBack;

    @ViewById
    Button webViewButtonForward;

    @ViewById
    Button webViewButtonHome;

    @ViewById
    Button webViewButtonHide;

    @ViewById
    Button webViewButtonShare;

    @ViewById
    Button webViewButtonClose;

    @ViewById(R.id.webViewControlPanel)
    RelativeLayout controlPanel;

    @ViewById(R.id.upperLayout)
    RelativeLayout upperLayout;

    @ViewById(R.id.mainRelativeLayout)
    RelativeLayout mainLayout;

    @ViewById(R.id.webViewShowControlPanelPanel)
    RelativeLayout showControlPanelPanel;

    Boolean isFirstRun = true;
    Integer orderIdOfStart = null;

    String tinyUrlString = "";
    boolean isTinyUrlReady;
    int tinyUrlCount;

    // ProgressDialog mProgressDialog;

    /** The view to show the ad. */
    private AdView adView;

    final Activity thisActivity = this;

    DisplayImageOptions options;

    Resources res;

    @SuppressLint("NewApi")
    @AfterViews
    void afterViews()
    {
        NUM_PAGES = headlineListAdapter.getCount();

        Intent thisIntent = getIntent(); // gets the previously created intent
        orderIdOfStart = thisIntent.getIntExtra("orderId", 0);

        mPagerAdapter = new ImageAdapter();
        MPAGER.setAdapter(mPagerAdapter);

        MPAGER.setCurrentItem(orderIdOfStart);

        MPAGER.setTransitionEffect(JazzyViewPager.TransitionEffect.Accordion);

        View decorView = getWindow().getDecorView();
        if (Build.VERSION.SDK_INT == 14 || Build.VERSION.SDK_INT == 15)
        {
            int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
            decorView.setSystemUiVisibility(uiOptions);
        }
        else if (Build.VERSION.SDK_INT >= 16)
        {
            if (Build.VERSION.SDK_INT >= 19)
            {
                int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
                decorView.setSystemUiVisibility(uiOptions);
                getTemporaryCache().setWebViewControlPanelVisible(true);
            }
            else
            {
                int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN;
                decorView.setSystemUiVisibility(uiOptions);
            }
        }

        res = getResources();

        DisplayImageOptions options = new DisplayImageOptions.Builder()
                .showImageOnLoading(R.drawable.ic_launcher)
                .showImageForEmptyUri(R.drawable.ic_launcher)
                .cacheInMemory(false)
                .build();
    }

    /** Called before the activity is destroyed. */
    @Override
    public void onDestroy()
    {
        try
        {
            upperLayout.removeAllViews();
            mainLayout.removeAllViews();
            mPagerAdapter = null;
            MPAGER.removeAllViews();
        }
        catch (Exception e)
        {
            Toast.makeText(thisActivity, e.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
        }

        super.onDestroy();
    }

    @Click(R.id.webViewButtonBack)
    void onButtonBackClick()
    {
        int desiredPage = MPAGER.getCurrentItem() - 1;

        if (desiredPage < 0)
        {
            desiredPage = 0;
        }

        if (desiredPage > mPagerAdapter.getCount())
        {
            desiredPage = mPagerAdapter.getCount();
        }

        MPAGER.setCurrentItem(desiredPage);

    }

    @Click(R.id.webViewButtonForward)
    void onButtonForwardClick()
    {
        int desiredPage = MPAGER.getCurrentItem() + 1;

        if (desiredPage < 0)
        {
            desiredPage = 0;
        }

        if (desiredPage > mPagerAdapter.getCount())
        {
            desiredPage = mPagerAdapter.getCount();
        }

        MPAGER.setCurrentItem(desiredPage);
    }

    @Click(R.id.webViewButtonHome)
    void onButtonHomeClick()
    {
        MPAGER.setCurrentItem(orderIdOfStart);
    }

    @Click(R.id.webViewButtonHide)
    void onButtonHideClick()
    {
        upperLayout.removeView(controlPanel);
        showControlPanelPanel.setVisibility(RelativeLayout.VISIBLE);
    }

    @Click(R.id.webViewButtonClose)
    void onButtonCloseClick()
    {
        finish();
    }

    @Click(R.id.webViewButtonShare)
    void onButtonShareClick()
    {
        ...
    }

    @Background
    void buildTinyUrl(String longUrl)
    {
        ...
    }

    @UiThread
    void onOtherShareButtonClickTask(String url)
    {
        ...
    }

    @UiThread
    void onFacebookShareButtonClickTask(String url)
    {
        ...
    }

    @UiThread
    void onTwitterShareButtonClickTask(String url)
    {
        ...
    }

    @Click(R.id.webViewButtonShow)
    void onButtonShowClick()
    {
        upperLayout.addView(controlPanel);
        showControlPanelPanel.setVisibility(RelativeLayout.INVISIBLE);
    }

    @UiThread
    void popupToast(String message)
    {
        if (message != null)
        {
            Toast.makeText(this, message, Toast.LENGTH_LONG).show();
        }
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        RelativeLayout layout = (RelativeLayout) findViewById(R.id.headlinesForAddRelativeLayout);
        layout.removeView(adView);

        if (adView != null)
        {
            // Destroy the AdView.
            adView.destroy();
        }
        super.onConfigurationChanged(newConfig);

    }

    @Override
    public void onBackPressed()
    {
        if (orderIdOfStart == MPAGER.getCurrentItem())
        {
            super.onBackPressed();
        }
        else if (MPAGER.getCurrentItem() > orderIdOfStart)
        {
            MPAGER.setCurrentItem(MPAGER.getCurrentItem() - 1);
        }
        else if (MPAGER.getCurrentItem() < orderIdOfStart)
        {
            MPAGER.setCurrentItem(MPAGER.getCurrentItem() + 1);
        }
    }

    private class ImageAdapter extends PagerAdapter
    {
        private LayoutInflater inflater;

        ImageAdapter()
        {
            inflater = LayoutInflater.from(thisActivity);
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object)
        {
            container.removeView((View) object);
        }

        @Override
        public int getCount()
        {
            return NUM_PAGES;
        }

        @Override
        public Object instantiateItem(ViewGroup view, int position)
        {
            View imageLayout = inflater.inflate(R.layout.simple_image_view, view, false);
            assert imageLayout != null;
            final TouchImageView imageView = (TouchImageView) imageLayout.findViewById(R.id.image);
            final ProgressBar spinner = (ProgressBar) imageLayout.findViewById(R.id.loading);

            ImageLoader.getInstance().displayImage(headlineListAdapter.getItem(position).getUrl(), imageView, options, new SimpleImageLoadingListener()
            {
                @Override
                public void onLoadingStarted(String imageUri, View view)
                {
                    spinner.setVisibility(View.VISIBLE);
                }

                @Override
                public void onLoadingFailed(String imageUri, View view, FailReason failReason)
                {
                    String message = null;
                    switch (failReason.getType()) {
                        case IO_ERROR:
                            message = "Input/Output error";
                            break;
                        case DECODING_ERROR:
                            message = "Image can't be decoded";
                            break;
                        case NETWORK_DENIED:
                            message = "Downloads are denied";
                            break;
                        case OUT_OF_MEMORY:
                            message = "Out Of Memory error";
                            break;
                        case UNKNOWN:
                            message = "Unknown error";
                            break;
                    }
                    Toast.makeText(thisActivity, message, Toast.LENGTH_SHORT).show();
                    spinner.setVisibility(View.GONE);
                }

                @Override
                public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage)
                {
                    spinner.setVisibility(View.GONE);
                    imageView.setZoom(1F);
                }
            });
            view.addView(imageLayout, 0);
            return imageLayout;
        }

        @Override
        public boolean isViewFromObject(View view, Object object)
        {
            return view.equals(object);
        }

        @Override
        public void restoreState(Parcelable state, ClassLoader loader)
        {
        }

        @Override
        public Parcelable saveState()
        {
            return null;
        }
    }
}

When I start this activity first time, I can see heap will go little up as view pager inflates 3 views and all things are ok when I navigate between tens of images. I clearly see that when I finish this activity memory does not released by system and if I start this activity again and again eventually I get OOME as expected.

This is logcat output

java.lang.OutOfMemoryError
            at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
            at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:623)
            at com.nostra13.universalimageloader.core.decode.BaseImageDecoder.decode(BaseImageDecoder.java:78)
            at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.decodeImage(LoadAndDisplayImageTask.java:264)
            at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.tryLoadBitmap(LoadAndDisplayImageTask.java:237)
            at com.nostra13.universalimageloader.core.LoadAndDisplayImageTask.run(LoadAndDisplayImageTask.java:135)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1080)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:573)
            at java.lang.Thread.run(Thread.java:856)

Could you guys please help me to find out memory leak?

Community
  • 1
  • 1
lecandas
  • 250
  • 4
  • 11

3 Answers3

1

The fact that your heap does not reduce when finishing your activity suggests that the GC cannot dispose of it. A typical culprit is that a persistent object is still holding a reference to it.

I suggest that you review what you have to ensure that your activity context isn't being held on to outside of its lifecycle. Consider, for instance, the following problem with your application:

  1. ImageLoader is created inside your activity
  2. An image starts loading in a background process
  3. So long as that background process is running, a reference exists to the activity, because that task references the instance of SimpleImageLoadingListener created inside the activity instance
  4. In the meantime, you finish the activity
  5. The activity is not GC'd, because ImageLoader is referencing SimpleImageLoadingListener, which references the activity
  6. Starting a new activity exceeds the heap size and causes the OOME

A possible solution would be to have a singleton SimpleImageLoadingListener, which you connect to when the activity is created.

I hope this provides some insight into the problem!

Paul Lammertsma
  • 37,593
  • 16
  • 136
  • 187
  • I moved my image loader as an singleton bean and injected this bean to my application class. I only used new bean's reference from my application at problematic activity. But still heap is growing every time I restart activity. – lecandas Oct 21 '14 at 22:29
1

Most of the memory leak problems are due to holding a direct reference to a Context instance. In your case, thisActivity variable is the source of leak.

There are, in fact, ways to avoid memory leaks caused by directly referencing contexts. One obvious choice is to use WeakReference for thisActivity and allow the GC to clear up memory. If you are looking for more "androidy" solution, you can either move all your activity references to a more local scope (like obtaining the activity reference directly inside the method) or you might want to use the application context simply by calling Context.getApplicationContext() or Activity.getApplication()

There is a great blog post in the Android Developers Blog about this where you can get more detailed information.

  • I moved my image loader as an singleton bean and injected this bean to my application class. I only used new bean's reference from my application at problematic activity. But still heap is growing every time I restart activity. I also removed thisActivity and now I'm not holding any reference of my activity. – lecandas Oct 21 '14 at 22:30
  • Just out of curiosity, does your ImageLoader or your activity ever recycle your Bitmaps? – Kaan Gürkan Oct 28 '14 at 14:41
  • There is no memory leak when I just scroll over the images in same activity, problem occurs when I restart the activity again and again. – lecandas Nov 19 '14 at 22:46
0

Ok, I find out the issue.

I used MAT to analyze my memory leaks and while inspecting my dump with MAT I realized that two separate libs causes this issue. By the way this is a good thread if you need some more information about MAT View Pager Memory Leak with Bitmaps and Volley

My first leak is ACRA. ACRA is a crash reporting tool, so as it is a natural behavior for a crash reporting tool, ACRA always holds a reference to recently closed activity until another activity started. But indeed this is not a big problem because ACRA release its reference immediately after user launches another activity. So ACRA is not the main reason I'm getting OOME.

My second leak is Google Ads. I couldn't find out the exact reason but when I finish my activity Google Ads always keeps a reference to my viewPager and opening that activity again and again cause app to crash because of OOME. For now I just simply remove the ads at that activity but will dig this problem later.

Thanks for precious comments folks.

Community
  • 1
  • 1
lecandas
  • 250
  • 4
  • 11