Android开发编码规范导致的内存泄露问题

本文介绍了在Android开发中如何避免常见的内存泄露问题,包括正确使用单例模式、避免使用非静态内部类导致的引用循环,以及如何正确管理和释放Handler资源。

在很久很久之前,看过一篇关于内存泄露的文章,里面列举了比较全的应该注意的问题,后来找不到原文地址,今天翻了微博,找到了该文章,为了方便日后自己查看,将注意的问题提取出来。在android开发中,我们的编码习惯可能会让我们编写出一些容易导致内存泄露的代码。所以我们应该要养成一个良好的编码习惯。

单例

平时,我们可能会这样写单例

public class Singleton{
    private static Singleton instance;
    private Context mContext;
    private Singleton(Context mContext){
        this.mContext = mContext;
    }
    public static Singleton getInstance(Context context){
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(context);
                }
            }
        }
        return instance;
    }
}

然后这样写会有隐患,原因是如果我们再Activity中或者其他地方使用Singleton.getInstance()时,我们会顺手写this或者mContext(这个变量也是指向this)作为参数传进去。我们的Singleton是单例,意味着被初始化后就应该一直存在内存中,以方便我们以后调用的时候不会再次创建Singleton对象,但是Singleton中的mContext变量一直都会持有Activity中的Context,导致Activity即使执行了onDestroy方法,也不能够将自己销毁,但是ApplicationContext就显然有所不同了,它一直伴随着我们的应用存在,所以就不用担心前面所说的导致Activity无法销毁的问题了。于是就诞生了正确的写法。

public class Singleton{
    private static Singleton instance;
    private Context mContext;
    private Singleton(Context mContext){
        this.mContext = mContext;
    }
    public static Singleton getInstance(Context context){
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(context.getApplicationContext());
                }
            }
        }
        return instance;
    }
}

匿名内部类

在Android开发中,很多地方会用到匿名内部类,比如事件的监听,Handler的消息处理等等,而一旦使用错误,也会导致内存泄露。

public class MainActivity extends Activity{   
    private Button btn;
    private Handler handler = new Handler(){
       @override
       public void handlerMessage(Message msg){   
       }
    };
    @override
    public void onCreate(Bundle bundle){
       super.onCreate(bundle);
       setContextView(R.layout.activity_main);
       btn=(Button)findViewById(R.id.btn);
       btn.setOnClickListner(View.OnClickListener(){
            @override
            public void onClick(View view){
                Message message = Message.obtain();
                handler.sendMessage(message);
            }
       });

    }
}

当我们执行了MainActivity的finish方法,被延迟的消息会在被处理之前存在于主线程消息队列中,而这个消息中又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的MainActivity的引用,所以这导致了MainActivity无法回收,进而导致MainActivity持有的很多资源都无法回收,所以产生了内存泄露。然而一个静态的匿名内部类实例是不会持有外部类的引用的。所以正确的写法应该是这样的。

public class MainActivity extends Activity{
    private Button btn;
    private static class MyHandler extends Handler {
        private final WeakReference<MainActivity> mActivity;
        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference<MainActivity>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = mActivity.get();
            if (activity != null) {
                 // ...
            }
        }
    }
    private final MyHandler handler = new MyHandler(this);
    @override
    public void onCreate(Bundle bundle){
       super.onCreate(bundle);
       setContextView(R.layout.activity_foo_layout);
       btn=(Button)findViewById(R.id.btn);
       btn.setOnClickListner(View.OnClickListener(){
            @override
            public void onClick(View view){
                Message message = Message.obtain();
                handler.sendMessage(message);
            }
       });
    }
}

很多时候我们甚至会使用handler发送一个匿名Runnable对象,同样的也会导致内存泄露的问题。因此Runnable对象应该也要使用静态的匿名内部类。

private static class MyRunnable implements Runnable{
    private WeakReference<TextView> textViewWeakReference;
    public MyRunnable(TextView textView){
        textViewWeakReference = new WeakReference<TextView>(textView);
    }
    @override
    public void run(){
         final TextView textView = textViewWeakReference.get();
         if(textView != null){
              textView.setText("OK");
         }
    };
}

使用的时候这样用就可以了

handler.postDelayed(new MyRunnable(textView),1000 * 60 * 10);

Handler

在使用了Handler之后,记得在onDestroy里面调用

handler.removeCallbacksAndMessages(object token);

移除相关消息。

我们可以使用

handler.removeCallbacksAndMessages(null);

当参数为null时,可以清除掉所有跟次handler相关的Runnable和Message,我们在onDestroy中调用次方法也就不会发生内存泄漏了。

对以上几点做一个总结
- 不要让生命周期长于Activity的对象持有到Activity的引用
- 尽量使用Application的Context而不是Activity的Context
- 尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用。如果使用静态内部类,将外部实例引用作为弱引用持有。
- 垃圾回收不能解决内存泄露,了解Android中垃圾回收机制

Context

注意Context与ApplicationContext的区别
- View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。
- Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。
- ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。
- Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

这里写图片描述
大家注意看到有一些NO上添加了一些数字,其实这些从能力上来说是YES,但是为什么说是NO呢?
- 数字1:启动Activity在这些类中是可以的,但是需要创建一个新的task。一般情况不推荐。
- 数字2:在这些类中去layout inflate是合法的,但是会使用系统默认的主题样式,如果你自定义了某些样式可能不会被使用。
- 数字3:在receiver为null时允许,在4.2或以上的版本中,用于获取黏性广播的当前值。(可以无视)
注:ContentProvider、BroadcastReceiver之所以在上述表格中,是因为在其内部方法中都有一个context用于使用。

好了,这里我们看下表格,重点看Activity和Application,可以看到,和UI相关的方法基本都不建议或者不可使用Application,并且,前三个操作基本不可能在Application中出现。实际上,只要把握住一点,凡是跟UI相关的,都应该使用Activity做为Context来处理;其他的一些操作,Service,Activity,Application等实例都可以,当然了,注意Context引用的持有,防止内存泄漏。
关于Context更详细的内容见Android Context 上下文 你必须知道的一切

在现代电机控制领域中,FOC(Field Oriented Control,矢量控制)技术的应用日益广泛,其主要目的是为了提高电机控制的性能和效率。FOC通过将电机定子电流分解为与转子磁场同步旋转的坐标系中的两个正交分量来实现对电机转矩和磁通的独立控制,类似于直流电机的控制效果,从而实现精确的转矩控制和高速响应。本文件提到的手搓FOC驱动器涉及到了三个控制环路:位置环、速度环和电流环。在位置环中,控制算法只需要一个P(比例)参数来调整,因为位置控制相对来说较为简单,只需要通过比例控制来实现位置的准确跟随。在速度环的控制中,刚性等级的调节是关键,刚性等级高意味着系统对速度变化的反应更快,但同时也可能导致机械系统承受较大的冲击和震动。因此,适当调节速度环的刚性等级是实现电机平稳运行和快速响应的重要手段。电流环是电机控制中最为复杂的一个环节,因为它涉及到电机的电流动态控制。本文件中提到了电流环PI参数基于带宽调节。PI(比例-积分)控制器的参数设置对于电流环的性能至关重要。带宽的调节通常与系统的动态响应能力和稳定性有关,带宽越大,系统的响应速度越快,但稳定性可能下降;反之,带宽越小,系统越稳定,但响应速度会变慢。SVPWM(Space Vector Pulse Width Modulation,空间矢量脉宽调制)是另一种先进的调制技术,用于在电机驱动器中生成高效的开关波形。本文件提到的SVPWM采用基于零序注入的SPWM(正弦脉宽调制)控制,这种方法可以在保持载波频率不变的同时,调整输出波形的电压和频率,以满足电机的运行需求。零点电角度识别技术则是在电机运行过程中实时确定转子的准确位置,这对于实现精确的矢量控制至关重要。手搓FOC驱动器的设计需要综合考虑位置、速度和电流三个环路的控制要求,并合理配置相应的PI参数,采用高效的SVPWM控制策略和精确的电角度识别技术
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值