Android 浅谈性能优化、内存泄漏处理

作者: KT_11 | 来源:发表于2018-11-02 13:58 被阅读5次

    1 布局优化

    • 尽量减少布局文件的层级
    • 简单布局优先考虑用 Framelayout 和 LinearLayout ,简单高效、渲染时间少
      多个嵌套的布局则优先考虑用 Relativelayout
    • 使用 include、merge 标签引入布局 , 或者用Viewstub延时加载
      可以通过手机开发者模式的调试GPU过度绘制来检测布局是否需要优化

    2 绘制优化

    • 绘制优化是指在自定义View的onDraw()方法里避免执行大量的操作
    • 不要在onDraw()方法中创建对象
      因为onDraw()频繁被调用,这样就会产生很多临时对象,会导致耗内存、系统频繁GC、降低运行效率.
    • 不要在onDraw()方法中执行耗时操作
      不停的执行onDraw(),就会有很多耗时任务轮训,造成View绘制不流畅.

    3 线程优化

    • 线程的创建和销毁都比较耗性能,所以线程的优化是采用线程池

    线程池优点:
    1.避免了线程创建销毁带来的消耗
    2.能够有效控制线程池的最大并发数,避免了大量的线程因互相抢占资源从而导致的阻塞现象.

    4 内存泄漏优化

    • 当一个对象已经不需要再使用了,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。
    • 不停的内存泄漏,就会导致内存溢出
      所以内存泄漏优化分为2个方面来解决:
      A 代码中注意
      B 上线前通过LeakCanary、Android Profiler、MAT等工具来检测

    具体分析:

    4-1 单例和静态变量造成的内存泄漏

    • 单例模式在Android开发中会经常用到,但是如果使用不当就会导致内存泄露。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄露。
    public class AppSettings {
    
        private static AppSettings sInstance;
        private Context mContext;
    
        private AppSettings(Context context) {
            this.mContext = context;
        }
    
        public static AppSettings getInstance(Context context) {
            if (sInstance == null) {
                sInstance = new AppSettings(context);
            }
            return sInstance;
        }
    }
    
    • 像上面代码中这样的单例,如果我们在调用getInstance(Context context)方法的时候传入的context参数是Activity、Service等上下文,就会导致内存泄露。
    • 以Activity为例,当我们启动一个Activity,并调用getInstance(Context context)方法去获取AppSettings的单例,传入Activity.this作为context,这样AppSettings类的单例sInstance就持有了Activity的引用,当我们退出Activity时,该Activity就没有用了,但是因为sIntance作为静态单例(在应用程序的整个生命周期中存在)会继续持有这个Activity的引用,导致这个Activity对象无法被回收释放,这就造成了内存泄露。
    • 解决:使用全局的上下文Application Context就是应用程序的上下文,和单例的生命周期一样长,这样就避免了内存泄漏。
      单例模式对应应用程序的生命周期,所以我们在构造单例的时候尽量避免使用Activity的上下文,而是使用Application的上下文
    private AppSettings(Context context) {
        this.mContext = context.getApplicationContext();
    }
    

    静态变量导致内存泄露

    • 静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后,它所持有的引用只有等到进程结束才会释放。
      比如下面这样的情况,在Activity中为了避免重复的创建info,将sInfo作为静态变量:
    public class MainActivity extends AppCompatActivity {
     
        private static Info sInfo;
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            if (sInfo != null) {
                sInfo = new Info(this);
            }
        }
    }
     
    class Info {
        public Info(Activity activity) {
        }
    }
    
    • Info作为Activity的静态成员,并且持有Activity的引用,但是sInfo作为静态变量,生命周期肯定比Activity长。所以当Activity退出后,sInfo仍然引用了Activity,Activity不能被回收,这就导致了内存泄露。
    • 在Android开发中,静态持有很多时候都有可能因为其使用的生命周期不一致而导致内存泄露,所以我们在新建静态持有的变量的时候需要多考虑一下各个成员之间的引用关系,并且尽量少地使用静态持有的变量,以避免发生内存泄露。当然,我们也可以在适当的时候讲静态量重置为null,使其不再持有引用,这样也可以避免内存泄露。(比如Activity的onDestroy方法执行的时候设为null)

    4-2 Handler容易造成内存泄漏

    • 当使用内部类或匿名内部类的方式创建Handler时,Handler对象会隐式地持有一个外部类对象的引用(这里的外部类是Activity)
    • 一般在一个耗时任务中会开启一个子线程,如网络请求或文件读写操作,我们会使用到Handler对象。但是,如果在任务未执行完时,Activity被关闭了,Activity已不再使用,此时由GC来回收掉Activity对象。
    • 由于子线程未执行完毕,子线程持有Handler的引用,而Handler又持有Activity的引用,这样直接导致Activity对象无法被GC回收,即出现内存泄漏。
    public class TestActivity extends AppCompatActivity {
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                //...
            }
        };
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            loadData();
        }
        private void loadData(){
            //...request
            Message message = Message.obtain();
            mHandler.sendMessage(message);
        }
    }
    

    (当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。)

    解决方法一:

    • 先将Handler声明为静态内部类
      因为静态内部类不会持有外部类的引用,则不会导致外部类实例出现内存泄露。
    • 然后在Handler中添加对外部Activity的弱引用
      由于Handler被声明为静态内部类,不再持有外部类对象的引用,导致无法在handleMessage()中操作Activity中的对象,所以需要在Handler中增加一个对Activity的弱引用
    • java对于 强引用 的对象,就绝不收回,对于 软引用 的对象,是能不收回就不收回,这里的能不收回就是指内存足够的情况,对于 弱引用 的对象,是发现就收回,但是一般情况下不会发现
    public class TestActivity  extends AppCompatActivity {
        private MyHandler mHandler = new MyHandler(this);
        private TextView mTextView ;
        private static class MyHandler extends Handler {
            private WeakReference<Context> reference;
            public MyHandler(Context context) {
                reference = new WeakReference<>(context);
            }
            @Override
            public void handleMessage(Message msg) {
                TestActivity  activity = (TestActivity ) reference.get();
                if(activity != null){
                    activity.mTextView.setText("");
                }
            }
        }
    

    解决方法二:

    • 我们还可以在Activity的Destroy时或者Stop时应该移除消息队列中的消息
      在原因中我们说到,正是因为被延时处理的 message 持有 Handler 的引用,Handler 持有对 Activity 的引用,形成了message – handler – activity 这样一条引用链,导致 Activity 的泄露。因此我们可以尝试在当前界面结束时将消息队列中未被处理的消息清除,从源头上解除了这条引用链,从而使 Activity 能被及时的回收。
        @Override
        protected void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacksAndMessages(null);
        }
    

    4-3 资源未释放

    • 解决:对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。

    4-4 无限轮训动画造成泄漏

    • 解决:销毁时,取消动画
    animator = ObjectAnimator.ofFloat(btn_home, "ratation", 0, 360).setDuration(2000);
    animator.setRepeatCount(ValueAnimator.INFINITE);
    animator.start();    
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        animator.cancel();
    }
    

    5 其他资源优化 (可结合自身经验自由发挥,无固定答案)

    • 避免过度的创建对象
    • 不要过度使用枚举,枚举占用的内存空间要比整型大
    • 常量请使用static final来修饰
    • 使用一些Android特有的数据结构,比如SparseArray和Pair等
    • 适当采用软引用和弱引用
    • 采用内存缓存和磁盘缓存
    • 尽量采用静态内部类,这样可以避免潜在的由于内部类而导致的内存泄漏。

    参考:

    https://blog.csdn.net/qq_38859786/article/details/80290151
    https://www.jianshu.com/p/ab4a7e353076
    https://www.jianshu.com/p/4d6c38e1f5b8
    https://www.jianshu.com/p/f2f186e3dd3e

    相关文章

      网友评论

        本文标题:Android 浅谈性能优化、内存泄漏处理

        本文链接:https://www.haomeiwen.com/subject/ubzfxqtx.html