【问题标题】:Android trouble finding memory leakAndroid 无法找到内存泄漏
【发布时间】:2011-11-23 23:34:10
【问题描述】:

我的应用程序遇到内存不足错误。我相当肯定我在某处泄漏了内存,并且我相信我已将其范围缩小到特定的 Activity,并且相信泄漏与 AdMob 有关。

为了说明我所看到的,如果启动我的应用程序,第一个活动占用大约 3Mb(它显示图像)。当第二个活动被加载时,第一个没有被销毁,总堆使用量增加到 ~ 7.8Mb。在第二个活动中加载 AdMob 广告后,总堆大小约为 12.5 Mb。

如果我通过按返回按钮返回第一个活动,则会调用第二个活动的onDestroy() 方法。但是,我的应用程序使用的内存量并没有减少。即使我明确调用 System.gc() 。我可能错了,但堆的数量不应该回到 3Mb 吗?

更让我困惑的是,如果我保持应用程序处于活动状态并再次打开第二个活动,那么每次返回和第四次时,堆大小可能只会增加 500kb 左右。就好像某些东西在第二个 Activity 中保持活跃并被重用,即使该 Activity 已被销毁。

我在这里简化了我的代码,看看是否有人可以告诉我我做错了什么。我还使用 MAT 查看了转储文件,但我不太确定我在看什么,也没有发现太多用处。

我的第一个(默认)活动

public class FirstActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                             WindowManager.LayoutParams.FLAG_FULLSCREEN);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        setContentView(R.layout.main);
     }


     public void startSelectionPage(View v){
         Intent intent = new Intent(FirstActivity.this, ImageSelectActivity.class);
         startActivity(intent);
     }
}

ma​​in.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/startpage" >

        <ImageButton
          android:id="@+id/pb"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@drawable/playbutton"
          android:onClick="startSelectionPage"          
     />
</RelativeLayout>

ImageSelectActivity

public class ImageSelectActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         requestWindowFeature(Window.FEATURE_NO_TITLE);
         getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                             WindowManager.LayoutParams.FLAG_FULLSCREEN);

         setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

         setContentView(R.layout.selectionpage);    

         ((Gallery) findViewById(R.id.gallery))
         .setAdapter(new ImageAdapter(this.getApplicationContext(), 150));     
    }

     @Override
     public void onDestroy() {
        AdView ad = (AdView) findViewById(R.id.adView);
        ad.destroy();
        super.onDestroy();
     }
 }

 class ImageAdapter extends BaseAdapter {
    private Context myContext;
    private int imageBackground;
    private int galleryHeight;

    private int[] myImageIds = {
                R.drawable.canyonthumb,
                R.drawable.yosemitethumb,
                R.drawable.flowerthumb,
                R.drawable.squirrelthumb

    };

    public ImageAdapter(Context c, int galleryHeight) { 
        this.myContext = c;
        TypedArray ta = c.obtainStyledAttributes(R.styleable.GalleryTheme);
        imageBackground = ta.getResourceId(R.styleable.GalleryTheme_android_galleryItemBackground, 1);
        ta.recycle();
        this.galleryHeight = galleryHeight;
    }

    public int getCount() { return this.myImageIds.length; }
    public Object getItem(int position) { return position; }
    public long getItemId(int position) { return position; }

    public View getView(int position, View convertView, ViewGroup parent) {     
        ImageView imageView = new ImageView(myContext);
        imageView.setScaleType(ImageView.ScaleType.FIT_XY);
        imageView.setLayoutParams(new Gallery.LayoutParams(galleryHeight,galleryHeight));
        imageView.setBackgroundResource(imageBackground);
        imageView.setImageResource(this.myImageIds[position]);
        return imageView;
    }
}

selectionpage.xml

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/chooseanimage"/>

        <RelativeLayout
            android:id="@+id/rlayout1"
              android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            >

                    <com.google.ads.AdView android:id="@+id/adView"
                         android:layout_width="wrap_content"
                         android:layout_height="60dp"
                         ads:adUnitId="----"
                         ads:adSize="IAB_BANNER"
                         ads:testDevices="TEST_EMULATOR,---"
                         ads:loadAdOnCreate="true"
                         />

                    <Gallery
                        android:id="@+id/gallery"
                        android:layout_width="fill_parent"
                        android:layout_height="fill_parent"
                        android:layout_below="@id/imageView1"
                        android:layout_above="@+id/adView"
                        />

        </RelativeLayout>   
        </LinearLayout>        

【问题讨论】:

  • 阅读System.gc()的文档。它不是“现在就做”,更像是“如果你现在就做就好了,如果不是我道歉”
  • 谢谢,但我相当肯定 GC 会在某个时候被调用。特别是如果我多次重新启动活动并且堆大小不断增加
  • 它当然会运行,但是没有定义GC会在你调用gc()时立即运行。引用:There is no guarantee that the garbage collector will actually be run.
  • 嗯.. 当onDestroy 被调用时,你是recycling 你的位图吗?如果我没记错的话,位图的处理方式是垃圾收集器无法释放它们使用的内存
  • 我自己并没有回收位图,但我认为这不是问题所在。我又弄乱了 MAT 并发现了以下内容。如果我有关于我的第二个活动的广告,当回到第一个活动时,我的第二个活动会保留在内存中。没有广告,第二个 Activity 被正确销毁。问题出在广告上,我一定没有正确销毁它。

标签: android memory-leaks


【解决方案1】:

看看这个问题:android memory management in activity lifecycle。它与您的问题不完全相同,但答案包含我相信您正在寻找的信息。

因此,与其对内存、垃圾收集器和活动生命周期之间的关系进行冗长的解释,我将向您推荐上述问题。

【讨论】:

  • 我已经阅读了,但我不确定它是否能回答我的问题?如何解释activity销毁后剩余的大量内存
  • 您确定onFinish 正在为您的活动而被调用吗?活动超出范围并不总是意味着它被销毁。
  • 你的意思是onDestroy?因为那肯定会被调用
  • 您是否保留了 Activity 类的任何成员?您是否保留对不同 Activity 中任何子视图或对象的引用?
【解决方案2】:

我不确定我的代码是否正确,但这是我的想法:

ImageSelectActivity 中创建ImageAdapter 的新对象。当您在 setAdapter() 调用中使用 new 时,它是一个匿名对象。

只要您的活动存在,ImageAdapter 对象就不会被销毁。您可以在 onDestroy() 中将您的活动适配器设置为 null

一篇非常好的博客文章是关于你的主题的:avoiding memory leaks

最后第三点:

如果您无法控制,请避免在活动中使用非静态内部类 它们的生命周期,使用静态内部类并进行弱引用 到里面的活动。这个问题的解决方案是使用静态 内部类具有对外部类的弱引用,如在 以 ViewRoot 及其 W 内部类为例

这是你应该做的。

【讨论】:

  • 我尝试了你的建议。事实上,我尝试完全删除 ImageAdapater 类。随着那节课的结束,我运行了我的应用程序,打开了第二个活动并关闭了它。然后我进行了内存转储,发现ImageSelectActivity 仍然存在。如果我查看活动的“GC 根路径”,顶部元素是 adView。我不认为 adView 被破坏了?并且它包含对上下文的引用?感谢您的帮助
【解决方案3】:

我不相信我曾经发生过内存泄漏。问题在于 GC,它似乎允许对象在内存中保留的时间比我预期的要长得多。如果我在 16Mb 模拟器上尝试代码,GC 的工作会更加积极。

我没想到的另一件事是 AdMob 的高内存使用率。向我的一项活动添加广告导致使用了大约 8MB 的堆。

【讨论】:

    猜你喜欢
    • 2011-12-06
    • 1970-01-01
    • 2020-08-19
    • 2018-04-07
    • 1970-01-01
    相关资源
    最近更新 更多