Android性能优化小结

作者: 大虾啊啊啊 | 来源:发表于2019-04-05 04:16 被阅读2次

    一、什么是性能?

    image.png

    以上图片形象的描述了什么是性能。如果我们开发出来的APP没有达到以上的快、稳、省、小。那说明我们的应用是有待优化的,就是我们说的性能优化。

    二、性能优化

    1、布局优化

    (1)避免不必要的多层级嵌套
    屏幕某个像素在同一帧的时间内被绘制了多次,会浪费CPU资源。
    (2)如果父控件已经有颜色,也是子控件需要的颜色,就没必要再子控件设置颜色。
    (3)能用LinearLayout和FrameLayout,尽量不用RelativeLayout。因为RelativeLayout相对复杂,绘制更耗时
    (4)使用ViewStub,按需加载,需要的时候才绘制。
    (5)复杂的界面可以用ConstraintLayout,可以有效减少层级(本人不怎么使用,使用比较麻烦)

    2、绘制的优化

    (1)不要在onDraw方法中做耗时操作,会导致View绘制不流畅
    (2)不要在onDraw方法中创建新的局部对象,因为onDraw方法会频繁的调用,这样就会创建很多局部对象,不仅仅暂用内存,也会导致系统频繁的GC操作。大大的降低了程序的运行效率。

    3、内存的优化

    3.1、内存泄露的危害

    我们知道JAVA中特有的GC让JAVA程序员更加的轻松,也就是JAVA虚拟机自动识别那些没用的对象,然后进行回收,释放内存。但是往往有的时候,有的对象逻辑上已经没用了,但是还保持着引用,想当于赖在内存中不走,空耗内存。这样就导致了内存的泄露。因为有了内存的泄露,那么内存被占用就越来越多,这样就更加容易触发GC,而每一次GC操作,所有的线程都是停止的状态,如果过多频繁的GC,那么线程停止和恢复就越频繁,也就会容易造成卡顿。
    简单的来说
    1、内存泄露会导致内存被空耗。
    2、内存泄露过多会造成频繁的GC,造成卡顿。

    3.2、造成内存泄露的情况

    上面我们说了内存泄露的危害,下面我们来了解下内存泄露的主要常见情况。

    1、集合类泄露
    static List<Object> mList = new ArrayList<>();
       for (int i = 0; i < 100; i++) {
           Object obj = new Object();
          mList.add(obj);
        }
    

    以上的静态集合变量持有了众多个对象,当我们不用该集合又不做处理的时候,就会造成内存泄露。处理也比较简单。先把集合清理掉,然后把它的引用也释放。

     mList.clear();
     mList = null;
    
    2、单例、静态变量造成的泄露

    单例模式具有静态性,它的生命周期等于应用程序的生命周期。往往很容易造成内存泄露。我们看下面的一个例子。

    public class SingleInstance {
    
        private static SingleInstance mInstance;
        private Context mContext;
    
        private SingleInstance(Context context){
            this.mContext = context;
        }
    
        public static SingleInstance newInstance(Context context){
            if(mInstance == null){
                mInstance = new SingleInstance(context);
            }
            return sInstance;
        }
    }
    

    我们看到构造方法SingleInstance需要一个Context对象,如果我们把Activity的Context传给它,当我们的Activity需要被销毁的时候,而静态变量SingleInstance mInstance还持有Activity的引用,会导致GC无法回收。所以就会出现了内存泄露,也就是生命周期长的持有生命周期短的引用,导致生命周期短的无法被回收。我们来看下下具体的解决办法。

    public class SingleInstance {
    
        private static SingleInstance mInstance;
        private Context mContext;
    
        private SingleInstance(Context context){
            this.mContext = context.getApplicationContext();
        }
    
        public static SingleInstance newInstance(Context context){
            if(mInstance == null){
                mInstance = new SingleInstance(context);
            }
            return sInstance;
        }
    }
    
    
    3、匿名内部类和非静态内部类

    首先我们说非静态内部类导致的内存泄露。
    我们首先要知道,非静态的内部类会持有外部类的引用,而静态的内部类不会持有外部类的引用。下面我们先来个例子:

    public class TestActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test);
            new MyAscnyTask().execute();
        }
    
        class MyAscnyTask extends AsyncTask<Void, Integer, String>{
            @Override
            protected String doInBackground(Void... params) {
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "";
            }
        }
    }
    
    

    在TestActivity 中有一个 非静态内部类MyAscnyTask ,如果MyAscnyTask 中的doInBackground操作耗时较长,而TestActivity 早就被关闭了,而MyAscnyTask 还持有TestActivity 的引用,就回导致TestActivity 无法被GC。解决办法,我们只需要把非静态内部类改成静态的内部类即可:

    public class TestActivity extends Activity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test);
            new MyAscnyTask().execute();
        }
    //改了这里 注意一下 static
       static  class MyAscnyTask extends AsyncTask<Void, Integer, String>{
            @Override
            protected String doInBackground(Void... params) {
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "";
            }
        }
    }
    
    

    说完非静态内部类,我们来说匿名内部类,其实匿名内部类和非静态内部类的性质是一样的,都是因为持有
    外部类的引用,导致无法GC。下面我们来看个例子

    public class TestActivity extends Activity {
    private TextView mText;
        private Handler mHandler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
    //do something
    mText.setText(" do someThing");
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test);
    mText = findVIewById(R.id.mText);
            //  匿名线程持有 Activity 的引用,进行耗时操作
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
        
            mHandler. sendEmptyMessageDelayed(0, 100000);
        }
    
    

    你们线程持有了外部类TestActivity 的引用,当TestActivity 销毁的时候,由于匿名线程还持有TestActivity的引用,导致无法GC。以上我们第一反应我们就会想到,我们只需把这个匿名内部类改成静态的内部类,这样就不会持有外部类的引用了,这样问题不就是解决了吗?对于以上,我们只需要把Thread和Handler 用一个静态的内部类实现,这就不会持有TestActivity 的引用了。
    是的,这样做只是解决问题第一步,我们观察

    mText.setText(" do someThing");
    

    我们把Handler改成了静态的,但是我们在handleMessage方法中做UI操作,mText.setText(" do someThing");而mText肯定是持有TestActivity 的引用,由于Handler是静态的,也就说生命周期大于TestActivity,这一来又会导致生命周期长的持有生命周期短的引用,无法GC。怎么解决呢?除了我们要把匿名内部类改成静态的内部类之外,我们还需要通过弱引用的方式去解决。我们先来了解一下JAVA中的几个引用。
    (1)强引用
    我们平时不做特殊处理的情况下,一般都是属于强引用。当无法GC的时候,就算内存溢出OOM了也不会回收。
    (2)软引用
    当内存空间足够的情况下不会回收,只有内存空间不足的时候才会强制回收
    (3)弱引用
    GC的时候,不管内存够不够都要回收他。由于GC是一个优先级很低的线程,不会频繁的操作,所以我们使用的时候,不用担心对象被盲目的GC掉。
    (4)虚引用
    没用过
    所以针对以上的问题,我们可以使用弱引用,要就是说,当TestActivity被关闭的时候,JVM会进行GC回收。这个时候即使其他的类还持有TestActivity的引用,由于我们使用的是弱引用,所以会进行强制的回收。具体解决办法

    public class TestActivity extends Activity {
        private TextView mText;
        private MyHandler myHandler = new MyHandler(TestActivity.this);
        private MyThread myThread = new MyThread();
    
        private static class MyHandler extends Handler {
    
            WeakReference<TestActivity> weakReference;
    
            MyHandler(TestActivity testActivity) {
                this.weakReference = new WeakReference<TestActivity>(testActivity);
    
            }
    
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                weakReference.get().mText.setText("do someThing");
    
            }
        }
    
        private static class MyThread extends Thread {
    
            @Override
            public void run() {
                super.run();
    
                try {
                    sleep(100000);
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test);
            mText = findViewById(R.id.mText);
            myHandler.sendEmptyMessageDelayed(0, 100000);
            myThread.start();
        }
    //最后清空这些回调 
        @Override
        protected void onDestroy() {
            super.onDestroy();
            myHandler.removeCallbacksAndMessages(null);
        }
    
    4、资源未关闭造成内存泄露

    网络、文件等流忘记关闭
    手动注册广播,忘记取消注册
    Service执行完忘记stopSelf
    EventBus 等观察者模式框架忘记手动解除注册
    平时注意代码规范。
    工具:leakcanary可以检测内存泄露

    4、启动速度的优化

    在介绍APP启动速度的优化的时候我们先来了解APP启动的三种方式:
    冷启动、热启动、温启动。

    冷启动

    冷启动指的是APP应用从头开始启动,通常是APP的第一次启动。启动的时候系统有三个任务:
    (1)加载并启动应用程序
    (2)启动后立即显示窗口(也就是我们平常看到的空白窗口)
    (3)创建应用进程
    只有创建应用进程完之后,才会创建我们的应用程序对象,主要由以下步骤:
    (1)启动主线程
    (2)创建主Activity
    (3)加载布局
    (4)屏幕布局(在屏幕中布置布局)
    (5)初始化绘制
    当绘制完成之后,系统才会交换当前显示的窗口,将其替换为主活动,此时用户可以使用该应用程序,也就是我们的系统界面可以看到了。在绘制完成之前,一直都是属于空白页。

    Application的创建

    我们知道Application初始化完之后,才会初始化我们的主Activity,然后加载布局,绘制操作,最后将窗口交换为当前Activity的界面。在显示主Activity界面之前窗口都是属于空白页。

    热启动

    热启动比冷启动开销较小,在热启动中,当我们需要启动的Activity已经在内存中,则不会创建新的Activity,而是直接把Activity带到前台,避免了创建Activity加载布局,绘制等操作。

    温启动

    没啥要讲的

    以上小结:冷启动和热启动

    当应用程序的Activity退出的时候,进程没有被杀死,重新启动Acivity的时候,会在原来的进程中创建Activity
    ,或者如果内存中已有需要启动的Activity将把该Activity移到前台。这样的启动方式叫做热启动。
    假如打开Activity的时候没有进程,则去创建一个新的进程,在该进程中创建Activity。这样的启动方式叫做任冷启动。
    不管是冷启动还是热启动都会经历空白页。所以我们针对以上进程优化:

    1、利用提前展示出来的Window,快速展示出一个界面来代替空白页。
    2、避免在Applicaion中做大量的初始化操作,如果能异步就异步。尽快加载主Activity。

    5、包体积的优化

    1、lint检测无用的代码、资源。
    2、包的体积主要在于图片。能自己手写shape就不要用图片
    3、适当压缩图片
    4、代码混淆,使用proGuard 代码混淆器工具,它包括压缩、优化、混淆等功

    6、电量的优化

    Battery Historian电量分析工具

    1、使用JobScheduler调度任务
    在合适的场景调度相应的任务。
    2、懒人做法。缓存数据、推迟执行等等

    7、相应速度的优化

    异步任务

    8、线程的优化

    使用线程池,因为大量的线程的创建和销毁会给性能带来一定的开销。线程池还能有效的控制并发数,因为如果有大量的线程相互想占资源,会造成堵塞现象。

    相关文章

      网友评论

        本文标题:Android性能优化小结

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