使用 ActionBar 和 Spinner 导航的 Android 内存泄漏
我的代码在方向改变时存在大量内存泄漏。旋转手机一两次后堆就满了。我花了两天时间试图追查泄漏情况,但没有成功。
该应用程序使用 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
它将取决于您的清单配置,但要测试您可以完成 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..
我不知道为什么,如何,或者这是否是我的问题。但是当我删除片段事务的 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?
内存泄漏的一个常见原因是在上下文引用中引用了
Activity
而不是Application
。使用this
(例如adapter = new ArrayAdapter(this
)可能会导致内存泄漏,如本 问题,所以尝试将其替换为getApplicationContext()
来引用Application
上下文。A common cause for Memory leaks is referring to the
Activity
instead of theApplication
in context references. The use ofthis
(for e.g.adapter = new ArrayAdapter<ListNavigationItem>(this
) may cause a memory leak as explained in this question, so try replacing it withgetApplicationContext()
to refer to theApplication
context instead.