Android 性能优化,学习总结

1 . APP启动相关优化

1. 黑白屏

首先谈到黑白屏对于有些人可能会一脸懵逼,这是啥! 咋出来的.黑白屏是在我们的APP启动时 会首先看到一个 黑色的页面或者白色的页面.故称之为黑白屏. 白屏出现的原因是我们在设置项目主题的时候,让我们主题继承了 Light类型的主题 例如:<style name="AppTheme" parent="Theme.AppCompat.Light"> 这样设置项目主题 ,我们的APP 在启动时会显示白色页面,如果不写parent 会在老版本上显示黑屏,现在的版本都是透明显示了.

解决方式:

1.在自己的<style name="AppTheme" parent="Theme.AppCompat.Light">中加入windowsbackground

2.设置windowbackground为透明的  <item name="android:windowIsTranslucent">true</item>

上面两种方式会有个问题,所有的activity启动都会显示

3.单独做成一个主题
<style name="AppTheme.Launcher">
        <item name="android:windowBackground">@drawable/bg</item>
    </style>
    <style name="AppTheme.Launcher1">
        <item name="android:windowBackground">@drawable/bg</item>
    </style>
    <style name="AppTheme.Launcher2">
        <item name="android:windowBackground">@drawable/bg</item>
    </style>
再在功能清单中的单独activity下设置
<activity
            android:theme="@style/AppTheme.Launcher"
然后在程序中使用setTheme(R.style.AppTheme);
让APP中所有的activity还是使用以前的样式,这样做就只有启动时才使用自己的样式

4.下面这种方式在QQ中被用到 ,禁止加载Priview window并且设置启动背景是透明,但是要注意 如果启动耗时会感觉无响应一段时间再进入APP,使用之前保证进入应用的耗时最短 
<item name="android:windowDisablePreview">true</item>
   <item name="android:windowBackground">@null</item>

2. 启动时长优化

APP启动时长也就是从点击app图标到肉眼可看到页面这一过程,而且APP的绘制是单线程工作的,如果在这一部分中操作过于耗时,那就会出现点击时候长时间无响应. 所以我们要降低这段时间内的耗时操作. 我们可操作性的启动周期一般是在Application的Oncreate() 方法到Activity的onResume 之间.

如果想要统计代码的运行时长可以使用如下API

Debug.startMethodTracing(filePath);
中间为需要统计执行时间的代码
Debug.stopMethodTracing();

使用上面的api 可以得到中间代码块运行的时间,主要分析Application 和Activity的的OnCreate方法,分析出耗时的操作,合理去安排.调用时机 达到缩短启动时长

解决方式:

1.分析业务代码,开线程去加载实时性要求不高没有UI操作 对异步要求不高的逻辑,
2,懒加载 用到的时候栽加载,网络 数据库 配置文件.....

2. UI绘制优化

UI绘制过程中如果任务繁重,也会导致频繁GC 占用主线程资源引起页面卡顿,所以需要先了解Android的绘制原理,分析在绘制过程中可能出现的卡段的点进行分析,这里不再赘述绘制过程 我的上一篇文章 中会有介绍绘制流程

源码分析setContentView()到底做了什么,布局绘制是在Activity的哪个周期),所以要分析这块减少GPU工作量(绘制),减少CPU工作量(计算)

1.过渡绘制优化

减少GPU工作量,可以开启开发者模式的Profile GPU rendering/调试GPU过度绘制 查看页面的绘制层级,绘制层级分为 蓝色 绿色 粉色 红色依次增加

解决方式:

1.减少背景重复挥着 
    (1)去掉单个activity的主题设置的属性可以在setContentView之前getWindow().setBackgroundDrawable(null);
    (2)去掉所有activity主题设置中的属性
直接在styles.xml中设置<item name="android:windowBackground">@null</item>
    (3)xml布局中嵌套控件的时候 背景设置要合理 优先最外层设置

设置主题背景要自行根据业务需求合理设置,

2.自定义控件使用剪裁减少控件之间重合部分
3.不要在onDraw方法中去做耗时操作,将一些全局的对象提前创建好,尤其在绘制大量图片是 ,提前在init中将bitmap 准备好.
4.合理使用api
    invalidate():不测量 不布局 只绘制
    requestLayout(): 重新测量布局 不去绘制

2.布局优化

减少CPU工作量,可以使用Device Monitor窗口中Hierarchy view去查看绘制三大方法的性能,去着重分析局部代码

三个点也是代表着View的Measure, Layout和Draw。
绿: 表示该View的此项性能比该View Tree中超过50%的View都要快;例如,代表Measure的是绿点,意味着这个视图的测量时间快于树中的视图对象的50%。
黄: 表示该View的此项性能比该View Tree中超过50%的View都要慢;
红: 表示该View的此项性能是View Tree中最慢的;。

image

解决方式:

1. xml层级嵌套一定要低,布局扁平化 ,LinearLayout 渲染会测量一次如果设置权重会测量两次, 但是RelativeLayout 会测量两次,但是RelativeLayout可以降低控件的层级,选择的时候要合理分析使用. 另外谷歌新出的协调布局ConstrainLayout也可以降低布局层级,复杂布局使用这个控件性能优于传统控件,  一般的布局使用这个控件性能消耗高于传统控件

2. 合理选择排版容器
    选用merge标签去合并,减少一层嵌套  
    不经常显示的控件使用Viewstub标签  
    经常使用的布局可以使用include 标签
    LinearLayout中的分割线可使用divide 属性 RecyclerView 分割线使用Itemdecoration
    占位布局使用Space 这个控件只是去测量(很少用)
    

3.内存优化

内存优化是一个大的点,也是我们最关注的,想要优化内存方面,就需要简单去了解下 ,java的虚拟机 GC回收器等方面的知识.

1.内存分析工具

top/procrank
meinfo
Procstats
DDMS
MAT
Finder-Activity
LeakCanary
LeakInspector
工具很多,掌握原理方法,工具随便找两个能用就行,用过MAT 比较容易定位问题出处

Android Profiler的使用
Run菜单下的profile...

    在图型用户界面上选择要分析的一段内存,右键export出来 
    Allocations:  动态分配对象个数
    Deallocation:解除分配的对象个数
    Total count :对象的总数
    Shallow Size:对象本身占用的内存大小
    Retained Size:GC回收能收走的内存大小

Mat工具的使用

    转换profile文件格式
    sdk/platform-tools/hprof-conv.exe
    转换命令   hprof-conv -z src dst
    下载:  https://www.eclipse.org/mat/downloads.php
    打开软件   File菜单下Open Heap Dump...     打开转换好的文件
    点击QQL按钮查找activity
        select * from instanceof android.app.Activity 

2.java虚拟机

Java虚拟机会有线程私有区 ,和共享数据区,本地栈\虚拟机栈\程序计数器都在线程私有区中,程序计数器是一个代码指示器记录代码执行的地址,本地栈就是我们平时说的栈区, 栈溢出的问题会发生在这里.

共享区又分为方法区和java堆区,这些地方容易引发OOM,方法去存放

字面量public satic final java常量,符号引用 类,接口全名,方法名.java堆区存放对象实例,这里也是最容易引发OOM的区域 .平时所做的优化主要集中在这一部分的.

3.GC 垃圾回收

1.引用计数法

对象创建赋值会计数加1 销毁置空会减一,早期使用的是这中方式, 这种方式的缺点是 如果出现对象的相互引用 计数器会出现不为0的情况,导致 对象不能及时回收

2.可达性算法

GC会扫描两次根对象 ,不可达的对象会首先标记,等待第二次回收之后才会对标记对象回收,所以可以在finalize 中去激活对象的引用.

3.引用类型

强引用: Object obj=new Object();

软引用:内存不足时回收,存放一些重要性不是很强又不能随便让清除的对象,比如图片切换到后台不需要马上显示了

弱引用:第一次扫到了,就标记下来,第二次扫到直接回收

虚引用:用于跟踪GC的回收通知,产生一个回收的回调

4.内存抖动,内存溢出

内存抖动是 内存频繁的分配与回收,当分配速度大于回收速度时会产生内存溢出

内存的回收算法: 标记清除算法, 复制算法,标记压缩算法,分代收集算法(年轻代 老年代 持久代 使用的收集算法很多)

5.内存泄漏

无用的对象因引用问题长时间得不到回收,就是内存泄漏, 最终会引起内存溢出OOM

解决方式

1. 合理使用数据类型 ,不要使用比需求占用更多内存的数据类型
2.循环尽量使用foreach 少用iterator ,自动装箱尽量少用
3.数据结构与算法,(数组 链表 栈 树 图...) 少量数据使用sparse数组 ArrayMap  性能不如HashMap 但是省内存
4.少用枚举每一个枚举都是单例对象 可使用其他方式代替
    public class SHAPE {
        public static final int RECTANGLE=0;
        public static final int TRIANGLE=1;
        public static final int SQUARE=2;
        public static final int CIRCLE=3;


        @IntDef(flag=true,value={RECTANGLE,TRIANGLE,SQUARE,CIRCLE})
        @Target({ElementType.PARAMETER,ElementType.METHOD,ElementType.FIELD})
        @Retention(RetentionPolicy.SOURCE)
        public @interface Model{

        }

        private @Model int value=RECTANGLE;
        public void setShape(@Model int value){
            this.value=value;
        }
        @Model
        public int getShape(){
            return this.value;
        }
   }
5.static final 问题  
    static会由编译器调用clinit方法进行初始化
    static final不需要进行初始化工作,打包在dex文件中可以直接调用,并不会在类初始化申请内存
    所以基本数据类型的成员,可以全写成static final
6.字符串连接少用+  使用stringbuffer(线程安全,速度慢)  stringbuilder (线程不安全,速度快)
7.重复申请内存  
    同一个方法多次调用,如递归函数 ,直接在循环中new对象等
    不要在onMeause()  onLayout() onDraw()  中去刷新UI(requestLayout)
8.LRU算法 重用回收的对象
9.尽量使用IntentService 
10.Activity组件泄漏
    Activity的上下文不要随便传递
    非静态内部类和匿名内部类持有Activity引用 只要在内部类中能使用到Activity的方法都会默认持有上下文,可以新建一个class 使用软引用来处理
    静态修饰的控件,每个控件都会有Activity的上下文
    单例模式
    handler的postdelay()问题,在onDestory 中记得销毁可能引发内存泄漏的对象
  

4.图片相关

这里不再分析,现在图片的加载都在用三方工具. 也有使用哈夫曼编码对rgb的元数据进行变频压缩

1.bitmap 获取


public class ImageResize {

    /**
     *  缩放bitmap
     * @param context
     * @param id
     * @param maxW
     * @param maxH
     * @return
     */
    public static Bitmap resizeBitmap(Context context,int id,int maxW,int maxH,boolean hasAlpha,Bitmap reusable){
        Resources resources = context.getResources();
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 只解码出 outxxx参数 比如 宽、高 ,不会加载整个图片
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources,id,options);
        //根据宽、高进行缩放
        int w = options.outWidth;
        int h = options.outHeight;
        //设置缩放系数
        options.inSampleSize = calcuteInSampleSize(w,h,maxW,maxH);
        if (!hasAlpha){
            options.inPreferredConfig = Bitmap.Config.RGB_565;
        }
        options.inJustDecodeBounds = false;
        //设置成能复用 图片复用设置下面两个方法
        options.inMutable=true;
        options.inBitmap=reusable;
        return BitmapFactory.decodeResource(resources,id,options);
    }

    /**
     * 计算缩放系数
     * @param w
     * @param h
     * @param maxW
     * @param maxH
     * @return 缩放的系数
     */
    private static int calcuteInSampleSize(int w,int h,int maxW,int maxH) {
        int inSampleSize = 1;
        if (w > maxW && h > maxH){
            inSampleSize = 2;
            //循环 使宽、高小于 最大的宽、高
            while (w /inSampleSize > maxW && h / inSampleSize > maxH){
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

5.包体积优化

1.将图片转换成webp 使用svg矢量图

webp 技术是从视屏解码演变过来的 ,通过像素宏 预测技术去记录色块的颜色,可以使用Androidstudio的 右键->convert to webp转成 webp图片 用法和普通图片一样 ,转成webp图片可以减少26%左右的图片大小,android 4.0 开始原生支持.但不支持透明度,4.2.1 才支持显示包含透明度的webp图片

[官方介绍:](<https://developers.google.com/speed/webp/docs/precompiled)

项目中的简单图标可以使用svg 矢量图.

2. 去除多语言

由于第三方库,如appcompat-v7的引入,库中包含了大量的国际化资源, 根据情况通过配置删除

android {
    defaultConfig {
        resConfigs "zh"
    }
}

3.NDK架构设置

如果项目中包含第三方SDK或者自己使用了ndk, 如果不进行配置会打包全cpu架构的动态库进入apk。 对于真机,只需要保留一个armeabi(armeabi-v7a)就可以了,基本上armable的so也是兼容armable-v7的,armable-v7a的库会对图形渲染方面有很大的改进,如果没有这方面的要求,可以精简。,x86的so库目前也只有模拟器在使用了

image

4.移除无用资源(Lint检查)

image

移除时要注意动态获取资源的方式,int indetifier =getResources().getIdentifier("img_bubble_receive", "drawable", ggetResources().getDrawable(indetifier); 动态获取资源id,未直接使用R.xx.xx 则这个id代表的资源会被认为没有使用过(类似不能混淆反射类)

5. Lint 检查

Lint 是Android Studio 提供的 代码扫描分析工具,它可以帮助我们发现代码结构/质量问题,同时提供一些解决方案,而且这个过程不需要我们手写测试用。 代码迭代版本一多,很容易会遗留一些无用的代码、资源文件,我们可以使用 Lint 进行 清除

6. 开启混淆

Android代码混淆,是一种Android APP保护技术,用于保护APP不被破解和逆 向分析。ProGuard的三大作用

压缩 移除未被使用的类、属性、方法等,并且会在优化动作执行之后再次执行(因为优化后可能会再 次暴露一些未被使用的类和成员。

优化 优化字节码,并删除未使用的结构。 

混淆 将类名、属性名、方法名混淆为难以读懂的字母

android {
    buildTypes {
        release {
            minifyEnabled true
        }
    }
}

7.开启删除无用资源(与Lint不同)

shrinkResources = true
shrinkResources 用来开启压缩无用资源,也就是没有被引用的文件(经过实测是drawable,layout,实际并不是彻底删除,文件名,但是没有内容,等等),但是因为需要知道是否被引用所以需要配合mififyEnable使用,只有当两者都为true的时候才起到真正的删 除无效代码和无引用资源的目的与去除无用资源不同的是,比如某个java类没有用到,被混淆时删除了,而该类引入了layout资 源 。此时会将这个资源也压缩掉

android {
   buildTypes {
       release {
           shrinkResources true
       }
   }
}

8.使用一套资源

根据分辨率的趋势建议只放720p的资源,其他分辨率的资源 与720 的相差不大. 但是要根据业务需求自行考虑放几套资源

9.使用tinypng有损压缩

android打包本身会对png进行无损压缩,所以使用像tinypng这样的有损压缩是有必要的。
重点是Tinypng使用智能有损压缩技术,以尽量少的失真换来图片大小的锐减,效果非常好,强烈推荐。
Tinypng的官方网站:http://tinypng.com/

10. 使用jpg格式

如果对于非透明的大图,jpg将会比png的大小有显著的优势,虽然不是绝对的,但是通常会减小到一半都不止。
在启动页,活动页等之类的大图展示区采用jpg将是非常明智的选择。

11. 缩小大图

如果经过上述步骤之后,你的工程里面还有一些大图,考虑是否有必要维持这样的大尺寸,是否能适当的缩小。
事实上,由于设计师出图的原因,我们拿到的很多图片完全可以适当的缩小而对视觉影响是极小的。

12. 覆盖第三库里的大图

有些第三库里引用了一些大图但是实际上并不会被我们用到,就可以考虑用1x1的透明图片覆盖。
你可能会有点不舒服,因为你的drawable下竟然包含了一些莫名其妙的名称的1x1图片…

13. 使用AndResGuard 对资源进行混淆

[图片上传失败...(image-727ecb-1576806224438)]

14. 使用debugApi 编译

一些三方类库在开发环境需要使用 正式环境不需要刻意使用debugApi 只在debug环境下引入

image

15.使用tint 着色器 使用shape 绘制背景

16.防止重复功能的类库引入

在开发中同样功能的库不要都引入 ,寻找最符合业务需求的最小的包去使用,如有必要刻意自行筛选出有用的功能模块自行去复制到项目中,精简业务代码,减少冗余代码.

17.其他方式

组件化 插件化 redex 其他三方的打包方式....

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,103评论 1 32
  • 1,UI优化:这篇文章总结的不错 2,内存泄漏优化 常见的几种形式: 资源对象没关闭造成的内存泄漏: 资源对象没关...
    Richard_7df6阅读 267评论 0 0
  • 一、前言: 用android的都知道,新买的手机用过一段时间后,手机变得越来越卡了;装了一些APP后,电量用得飞快...
    因为我的心阅读 1,352评论 2 19
  • 因为比较零散,所以就一起说了。 陀老的荣如德译本比较口语化,符合汉语环境,但是否还有陀老自己的文学韵味,目前还不方...
    ArimaKisho阅读 279评论 0 0
  • LVQ 试图找到一组原型向量来刻画聚类结构,但与一般聚类算法不同的是,LVQ假设数据样本带有类别标记,学习过程利用...
    程序猿爱打DOTA阅读 2,371评论 0 1