Android Activity内存占用过高且无法释放,如何优化?
先理清楚你的问题场景:
我有一个Activity内存占用约70MB,调用finish()关闭该Activity后内存并未释放,再次启动时内存占用额外增加70MB。该Activity使用数据库,但我认为数据库并非问题所在,因为另一个使用相同数据库的Activity仅占用14MB内存。
你提供的问题类代码如下:
package ir.noideaw.aufbau; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.design.widget.AppBarLayout; import android.support.design.widget.CollapsingToolbarLayout; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.ListAdapter; import android.widget.ListView; import java.util.ArrayList; import java.util.List; import ir.noideaw.aufbau.Adapter.UnitAdapter; import ir.noideaw.aufbau.DatabaseHandler.DatabaseAccess; import ir.noideaw.aufbau.Model.Unit; public class ElementInfo extends AppCompatActivity { private ImageView elementPic; private CollapsingToolbarLayout ct; private AppBarLayout appBarLayout; private int currentApiVersion; private ArrayList<Unit> ovUnitsData; private List<String> properties; private ListView listView; // 新增:保存监听器成员变量 private AppBarLayout.OnOffsetChangedListener appBarOffsetListener; private View.OnSystemUiVisibilityChangeListener systemUiListener; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.element_details); initialize(); Bundle data = getIntent().getExtras(); final String sy = data.getString("elementSymbol"); DatabaseAccess database = DatabaseAccess.getInstance(this); database.open(); properties = database.setProperties(sy); database.close(); int atomicNumber = Integer.valueOf(database.getAtomicNumber()); int id = getResources().getIdentifier("element_" + atomicNumber, "drawable", getPackageName()); if (id != 0) { elementPic.setImageResource(id); } else { elementPic.setBackgroundColor(Color.parseColor(database.getColor())); } // 把监听器赋值给成员变量 appBarOffsetListener = new AppBarLayout.OnOffsetChangedListener() { boolean isShow = true; int scrollRange = -1; @Override public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { if (scrollRange == -1) { scrollRange = appBarLayout.getTotalScrollRange(); } if (scrollRange + verticalOffset == 0) { ct.setTitle(sy); isShow = true; } else if (isShow) { ct.setTitle(" ");//careful there should be a space between double quote otherwise it wont work isShow = false; } } }; appBarLayout.addOnOffsetChangedListener(appBarOffsetListener); setUnitsData(); UnitAdapter adapter = new UnitAdapter(this, ovUnitsData); listView.setAdapter(adapter); listView.setMinimumHeight(listView.getChildCount()*40); currentApiVersion = android.os.Build.VERSION.SDK_INT; final int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; if (currentApiVersion >= Build.VERSION_CODES.KITKAT) { getWindow().getDecorView().setSystemUiVisibility(flags); final View decorView = getWindow().getDecorView(); // 保存系统UI监听器到成员变量 systemUiListener = new View.OnSystemUiVisibilityChangeListener() { @Override public void onSystemUiVisibilityChange(int visibility) { if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { decorView.setSystemUiVisibility(flags); } } }; decorView.setOnSystemUiVisibilityChangeListener(systemUiListener); } setListViewHeightBasedOnChildren(listView); setListViewDevider(listView); listView.setDividerHeight(1); // 建议移除手动GC调用,交给系统自动处理 // System.gc(); } @Override public void onBackPressed() { finish(); } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (currentApiVersion >= Build.VERSION_CODES.KITKAT && hasFocus) { getWindow().getDecorView().setSystemUiVisibility( View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY); } } @Override protected void onDestroy() { super.onDestroy(); // 移除AppBarLayout的监听器,避免持有Activity引用 if (appBarOffsetListener != null) { appBarLayout.removeOnOffsetChangedListener(appBarOffsetListener); } // 移除系统UI监听器 if (systemUiListener != null && currentApiVersion >= Build.VERSION_CODES.KITKAT) { getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(null); } // 清空ListView的Adapter,切断引用 if (listView != null) { listView.setAdapter(null); } // 清空数据源,帮助GC回收 if (ovUnitsData != null) { ovUnitsData.clear(); ovUnitsData = null; } properties = null; // 同样不建议手动调用GC // Runtime.getRuntime().gc(); } private void initialize() { elementPic = findViewById(R.id.backdrop); ct = findViewById(R.id.collapsing_toolbar); appBarLayout = findViewById(R.id.appbar); listView = findViewById(R.id.ov_list_view); ovUnitsData = new ArrayList<>(); } public static void setListViewHeightBasedOnChildren(ListView listView) { ListAdapter listAdapter = listView.getAdapter(); if (listAdapter == null) { // pre-condition return; } int totalHeight = 0; for (int i = 0; i < listAdapter.getCount(); i++) { View listItem = listAdapter.getView(i, null, listView); listItem.measure(0, 0); totalHeight += listItem.getMeasuredHeight(); } ViewGroup.LayoutParams params = listView.getLayoutParams(); params.height = totalHeight + // 这里你的代码好像没写完,注意补全这部分逻辑,避免潜在问题 } }
接下来咱们分析几个核心的泄漏点和解决思路:
1. 未移除的监听器是最大嫌疑
你在onCreate里给AppBarLayout和DecorView都添加了匿名内部类监听器,这些匿名类会隐式持有Activity的强引用。如果不在Activity销毁时移除这些监听器,系统GC就无法回收这个Activity实例,导致内存泄漏。
解决方法就是把监听器保存为Activity的成员变量,然后在onDestroy方法中主动移除它们,就像上面修改后的代码那样。
2. 数据库单例的上下文问题
虽然你提到另一个用相同数据库的Activity没问题,但还是要检查DatabaseAccess.getInstance(this)这里的上下文:如果这个单例内部持有了传入的Activity上下文,而不是Application上下文,那么这个单例会一直持有Activity的引用,导致泄漏。
建议修改DatabaseAccess的getInstance方法,改用getApplicationContext():
DatabaseAccess database = DatabaseAccess.getInstance(getApplicationContext());
3. ListView和Adapter的引用问题
你的UnitAdapter传入了Activity的上下文,如果Adapter内部持有这个上下文的强引用,或者数据源ovUnitsData里的对象有其他引用,也可能导致泄漏。在onDestroy里清空Adapter和数据源,可以切断这些引用链。
4. 避免手动调用GC
你在onCreate和onDestroy里手动调用了System.gc()和Runtime.getRuntime().gc(),其实这没啥必要——GC的触发时机由系统控制,手动调用不仅不能立即回收内存,还可能影响应用性能,建议移除这些调用。
最后,你可以用Android Studio的Profiler工具来确认内存泄漏:打开Profiler的Memory面板,多次启动和关闭这个Activity,查看内存快照里是否有残留的ElementInfo实例,这样就能验证咱们的修复是否生效了。
内容的提问来源于stack exchange,提问作者Jack Johnson

