Android-内存泄漏问题多多,怎么优化?

  • 初始化

执行类构造器<clinit>方法的过程,变量的声明初始化就在这个阶段进行。

虚拟机类加载的时机?1)遇到new、getstatic、putstatic或者invokestatic 这四条字节码指令的时候,且该类没有进行初始化则进行该类的初始化;
2)使用反射机制的时候;
3)初始化类的父类;
4)初始化虚拟机要执行主类;
5)使用动态语言特性的时候;总之,当对一个类进行主动引用的时候就会进行初始化操作,而进行被动引用的时候便不会触发类初始化操作,比如通过子类引用父类静态字段时子类不会被初始化。

常见内存泄漏与优化
2.1 内存泄漏

当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被垃圾收集器回收,结果它们就一直存在于内存中(通常指Java堆内存),占用有效空间,永远无法被删除。随着内存不断泄漏,堆中的可用空间就不断变小,这意味着为了执行常用的程序,垃圾清理需要启动的次数越来越多,非常严重的话会直接造成应用程序报OOM异常。优化/避免内存泄漏原则:

  • 涉及到使用Context时,尽量使用Application的Context;
  • 对于非静态内部类、匿名内部类,需将其独立出来或者改为静态类;
  • 在静态内部类中持有外部类(非静态)的对象引用,使用弱引用来处理;
  • 不再使用的资源(对象),需显示释放资源(对象置为null),如果是集合需要清空;
  • 保持对对象生命周期的敏感,尤其注意单例、静态对象、全局性集合等的生命周期;
2.2 常见内存泄漏与优化

(1) 单例造成的内存泄漏

  • 案例

/** 工具类,单例模式

  • @Auther: Jiangdg
  • @Date: 2019/10/8 17:23
  • @Description:
    */
    public class CommonUtils {
    private static CommonUtils instance;
    private Context mCtx;

private CommonUtils(Context context){
this.mCtx = context;
}

public static CommonUtils getInstance(Context context) {
if(instance == null) {
instance = new CommonUtils(context);
}
return instance;
}
}

/**使用单例模式时造成内存泄漏
*

  • @Auther: Jiangdg
  • @Date: 2019/10/8 17:24
  • @Description:
    */
    public class SingleActivity extends AppCompatActivity {
    private CommonUtils mUtils;

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

mUtils = CommonUtils.getInstance(this);
}
}

  • 分析与优化

在上述示例中,当SingleActivity实例化Commontils对象完毕后,Commontils将持有SingleActivity对象的引用,而由于单例模式的静态特性,Commontils对象的生命周期将于应用进程的一致,这就会导致在应用未退出的情况下,如果SingleActivity对象已经不再需要了,而Commontils对象该持有该对象的引用就会使得GC无法对其进行正常回收,从而导致了内存泄漏。优化:对于需要传入Context参数的情况,尽量使用Application的Context,因为它会伴随着应用进程的存在而存在。

public class SingleActivity extends AppCompatActivity {
private CommonUtils mUtils;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 造成内存泄漏
//mUtils = CommonUtils.getInstance(this);

mUtils = CommonUtils.getInstance(this.getApplicationContext());
}
}

(2) Handler造成的内存泄漏

  • 案例

/** 使用Handler造成内存泄漏

  • @Auther: Jiangdg
  • @Date: 2019/10/8 17:55
  • @Description:
    */
    public class HandlerActivity extends AppCompatActivity {

// 匿名内部类
private Handler mUIHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};

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

new Thread(new Runnable() {
@Override
public void run() {
// 处理耗时任务
// …

mUIHandler.sendEmptyMessage(0x00);
}
});
}
}

  • 分析与优化

在Android应用启动时,应用程序的主线程会为其自动创建一个Looper对象和与之关联的MessageQueue,当主线程实例化一个Handler对象后,它就自动与主线程的MessageQueue关联起来,所有发送到MessageQueueMessage(消息)都会持有Handler的引用。由于主线程的Looper对象会随着应用进程一直存在的且Java类中的非静态内部类和匿名内部类默认持有外部类的引用,假如HandlerActivity提前出栈不使用了,但MessageQueue中仍然还有未处理的MessageLooper就会不断地从MessageQueue取出消息交给Handler来处理,就会导致Handler对象一直持有HandlerActivity对象的引用,从而出现HandlerActivity对象无法被GC正常回收,进而造成内存泄漏。优化:将Handler类独立出来,或者使用静态内部类,因为静态内部类不持有外部类的引用。

public class HandlerActivity extends AppCompatActivity {

// 匿名内部类默认持有HandlerActivity的引用
// 造成内存泄漏
// private Handler mUIHandler = new Handler() {
// @Override
// public void handleMessage(Message msg) {
// super.handleMessage(msg);
// }
// };

// 优化,使用静态内部类
// 假如要持有HandlerActivity,以便在UIHandler中访问其成员变量或成员方法
// 需要使用弱引用处理
private UIHandler mUIHandler;
static class UIHandler extends Handler {
private WeakReference mWfActivity;

public UIHandler(HandlerActivity activity) {
mWfActivity = new WeakReference<>(activity);
}

@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}

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

mUIHandler = new UIHandler(this);
}
}

Java中四种引用关系:

  • 强引用用来描述永远不会被垃圾收集器回收掉的对象,类似"Object obj = new Object"
  • 软引用用来描述一些还有用但并非必须的对象,由SoftReference类实现。被软引用关联着的对象会在系统将要发生OOM之前,垃圾收集器才会回收掉这些对象。
  • 弱引用用来描述非必须的对象,比软引用更弱一些,由WeakReference类实现。被弱引用的对象只能生产到下一次垃圾收集发生之前,无论当前内存是否足够。
  • 虚引用最弱的引种引用关系,由PhantomReference类实现。一个对象是否有虚引用的存在,完全不会对其生存时间产生影响,也无法通过虚引用来获取该对象实例。为一个对象设置虚引用关联的唯一目的是能在这个对象被垃圾收集器回收时收到一个系统通知

(3) 线程(非静态内部类或匿名内部类)造成的内存泄漏

  • 案例

/** 使用线程造成的内存泄漏

  • @Auther: Jiangdg
  • @Date: 2019/10/9 10:04
  • @Description:
    */
    public class ThreadActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 开启一个子线程
new Thread(new MyRunnable()).start();
// 开启一个异步任务
new MyAsyncTask(this).execute();
}

class MyRunnable implements Runnable {

@Override
public void run() {

}
}

class MyAsyncTask extends AsyncTask {
private Context mCtx;

public MyAsyncTask(Context context) {
this.mCtx = context;
}

@Override
protected Object doInBackground(Object[] objects) {
return null;
}
}
}

  • 分析与优化

在之前的分析中可知,Java类中的非静态内部类和匿名内部类默认持有外部类的引用。对于上述示例中的MyRunnableMyAsyncTask来说,它们是一个非静态内部类,将默认持有ThreadActivity对象的引用。假如子线程的任务在ThreadActivity销毁之前还未完成,就会导致ThreadActivity无法被GC正常回收,造成内存泄漏。优化:将MyRunnable和MyAsyncTask独立出来,或使用静态内部类,因为静态内部类不持有外部类的引用。

public class ThreadActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 开启一个子线程
new Thread(new MyRunnable()).start();
// 开启一个异步任务
// 优化:使用Application的Context
new MyAsyncTask(this.getApplicationContext()).execute();
}

// 优化:使用静态内部类
static class MyRunnable implements Runnable {

@Override
public void run() {

}
}

// 优化:使用静态内部类
// 如果需要传入Context,使用Application的Context
static class MyAsyncTask extends AsyncTask {
private Context mCtx;

public MyAsyncTask(Context context) {
this.mCtx = context;
}

@Override
protected Object doInBackground(Object[] objects) {
return null;
}
}
}

(4) 静态实例造成的内存泄漏

  • 案例

/**非静态内部类创建静态实例造成的内存泄漏

  • @Auther: Jiangdg
  • @Date: 2019/10/9 10:43
  • @Description:
    */
    public class StaticInstanceActivity extends AppCompatActivity {
    private static SomeResources mSomeResources;

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

if(mSomeResources == null) {
mSomeResources = new SomeResources(this);
}
}

class SomeResources {
private Context mCtx;

public SomeResources(Context context) {
this.mCtx = context;
}
}
}

  • 分析与优化

在上述案例中,演示了防止StaticInstanceActivity重建,比如横竖屏切换,导致反复创建SomeResources实例的问题,这里使用了static修饰关键字将SomeResources实例声明了静态实例,以确保该实例始终存在的是同一个,且它的生命周期与应用相同。然而,由于SomeResources是一个非静态内部类,其对象默认持有外部类StaticInstanceActivity的引用,就会导SomeResources的对象一直持有该引用,造成内存泄漏。优化:使用单例模式实现SomeResources,或者将其改成静态内部类。如果需要传入Context参数,必须使用Application的Context。

public class StaticInstanceActivity extends AppCompatActivity {
private static SomeResources mSomeResources;

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

if(mSomeResources == null) {
// 优化,使用Application的Context
mSomeResources = new SomeResources(this.getApplicationContext());
}
}

// 优化:使用静态内部类
static class SomeResources {
private Context mCtx;

public SomeResources(Context context) {
this.mCtx = context;
}
}
}

(5) 资源未关闭或监听器未移除(注销)引起的内存泄露情况在开发中,如果使用了BraodcastReceiverContentObserverFileCursorStreamBitmap自定义属性attributeattr、传感器等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。比如:

// 使用传感器等资源,需要注销
SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
sensorManager.unregisterListener(listener);
// 使用BraodcastReceiver,需要注销
Myreceiver recevier = new Myreceiver();
intentFilter = new IntentFilter();
intentFilter.addAction(“android.net.conn.CONNECTIVITY_CHANGE”);
registerReceiver(recevier,intentFilter);
unRegisterReceiver(recevier);
// 自定义属性,需要recycle
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AttrDeclareView);
int color = a.getColor(R.styleable.AttrDeclareView_background_color, 0);
a.recycle();

除了上述常见的5种内存泄漏外,还有包括无限循环动画使用ListView使用集合容器以及使用WebView也会造成内存泄漏,其中,无限循环动画造成泄漏的原因是没有再Activity的onDestory中停止动画;使用ListView造成泄漏的原因是构造Adapter时没有使用缓存的convertView;使用集合容器造成泄漏的原因是在不使用相关对象时,没有清理掉集合中存储的对象引用。在优化时,在退出程序之前将集合中的元素(引用)全部清理掉,再置为null;使用WebView造成泄漏的原因是在不使用WebView时没有调用其destory方法来销毁它,导致其长期占用内存且不能被回收。在优化时,可以为WebView开启另外一个进程,通过AIDL与主线程进行通信,便于WebVIew所在的进程可以根据业务需要选择合适的时机进行销毁。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

小结

有了这么多优秀的开发工具,可以做出更高质量的Android应用。

当然了,“打铁还需自身硬”,想要写出优秀的代码,最重要的一点还是自身的技术水平,不然用再好的工具也不能发挥出它的全部实力。

在这里我也分享一份大佬自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点。

总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

打铁还需自身硬*”,想要写出优秀的代码,最重要的一点还是自身的技术水平,不然用再好的工具也不能发挥出它的全部实力。

在这里我也分享一份大佬自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点。

总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 17
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值