转载请注明原文地址:http://blog.csdn.net/mxm691292118/article/details/51020023
我把Android重难点和读书笔记都整理在github上:https://github.com/miomin/AndroidDifficulty
如果你觉得对你有帮助的话,希望可以star/follow一下哟,我会持续保持更新。
一、内存泄露
- 垃圾回收器无法回收原本应该被回收的对象,这个对象就引发了内存泄露。
- 内存泄露的危害:
- (1)过多的内存泄露最终会导致内存溢出(OOM)
- (2)内存泄露导致可用内存不足,会触发频繁GC,不管是Android2.2以前的单线程GC还是现在的CMS和G1,都有一部分的操作会导致用户线程停止(就是所谓的Stop the world),从而导致UI卡顿。
二、内存溢出(OOM)
android为每个进程设置Dalvik Heap Size阈值,这个阈值在不同的设备上会因为RAM大小不同而各有差异。如果APP想要分配的内存超过这个阈值,就会发生OOM。
ActivityManager.getMemoryClass()可以查询当前APP的Heap Size阈值,单位是MB。
在3.x以前,Bitmap分配在Native heap中,而在4.x之后,Bitmap分配在Dalvik或ART的Java heap中。
Android 2.x系统,当dalvik allocated + native allocated + 新分配的大小 >= dalvik heap 最大值时候就会发生OOM,也就是说在2.x系统中,考虑native heap对每个进程的内存限制。
- Android 4.x系统,废除了native的计数器,类似bitmap的分配改到dalvik的java heap中申请,只要allocated + 新分配的内存 >= dalvik heap 最大值的时候就会发生OOM(art运行环境的统计规则还是和dalvik保持一致),也就是说在4.x系统中,不考虑native heap对每个进程的内存限制,native heap只会收到本机总内存(包括RAM以及SWAP区或分页文件)的限制。
三、如何避免内存泄漏
参考在MDCC 2015中国移动开发者大会上胡凯前辈的讲述,整理总结。
1、使用轻量的数据结构
2、不要使用Enum
3、大胖子Bitmap的处理
- Bitmap压缩
- Lru机制处理Bitmap(下一节博客会详细讲解),也可以使用那些有名的图片缓存框架。
4、不要使用String进行字符串拼接
5、非静态内部类内存泄露
static class ImageDownloadTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private WeakReference<PhotoAdapter> photoAdapter;
public ImageDownloadTask(PhotoAdapter photoAdapter) {
this.photoAdapter = new WeakReference<PhotoAdapter>(photoAdapter);
}
@Override
protected Bitmap doInBackground(String... params) {
//在后台开始下载图片
url = params[0];
Bitmap bitmap = photoAdapter.get().loadBitmap(url);
if (bitmap != null) {
//把下载好的图片放入LruCache中
String key = MD5Tools.decodeString(url);
photoAdapter.get().put(key, bitmap);
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
//把下载好的图片显示出来
ImageView mImageView = (ImageView) photoAdapter.get().mGridView.get().findViewWithTag(MD5Tools.decodeString(url));
if (mImageView != null && bitmap != null) {
mImageView.setImageBitmap(bitmap);
photoAdapter.get().mDownloadTaskList.remove(this);//把下载好的任务移除
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
6、匿名内部类内存泄漏
private static class MyHandler extends Handler {
private final WeakReference<Context> context;
private MyHandler(Context context) {
this.context = new WeakReference<Context>(context);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
}
}
}
private final MyHandler mHandler = new MyHandler(this);
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// 发送一个10分钟后执行的一个消息
mHandler.postDelayed(sRunnable, 600000);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
private static class MyThread extends Thread {
@Override
public void run() {
while (true) {
// TODO 耗时任务
}
}
}
new MyThread().start();
7、Context持有导致内存泄漏
- Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。
- 解决:对于大部分非必须使用Activity Context的情况(创建Dialog的Context必须是Activity Context),应该使用Application Context。
8、记得注销监听器
- 注册监听器的时候会add Listener,不要忘记在不需要的时候remove掉Listener。
9、资源文件需要选择合适的文件夹进行存放
- hdpi/xhdpi/xxhdpi等等不同dpi的文件夹下的图片在不同的设备上会经过scale的处理。例如我们只在hdpi的目录下放置了一张100100的图片,那么根据换算关系,xxhdpi的手机去引用那张图片就会被拉伸到200200。需要注意到在这种情况下,内存占用是会显著提高的。对于不希望被拉伸的图片,需要放到assets或者nodpi的目录下。
10、谨慎使用static对象
11、谨慎使用单例中不合理的持有
- 单例中的对象生命周期与应用一致,注意不要在单例中进行不必要的外界引用持有。如果一定要引用外部变量,需要在外部变量生命周期结束的时候接触引用(赋为null)。
12、一定要记得关闭无用连接
- 在onDestory中关闭Cursor,I/O,数据库,网络的连接用完记得关闭。
注意:谨慎使用lager heap
- 不同的设备有不容的RAM,他们为应用程序设置了不同大小的Heap的阈值。虽然可以通过largeHeap=true的属性来为应用获得一个更大的heap空间,然后通过getLargeMemoryClass()来获取到这个更大的heap阈值。但是你要注意,largeHeap只是为了一些本来就需要大量内存的APP存在,比如图墙和图片编辑软件。所以,不要随意的使用large heap,否则会影响系统整体的用户体验,会使每次gc时间更长。
四、内存泄露检测
public class MyApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
public class MyApplication extends Application {
private static RefWatcher sRefWatcher;
@Override
public void onCreate() {
super.onCreate();
sRefWatcher = LeakCanary.install(this);
}
public static RefWatcher getRefWatcher() {
return sRefWatcher;
}
}
- 然后对某个可能发生泄露的占用大内存的对象进行监测:
MyApplication.getRefWatcher().watch(sLeaky);
- 对Fragment、BroadcastReceiver、Service进行监测:
public class MyFragment extends Fragment {
@Override
public void onDestroy() {
super.onDestroy();
MyApplication.getRefWatcher().watch(this);
}
}
参考文献
|