Android性能优化 基础理论篇

作者: lonamessi | 来源:发表于2018-05-21 11:01 被阅读14次

    做什么都要往精益求精的方面去做,就拿写文章来说,写的越详细越好,一方面让读者可以读懂弄明白,再一方面自己在以后回头看也能清楚的想起来。

    性能优化的重要性不再强调,新手一枚如果有不对的地方请多多指导!本文只是抛砖引玉,毕竟性能这块研究起来还是需要各方面综合起来的,那么开车了开车了!!!
    472b43460482be61a4fe93749e832d51.gif

    界面是直接反馈给用户的,其实说白了性能就是流畅度!

    目录

    • Android渲染知识
    • 界面绘制
    • 内存问题

    一 Android渲染知识


    1.1 绘制原理

    Android需要每一帧在16ms内绘制完成,保证流畅的用户体验,如果没有在16ms内绘制完一帧的话就会出现丢帧的情况。那么一秒的帧率就是大约60帧。因为人眼无法感知到超过60帧的画面更新。我们都知道电影画面大部分都是24帧每秒(人眼能感知的连续线性的运动),为了更好的变现绚丽的画面内容,安卓采用了60帧来限定绘制!

    1.2渲染性能问题
    渲染问题其实就是丢帧现象

    1.在UI主线程做大量的耗时操作,容易引起ANR;

    2.布局过于复杂,导致无法在16ms内完成渲染;

    3.在较短的时间内过于频繁的执行动画,导致CPU和GUP工作超负荷;

    4.View的过度绘制,比如一些不能被用户看到,无用的背景,也被绘制;

    5.频繁的去GC;

    6.View频繁的触发measure、layout,导致measure、layout累计耗时过多 及整个View频繁的重新渲染;

    7.冗余资源及逻辑等导致加载和执行缓慢;

    二 界面绘制


    这里主要讲解界面布局优化
    • 尽量减少布局的层级关系,能使用相对布局减少层级关系的就使用相对布局,不然就使用线性布局;(Android中RelativeLayout和LinearLayout性能分析

    • 标签的使用<include>,<merge>,以及<ViewStub> 来减少层级关系和不必要的界面绘制。

    • 尽量避免使用 layout_weight 属性。使用包含 layout_weight 属性的线性布局 LinearLayout 每一个子组件都需要被测量两次,会消耗过多的系统资源。

    • 避免在View的onDraw方法中创建新的局部对象,onDraw可能会被频繁调用,如果在此方法中创建局部对象的话,可能会瞬间产生大量临时变量,然后系统会频繁GC,降低了程序的执行效率。

    • 尽量减少在onDraw方法中进行耗时操作。

    • 动画带来的绘制丢帧,我们有时候需要开启硬件加速!

    三 内存问题


    程序内存的管理是否合理高效对应用的性能有着很大的影响。

    友情链接>>>>>>>>Android性能优化典范

    3.1 内存浪费
    3.1.1 ArrayMap(源码大量运用),SparseArray

    安卓为我们提供了一些更佳高效的容器,那就是ArrayMap和SparseArray,为了解决HashMap的内存占量大的问题,HashMap有固定的的储存空间,如果超过会成倍增加,虽然在时间上HashMap更快,但是它也花费了更多的内存空间。由于HashMap存储的是非基本数据类型,因此自动装箱的存在意味着每次插入都会有额外的对象创建,这会影响到内存的利用。
    SparseArray比HashMap更省内存,在某些条件下性能更好,主要是因为它避免了对key的自动装箱(int转为Integer类型),它内部则是通过两个数组来进行数据存储的,一个存储key,另外一个存储value,为了优化性能,它内部对数据还采取了压缩的方式来表示稀疏数组的数据,从而节约内存空间
    ArrayMap是一个<key,value>映射的数据结构,它设计上更多的是考虑内存的优化,内部是使用两个数组进行数据存储,一个数组记录key的hash值,另外一个数组记录Value值,它和SparseArray一样。

    3.1.2避免自动装箱

    自动拆装箱的目的就是自动地将基础类型与它们的对象版本相互转化,这样你就不用操心你代码中的这些转化了,这个过程其实是这样的

    Integer total = 0;
    for (int i = 0; i < 100; i++) {
      total += i;
    }
    

    看似这样使我们写代码方便了许多,其实他真正的过程是这样的

    Integer total = 0;
    for (int i = 0; i < 100; i++) {
      //total += i;
      // create new Integer()
      // push in new value
      // add to total
    }
    

    自动装箱会创建大量的对象,然后会GC进行频繁回收。这样就导致会发生卡顿现象,经常发生在类似HashMap这样的容器里面,对HashMap的增删改查操作都会发生了大量的自动装箱的行为。当key是int类型的时候,HashMap和ArrayMap都有Autoboxing行为,为了避免Autoboxing行为Android提供了SparseArray,此容器使用于key为int类型。

    链接直通车>>>>>>>>>>>>>>> 小心自动装箱

    3.1.3 Enum(项目中尽量避免使用枚举)
    Android官方强烈建议不要在Android程序里面使用到enum,使用enum运行时还会产生额外的内存占用.

    3.2 内存泄漏

    一些不用的对象长时间被持有,GC无法回收,导致内存无法被释放
    可能发生内存泄漏的场景
    • 静态变量导致的内存泄漏
    public class MainActivity extends AppCompatActivity {
    
        public static Context context;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            context = this;
            CommUtil.getInstance(getApplicationContext());
        }
    
    

    上面这种是最简单的形式,因为静态变量context引用了当前的Activity,这就导致当前的Activity无法被销毁。类似这种的静态变量都会导致内存泄漏

    • Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。
      这个最典型的就是单利造成的内存泄漏
    public class CommUtil {
        private static CommUtil commUtil;
        private Context context;
    
        public CommUtil(Context context) {
            this.context = context;
        }
        public static CommUtil getInstance(Context context){
            if (null==commUtil){
                commUtil = new CommUtil(context);
            }
            return commUtil;
        }
    }
    
    public class MainActivity extends AppCompatActivity {
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            CommUtil.getInstance(this);
        }
    }
    

    泄漏的原因是Activity的对象被单例模式的CommUtil 类所持有,而单利模式的特点是其生命周期和Application保持一致,因此Activity对象无法被及时释放。修改的方法就是把this换成getApplicationContext();

    • 内部类引用导致Activity的泄漏
      这种也有一个典型的案例就是创建Handler的方式会造成内存泄漏。
    ublic class MainActivity 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);
    
        }
    
    }
    

    由于mHandler是非静态匿名内部类的实例,那么他就持有当前activity的引用,如果有大量消息还没有处理完的情况下关闭了activity,那样Handler会继续处理,又由于handler持有当前activity的引用,这就导致该Activity的内存资源无法及时回收,引发内存泄漏。
    修改的方法就是创建一个静态的内部类Handler,对Handler对象使用弱引用,这样就避免了不能回收Handler持有的对象,也就避免了Activity的泄漏,然后我们在Activity的Destory时把消息移除就行了。具体做法如下:

    public class MainActivity 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) {
    
                MainActivity activity = (MainActivity) reference.get();
    
                if(activity != null){
    
                    activity.mTextView.setText("");
    
                }
    
            }
    
        }
        @Override
    
        protected void onCreate(Bundle savedInstanceState) {
    
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_main);
    
            mTextView = (TextView)findViewById(R.id.textview);
    
            loadData();
    
        }
    
        private void loadData() {
    
            //...request
    
            Message message = Message.obtain();
    
            mHandler.sendMessage(message);
    
        }
    
        @Override
    
        protected void onDestroy() {
    
            super.onDestroy();
    
            mHandler.removeCallbacksAndMessages(null);
    
        }
    
    }
    

    还有一种就是线程造成的内存泄漏,创建线程一般也是匿名内部类,这样也会持有当前activity的对象,如果线程任务未完成就关闭当前activity,就会导致activity无法被释放回收,引起内存泄漏。

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

    内存泄漏的例子还很多,这里就不一一列举了。Android的内存优化涉及的知识面还有很多:内存管理的细节,垃圾回收的工作原理,如何查找内存泄漏等等都可以展开讲很多。OOM是内存优化当中比较突出的一点,尽量减少OOM的概率对内存优化有着很大的意义。这篇只是一个基础知识篇,这个系列还会继续更新。有不对的地方也望指出!

    相关文章

      网友评论

        本文标题:Android性能优化 基础理论篇

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