使用 ActionBar 和 Spinner 导航的 Android 内存泄漏

发布于 2025-01-04 20:25:24 字数 3999 浏览 0 评论 0原文

我的代码在方向改变时存在大量内存泄漏。旋转手机一两次后堆就满了。我花了两天时间试图追查泄漏情况,但没有成功。

该应用程序使用 SDK 15(至少 14)。

MainActivity 显示带有列表导航的 ActionBar (NAVIGATION_MODE_LIST)。导航微调器会替换显示的片段。

该代码有效。

ListNavigationItem 其中包含实例化 Fragments 所需的信息:

public class ListNavigationItem
{
    public final String fragmentClass;
    public final String title; // Used as fragment tag and as title

    public ListNavigationItem(String fragmentClass, String title)
    {
        this.fragmentClass = fragmentClass;
        this.title = title;
    }

    @Override
    public String toString()
    {
        return title;
    }
}

最后是我的(简化的)MainActivity

public class MainActivity extends Activity implements OnNavigationListener
{
    private MyApplication app;
    private ArrayAdapter<ListNavigationItem> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        app = (MyApplication) getApplicationContext();

        ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
        actionBar.setDisplayShowTitleEnabled(false);
        actionBar.setDisplayShowHomeEnabled(false);

        adapter = new ArrayAdapter<ListNavigationItem>(this, android.R.layout.simple_spinner_dropdown_item);
        adapter.add(new ListNavigationItem(FirstFragment.class.getName(), "First"));
        adapter.add(new ListNavigationItem(SecondFragment.class.getName(), "Second"));

        actionBar.setListNavigationCallbacks(adapter, this);
    }

    public boolean onNavigationItemSelected(int itemPosition, long itemId)
    {
        ListNavigationItem item = adapter.getItem(itemPosition);
        if (item == null)
            return false;

        Fragment f = getFragmentManager().findFragmentByTag(item.title);

        if (f == null)
            f = Fragment.instantiate(this, item.fragmentClass);
        else if (f.isAdded())
            return true;

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        fragmentTransaction.replace(R.id.main_content, f, item.title);
        fragmentTransaction.commit();

        return true;
    }

    @Override
    protected void onDestroy()
    {
        adapter = null; // Do I need this?
        app = null;
        super.onDestroy();
    }
}

清单:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="at.company.app"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="14" />
    <uses-sdk android:targetSdkVersion="15" />

    <application
        android:name=".Application"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

MAT 堆分析表明类 android.content.res .Resources 占用可用内存的 57%,android.graphics.Bitmap 占用 12%。然而,我的片段只包含一个简单的 TextView

MAT 直方图显示 MainActivity 有 4 个对象(我猜是实例?)。此外,logcat 显示堆在方向更改 (GC_CONCURRENT) 后经常被清空。

官方开发指南不鼓励使用 android:configChanges="orientation"。 (来源

我对 Java 和Android 平台,我将不胜感激任何帮助。

My code has a massive memory leak on orientation change. The heap is full after rotating the phone once or twice. I tried to track down the leak for two days now without success.

The application uses SDK 15 (min 14).

MainActivity shows an ActionBar with list navigation (NAVIGATION_MODE_LIST). Navigating the spinner replaces the fragment shown.

The code works.

ListNavigationItem which contains the information needed to instantiate Fragments:

public class ListNavigationItem
{
    public final String fragmentClass;
    public final String title; // Used as fragment tag and as title

    public ListNavigationItem(String fragmentClass, String title)
    {
        this.fragmentClass = fragmentClass;
        this.title = title;
    }

    @Override
    public String toString()
    {
        return title;
    }
}

And finally my (simplified) MainActivity:

public class MainActivity extends Activity implements OnNavigationListener
{
    private MyApplication app;
    private ArrayAdapter<ListNavigationItem> adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        app = (MyApplication) getApplicationContext();

        ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
        actionBar.setDisplayShowTitleEnabled(false);
        actionBar.setDisplayShowHomeEnabled(false);

        adapter = new ArrayAdapter<ListNavigationItem>(this, android.R.layout.simple_spinner_dropdown_item);
        adapter.add(new ListNavigationItem(FirstFragment.class.getName(), "First"));
        adapter.add(new ListNavigationItem(SecondFragment.class.getName(), "Second"));

        actionBar.setListNavigationCallbacks(adapter, this);
    }

    public boolean onNavigationItemSelected(int itemPosition, long itemId)
    {
        ListNavigationItem item = adapter.getItem(itemPosition);
        if (item == null)
            return false;

        Fragment f = getFragmentManager().findFragmentByTag(item.title);

        if (f == null)
            f = Fragment.instantiate(this, item.fragmentClass);
        else if (f.isAdded())
            return true;

        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
        fragmentTransaction.replace(R.id.main_content, f, item.title);
        fragmentTransaction.commit();

        return true;
    }

    @Override
    protected void onDestroy()
    {
        adapter = null; // Do I need this?
        app = null;
        super.onDestroy();
    }
}

Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="at.company.app"
    android:versionCode="1"
    android:versionName="1.0">

    <uses-sdk android:minSdkVersion="14" />
    <uses-sdk android:targetSdkVersion="15" />

    <application
        android:name=".Application"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name">

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

A MAT heap analysis says that the class android.content.res.Resources occupies 57% of the available memory, and android.graphics.Bitmap occupies 12%. My fragments, however, contain just a simple TextView.

The MAT histogram shows that there are 4 Objects (Instances, I assume?) of MainActivity. Also, logcat shows that the heap is frequently being emptied after orientation change (GC_CONCURRENT).

The official Dev Guide discourages from using android:configChanges="orientation". (Source)

I am relatively new to Java and the Android platform, and I would appreciate any help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

为人所爱 2025-01-11 20:25:24

它将取决于您的清单配置,但要测试您可以完成 onConfigurationChanged.. 否则它只会创建一个新活动,而不会潜在地破坏旧活动..

It will depend on your manifest config but to test you could do a finish onConfigurationChanged.. otherwise it will just create a new activity without destroying the old one potentially..

快乐很简单 2025-01-11 20:25:24

我不知道为什么,如何,或者这是否是我的问题。但是当我删除片段事务的 setTransition 时,我似乎停止获取“增长堆”输出。试试那个?

I don't know why, how, or if this is my problem yet. But I seemed to stop getting the "grow heap" output when i removed the setTransition for the fragment transaction. Try that?

辞旧 2025-01-11 20:25:24

内存泄漏的一个常见原因是在上下文引用中引用了 Activity 而不是 Application。使用 this (例如 adapter = new ArrayAdapter(this )可能会导致内存泄漏,如本 问题,所以尝试将其替换为getApplicationContext() 来引用 Application 上下文。

A common cause for Memory leaks is referring to the Activity instead of the Application in context references. The use of this (for e.g. adapter = new ArrayAdapter<ListNavigationItem>(this ) may cause a memory leak as explained in this question, so try replacing it with getApplicationContext() to refer to the Application context instead.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文