如果我使用ViewPager重新启动已完成的活动,则OutOfMemoryError

时间:2014-10-21 20:34:13

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

我真的在解决这个问题,但找不到解决方案,期望这种不自然的方式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)

你能帮我找一下内存泄漏吗?

3 个答案:

答案 0 :(得分:1)

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

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

  1. 在活动中创建了ImageLoader
  2. 图像开始在后台进程中加载​​
  3. 只要该后台进程正在运行,就会存在对该活动的引用,因为该任务引用在活动实例中创建的SimpleImageLoadingListener实例
  4. 与此同时,您完成了活动
  5. 活动不是GC,因为ImageLoader引用了引用活动的SimpleImageLoadingListener
  6. 启动新活动超出堆大小并导致OOME
  7. 一个可能的解决方案是拥有一个单例SimpleImageLoadingListener,您可以在创建活动时连接它。

    我希望这可以提供一些有关问题的见解!

答案 1 :(得分:1)

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

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

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

答案 2 :(得分:0)

好的,我发现了这个问题。

我用MAT来分析我的内存泄漏,在用MAT检查我的转储时,我意识到两个独立的库导致了这个问题。顺便说一下,如果您需要有关MAT View Pager Memory Leak with Bitmaps and Volley

的更多信息,这是一个很好的主题

我的第一次泄漏是ACRA。 ACRA是一种崩溃报告工具,因为它是崩溃报告工具的自然行为,ACRA始终保留对最近关闭的活动的引用,直到另一个活动开始。但实际上这不是一个大问题,因为ACRA在用户启动另一项活动后立即发布其参考。所以ACRA并不是我获得OOME的主要原因。

我的第二个漏洞是谷歌广告。我无法找到确切的原因但是当我完成我的活动时,Google广告始终会保留对我的viewPager的引用并再次打开该活动,导致应用因OOME而崩溃。目前我只是删除该活动的广告,但稍后会解决此问题。

感谢宝贵的评论。