【问题标题】:OutOfMemoryError if I restart a finished activity with ViewPager如果我使用 ViewPager 重新启动已完成的活动,则会出现 OutOfMemoryError
【发布时间】:2014-12-17 05:10:29
【问题描述】:

我确实在这个问题上工作过,但找不到解决方案,期待这种不自然的方式https://stackoverflow.com/a/2866717/2008040

我有一个从我的主要活动开始的活动 (WebViewActivityForHeadlines),该活动包含一个自定义视图寻呼机(当我使用标准视图寻呼机时会发生相同的行为)并且此视图寻呼机会膨胀自定义可缩放图像视图(当我使用标准图像视图)。

你可以在下面找到活动代码,我只是剪掉了一些不相关的部分。

@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;
        }
    }
}

当我第一次开始这个活动时,我可以看到堆会增加一点,因为视图分页器会增加 3 个视图,当我在数十个图像之间导航时一切正常。我清楚地看到,当我完成这个活动时,系统不会释放内存,如果我一次又一次地开始这个活动,最终我会得到预期的 OOME。

这是 logcat 输出

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)

你们能帮我找出内存泄漏吗?

【问题讨论】:

    标签: android android-viewpager android-lifecycle android-memory


    【解决方案1】:

    好的,我找到问题了。

    我使用 MAT 分析我的内存泄漏,在使用 MAT 检查我的转储时,我意识到两个单独的库会导致这个问题。顺便说一句,如果您需要更多关于 MAT 的信息View Pager Memory Leak with Bitmaps and Volley

    我的第一个泄漏是 ACRA。 ACRA 是一个崩溃报告工具,因此它是崩溃报告工具的自然行为,ACRA 始终持有对最近关闭的活动的引用,直到另一个活动开始。但这确实不是一个大问题,因为 ACRA 在用户启动另一个活动后立即发布它的引用。所以 ACRA 不是我得到 OOME 的主要原因。

    我的第二个漏洞是 Google Ads。我找不到确切的原因,但是当我完成我的活动时,Google Ads 总是保留对我的 viewPager 的引用,并且一次又一次地打开该活动会导致应用程序因 OOME 而崩溃。现在我只是简单地删除该活动中的广告,但稍后会挖掘这个问题。

    感谢宝贵的 cmets 人。

    【讨论】:

      【解决方案2】:

      大多数内存泄漏问题是由于持有对Context 实例的直接引用。在您的情况下,thisActivity 变量是泄漏源。

      事实上,有一些方法可以避免直接引用上下文导致的内存泄漏。一个明显的选择是对thisActivity 使用WeakReference 并允许GC 清理内存。如果您正在寻找更多“androidy”解决方案,您可以将所有活动引用移动到更本地的范围(例如直接在方法内获取活动引用),或者您可能只想通过调用 Context.getApplicationContext() 来使用应用程序上下文或Activity.getApplication()

      在 Android 开发者博客中有一个很棒的blog post,您可以在其中获得更多详细信息。

      【讨论】:

      • 我将图像加载器作为单例 bean 移动,并将这个 bean 注入到我的应用程序类中。我只在有问题的活动中使用了我的应用程序中的新 bean 引用。但是每次我重新启动活动时,堆仍然在增长。我还删除了 thisActivity,现在我没有任何对我的活动的引用。
      • 出于好奇,您的 ImageLoader 或您的活动是否曾经回收过您的位图?
      • 当我在同一个活动中滚动图像时没有内存泄漏,当我一次又一次地重新启动活动时出现问题。
      【解决方案3】:

      完成活动后堆没有减少的事实表明 GC 无法处理它。一个典型的罪魁祸首是持久对象仍然持有对它的引用。

      我建议您检查您必须确保您的活动上下文不会被保留在其生命周期之外的内容。例如,考虑一下您的应用程序的以下问题:

      1. ImageLoader 在您的活动中创建
      2. 图像开始在后台进程中加载​​
      3. 只要后台进程正在运行,就存在对 Activity 的引用,因为该任务引用了在 Activity 实例中创建的 SimpleImageLoadingListener 实例
      4. 与此同时,您完成了活动
      5. 该活动没有被 GC,因为 ImageLoader 正在引用 SimpleImageLoadingListener,它引用了该活动
      6. 启动新活动超出堆大小并导致 OOME

      一个可能的解决方案是使用单例 SimpleImageLoadingListener,您可以在创建 Activity 时连接到它。

      我希望这可以对问题有所了解!

      【讨论】:

      • 我将图像加载器作为单例 bean 移动,并将这个 bean 注入到我的应用程序类中。我只在有问题的活动中使用了我的应用程序中的新 bean 引用。但是每次我重新启动活动时,堆仍然在增长。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-09-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-23
      相关资源
      最近更新 更多