Android内存优化之避免内存泄漏
前言
内存泄漏向来都是内存优化的重点,它如同幽灵一般存于我们的应用当中,有时它不会现身,但一旦现身就会让你头疼不已。因此,如何避免、发现和解决内存泄漏就变得尤为重要。这一篇我们先来学习如何避免内存泄漏。
1.什么是内存泄漏
我们知道,每个应用程序都需要内存来完成工作,为了确保Android系统的每个应用都有足够的内存,Android系统需要有效地管理内存分配。当内存不足时,Android运行时就会触发GC,GC采用的垃圾标记算法为根搜索算法,
讲到根搜索算法,如下图所示:
从上图看以看出,Obj4是可达的对象,表示它正被引用,因此不会标记为可回收的对象。Obj5、Obj6和Obj7都是不可达的对象,其中Obj5和Obj6虽然互相引用,但是因为他们到GC Roots是不可达的所以它们仍旧会标记为可回收的对象。
内存泄漏就是指没有用的对象到GC Roots是可达的(对象被引用),导致GC无法回收该对象。此时,如果Obj4是一个没有用的对象,但它仍与GC Roots是可达的,那么Obj4就会内存泄漏。
内存泄漏产生的原因,主要分为三大类:
1.由开发人员自己编码造成的泄漏。
2.第三方框架造成的泄漏。
3.由Android 系统或者第三方ROM造成的泄漏。
其中第二种和第三种有时是不可控的,但是*种是可控的,既然是可控的,我们就要尽量在编码时避免造成内存泄漏,下面就来列举出常见的内存泄漏的场景。
2.内存泄漏的场景
2.1 非静态内部类的静态实例
非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接的长期维持着外部类的引用,阻止被系统回收。
public class SecondActivity extends AppCompatActivity { private static Object inner; private Button button; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.bt_next); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { createInnerClass(); finish(); } }); } void createInnerClass() { class InnerClass { } inner = new InnerClass();//1 } } |
当点击Button时,会在注释1处创建了非静态内部类InnerClass的静态实例inner,该实例的生命周期会和应用程序一样长,并且会一直持有SecondActivity 的引用,导致SecondActivity无法被回收。
2.2 匿名内部类的静态实例
和前面的非静态内部类一样,匿名内部类也会持有外部类实例的引用。
public class AsyncTaskActivity extends AppCompatActivity { private Button button; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task); button = (Button) findViewById(R.id.bt_next); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { startAsyncTask(); finish(); } }); } void startAsyncTask() { new AsyncTask<Void, Void, Void>() {//1 protected Void doInBackground(Void... params) { while (true) ; } }.execute(); } } |
在注释1处实例化了一个AsyncTask,当AsyncTask的异步任务在后台执行耗时任务期间,AsyncTaskActivity 被销毁了,被AsyncTask持有的AsyncTaskActivity实例不会被垃圾收集器回收,直到异步任务结束。
解决办法就是自定义一个静态的AsyncTask,如下所示。
public class AsyncTaskActivity extends AppCompatActivity { private Button button; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_async_task); button = (Button) findViewById(R.id.bt_next); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { startAsyncTask(); finish(); } }); } void startAsyncTask() { new MyAsyncTask().execute(); } private static class MyAsyncTask extends AsyncTask<Void, Void, Void> { protected Void doInBackground(Void... params) { while (true) ; } } } |
与AsyncTask类似的还有TimerTask,这里就不再举例。
2.3 Handler内存泄漏
Handler的Message被存储在MessageQueue中,有些Message并不能马上被处理,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler 是非静态的,则Handler也会导致引用它的Activity或者Service不能被回收。
public class HandlerActivity extends AppCompatActivity { private Button button; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); button = (Button) findViewById(R.id.bt_next); final Handler mHandler = new Handler() { public void handleMessage(Message msg) { super.handleMessage(msg); } }; button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { mHandler.sendMessageDelayed(Message.obtain(), 60000); finish(); } }); } } |
Handler 是非静态的匿名内部类的实例,它会隐性引用外部类HandlerActivity 。上面的例子就是当我们点击Button时,HandlerActivity 会finish,但是Handler中的消息还没有被处理,因此HandlerActivity 无法被回收。
解决方法就是要使用一个静态的Handler内部类,Handler持有的对象要使用弱引用,并且在Activity的Destroy方法中移除MessageQueue中的消息,如下所示。
public class HandlerActivity extends AppCompatActivity { private Button button; private MyHandler myHandler = new MyHandler(this); protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler); button = (Button) findViewById(R.id.bt_next); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { myHandler.sendMessageDelayed(Message.obtain(), 60000); finish(); } }); } public void show() { } private static class MyHandler extends Handler { private final WeakReference<HandlerActivity> mActivity; public MyHandler(HandlerActivity activity) { mActivity = new WeakReference<HandlerActivity2>(activity); } public void handleMessage(Message msg) { if (mActivity != null && mActivity.get() == null) { mActivity.get().show(); } } } public void onDestroy() { if (myHandler != null) { myHandler.removeCallbacksAndMessages(null); } super.onDestroy(); } } |
MyHandler是一个静态的内部类,它持有的 HandlerActivity对象使用了弱引用,并且在onDestroy方法中将Callbacks和Messages全部清除掉。
如果觉得麻烦,也可以使用避免内存泄漏的Handler开源库WeakHandler。
2.4 未正确使用Context
对于不是必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们可以考虑使用Application Context来代替Activity的Context,这样可以避免Activity泄露,比如如下的单例模式:
public class AppSettings { private Context mAppContext; private static AppSettings mAppSettings = new AppSettings(); public static AppSettings getInstance() { return mAppSettings; } public final void setup(Context context) { mAppContext = context; } } |
mAppSettings作为静态对象,其生命周期会长于Activity。当进行屏幕旋转时,默认情况下,系统会销毁当前Activity,因为当前Activity调用了setup方法,并传入了Activity Context,使得Activity被一个单例持有,导致垃圾收集器无法回收,进而产生了内存泄露。
解决方法就是使用Application的Context:
public final void setup(Context context) { mAppContext = context.getApplicationContext(); } |
2.5 静态View
使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收,解决的办法就是在onDestory方法中将静态View置为null。
public class SecondActivity extends AppCompatActivity { private static Button button; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); button = (Button) findViewById(R.id.bt_next); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { finish(); } }); } } |
2.6 WebView
不同的Android版本的WebView会有差异,加上不同厂商的定制ROM的WebView的差异,这就导致WebView存在着很大的兼容性问题。WebView都会存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主进程进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。
2.7 资源对象未关闭
资源对象比如Cursor、File等,往往都用了缓冲,不使用的时候应该关闭它们。把他们的引用置为null,而不关闭它们,往往会造成内存泄漏。因此,在资源对象不使用时,一定要确保它已经关闭,通常在finally语句中关闭,防止出现异常时,资源未被释放的问题。
2.8 集合中对象没清理
通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就会更加严重。
2.9 Bitmap对象
临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。
避免静态变量持有比较大的bitmap对象或者其他大的数据对象,如果已经持有,要尽快置空该静态变量。
2.10 监听器未关闭
很多系统服务(比如TelephonyMannager、SensorManager)需要register和unregister监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的Listener,要记得在合适的时候及时remove这个Listener。