美文网首页
Android 性能优化,学习总结

Android 性能优化,学习总结

作者: Ord1nAry_c568 | 来源:发表于2019-12-20 09:43 被阅读0次

    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 其他三方的打包方式....

    相关文章

      网友评论

          本文标题:Android 性能优化,学习总结

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