Android性能优化 基础理论篇

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

性能优化的重要性不再强调,新手一枚如果有不对的地方请多多指导!本文只是抛砖引玉,毕竟性能这块研究起来还是需要各方面综合起来的,那么开车了开车了!!!
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的概率对内存优化有着很大的意义。这篇只是一个基础知识篇,这个系列还会继续更新。有不对的地方也望指出!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容