美文网首页Android...
程序员自我修养之性能优化篇

程序员自我修养之性能优化篇

作者: 巴菲伟 | 来源:发表于2021-03-29 11:47 被阅读0次

一、UI布局优化

1.启动优化

        冷启动分为三个步骤:创建进程、启动应用、绘制界面

2.创建进程分为三个步骤:

        加载startingWindow、创建应用进程、启动App

3.启动应用分为三个步骤:

        创建Application、创建主线程、创建MainActivity

4.绘制界面分为两个步骤:

        加载布局、图像绘制

5.统计启动时间的方法:

        a. TraceCompat.beginSection();

            TraceCompat.endSection(); 

        wall time : 是代码执行完花费的时间(包含cpu和IO的读写操作)

  cpu time : 是cpu花费多少时间执行完这段代码(这里才是作为主要的优化时间点)

        执行systrace.py这个参数,就可以把你加的日志生成一个html文件进行查看,它也可以底层的执行时间

        b.  使用AOP切面编程:代码无侵略性

        c.  AS的profiler这个工具也可以查看耗时

 6.UI常见问题以及优化

        常见问题 : 过度绘制、布局复杂、层级过深

        解决办法:  减少从xml文件创建为对象、减少过度绘制操作

        1).xml文件上解决过度绘制 : 使用android:backgroud会增加一次过度绘制次数

        2).自定义view过度绘制:

clipRect : 识别可见区域绘制,非可见部分就不进行绘制,俗称裁剪。注意:在调用cilpRect、scale(旋转)、translate(平移)、缩放等这些操作之前,需要调用canvas.save方法

        3).可以采用ViewStub的方式去占位布局,使用时再去渲染

4).查看代码性能问题:AS->Analyze->Inspect Code 查看关键字Performance、Xml文件

        5).常见的异步操作的手段

 AsyncTask :UI线程和工作线程之间快速切换提供的简单机制

         适用场景:立即需要启动,但是异步执行的生命周期短暂

HanderThread : 为某些回调方法或者等待某些任务执行设置一个专属线程

ThreadPool : 把任务分解到各个不同的线程上,进行同时并发处理

IntentService : 适合执行由UI触发的后台service,可以把后台执行任务反馈给UI(适合在Application 的onCrate里面适用,操作简单,且自己可以销毁对象,其他方式小题大做)

       6).ListView性能优化

            a.复用converView

            b.复用viewHolder

            c.getView少做耗时的操作

            d.滑动停止时候再去加载图片

            e.使用异步线程加载图片

滑动时候为什么不会发生OOM的原因(RecycleBin的复用机制)

         RecycleBin里面有两个存数据的数组,一个是ActivtyView数组,这个是给用户展示的view,另一个是ScrapView数组,它是不需要展示view,但是已经创建,为了二次展示作为复用来使用。展示的view会先从ScrapView数组去遍历查找是否存在,如果存在直接在复用给ActivityView来使用,如果不存在创建之后给ActivityView来展示。

 7.Systrace去检查UI卡顿问题

    在android-sdk/platform-tools/systrace/执行systrace.py

    执行python systrace.py --time=10 -o mynewtrace.html 命令生成.html文件

W:界面放大

S:界面缩小

A:界面左移

D:界面右移

二、内存优化

    1.OOM容易混淆的概念

        内存溢出:我们申请的内存已经超出虚拟机的最大内存的限度就会抛出Out  Of  memory

        内存抖动 :  短时间大量的对象被创建然后就释放,触发的GC机制,严重占用内存区。

        内存泄露 :  我们要回收的对象无法进行GC的回收操作

        造成OOM的原因:

        Java堆内存溢出、无足够的连续空间、虚拟内存不足、线程数量超出限制

    2.内存泄漏的常见案例

        1).单例:长生命周期的对象(Application的对象)被短生命周期的对象持有

2). handler : 非静态内部类持有外部类的引用,导致于无法释放外部类的对象---->解决方法将handler至于为static的对象,然后将外部类改为弱引用来使用

        3).线程引起: AsyncTask和Runnable使用匿名内部类,因为非静态内部类持有外部类的引用,和handler类似

        4).文件的读写、数据库网络启用后没有关闭、三方的框架没有销毁

        5).注册广播以及使用三方的库没有再销毁时候关闭

     3.不同内存的类型

    内存的类别如下:

        Java:从 Java 或 Kotlin 代码分配的对象的内存。

        Native:从 C 或 C++ 代码分配的对象的内存。

        Graphics:图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。

        Stack:您的应用中的原生堆栈和 Java 堆栈使用的内存

        Code:您的应用用于处理代码和资源(如 dex 字节码、经过优化或编译的 dex 代码、.so 库和字体)的内存。

        Others:您的应用使用的系统不确定如何分类的内存。

 4.内存优化方向

     1).BitMap的图片优化,避免滥用Bitmap导致内存的浪费,使用完及时回收

     2).减少对象重复创建,多采用复用方式,避免内存碎片化

     3).包体的优化 : 混淆、删除多余文件和图片、压缩图片、语言和cpu架构的配置

     4).使用适当的数据结构去存储数据

 5.内存优化之垃圾对象的处理

 FinalizerReference就会越来越大原因:

一旦用户创建对象(重写了finalize函数)的速度快于finalize队列移除各元素的速度,finalize里面回收的是弱引用和虚引用,虚引用主要记录对象的销毁,也因为JVM的垃圾回收、内存管理都是守护线程,他们优先级比用户线程低很多。设置守护线程方法是setDaemon(true)的方法

解决FinalizerReference过大的办法:

    1.不要重写finalize()方法,实在要释放资源,到destroy一类函数中处理

    2.重复利用资源,可以采用对象池方式处理,防止发生内存抖动,避免反复创建

    3.调用System.runFinalization()回收

 System.gc()和System.runFinalization()区别:

    System.gc():告诉垃圾收集器打算进行垃圾收集,而垃圾收集器进不进行收集是不确定的

    System.runFinalization():强制调用已经失去引用的对象的finalize方法

 6.内存优化之碎片化 

  解决方式:

   共享元设计模式对象池去处理 : 创建一个读对象之后就不会再创建了,只是里面的数据会被清空,对象不会被销毁

    对象池线程不安全的

    Pools.SimplePool<Integer> pool = new Pools.SimplePool(10);

    对象池线程安全的

    Pools.SynchronizedPool<Integer> pool1 = new Pools.SynchronizedPool<>(10);    

 java内存的大小计算 : shallow size、retained size

    shallow size : 自身占用的内存的大小

    retained size :该对象能直接或间接访问到对象的shallow size之和

   案例讲解:

从obj1入手,上图中蓝色节点代表仅仅只有通过obj1才能直接或间接访问的对象。因为可以通过GC Roots访问,所以左图的obj3不是蓝色节点;而在右图却是蓝色,因为它已经被包含在retained集合内。

    对于左图,obj1的retained size是obj1、obj2、obj4的shallow size总和;右图的retained size是obj1、obj2、obj3、obj4的shallow size总和。

    对于obj2,它的retained size是:在左图中,是obj2和obj4的shallow size的和;在右图中,是obj2、obj3和obj4的shallow size的和。

     Linux内存管理:(做为了解,后续再补充)

    优质的网址:https://www.kancloud.cn/zhangyi8928/kernel/531016

     VSS- Virtual Set Size 虚拟耗用内存(包含共享库占用的内存),用处不大

     RSS- Resident Set Size 实际使用物理内存(包含共享库占用的内存),用处不大

     PSS- Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)

     USS- Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存),它是观察内存泄漏的最好参数

 7.内存优化之图片优化

     1). 一张图片加载到内存的计算

        加载分辨率为1080*452的图片,文件本身大小是56kb

        1080*452*4B =  1952640B -----> 1.86MB  这样计算会有问题

        正确计算方式:

      1080*452*4B*(设备的dpi(240)/目录的dpi(1.5))-------->这个就可以被放在对应目录下的正确的大小

    2).BitMap的几种像素格式

        ALPHA_8:每个像素都需要1(8位)个字节的内存,只存储位图的透明度,没有颜色信息

        ARGB_4444:A(Alpha)占4位的精度,R(Red)占4位的精度,G(Green)占4位的精度,B(Blue)占4位的精度,加起来一共是16位的精度,折合是2个字节,也就是一个像素占两个字节的内存,同时存储位图的透明度和颜色信息。不过由于该精度的位图质量较差,官方不推荐使用

        ARGB_8888:这个类型的跟ARGB_4444的原理是一样的,只是A,R,G,B各占8个位的精度,所以一个像素占4个字节的内存。由于该类型的位图质量较好,官方特别推荐使用。但是,如果一个480*800的位图设置了此类型,那个它占用的内存空间是:480*800*4/(1024*1024)=1.5M

        RGB_565:同理,R占5位精度,G占6位精度,B占5位精度,一共是16位精度,折合两个字节。这里注意的时,这个类型存储的只是颜色信息,没有透明度信息

    3).图片优化的方式 : 降低分辨率、减少每个像素点大小

        如何去加载一个大图的方式:根本原因是降低图片的像素

        图片的优化行为:

        质量压缩 :  可以降低图片质量,不能降低图片的像素,不能起到图片优化效果

        采样率压缩 :  可以降低图片的像素options.inSample 这个参数,起到图片优化效果

        尺寸压缩 : 通过压缩图片宽、高来降低图片的像素,起到图片优化效果

        改变图片的格式,从RGBA_8888(默认)转变为其他格式,这种不推荐使用

   4).  图像的开辟和销毁

        图像开辟:

        图片需要缩放,都是开辟在native层的,如果不需要缩放,8.0以前是在java层开辟的

        8.0以下放在 java的内存之中

        8.0以上是放在native内存之中,有2G内存供使用

        图片销毁:

        bitMap.recycle() : 回收是像素数据,只是在8.0以及以上可以使用

        6.0以上是通过监听GC触发机制去传递给底层使用,然后进行销毁,监听方式是ReferenceQueue队列去监听

     5).BitMap的BitmapFactory.Options参数讲解

        Scale(转换率) =  inTargetDensity /  inDensity

        BitmapFactory.Options options =new BitmapFactory.Options();

        //会根据drawable文件夹的分辨率来赋值

        options.inDensity

        //会根据屏幕的像素密度来赋值

        options.inTargetDensity

        //对图片进行压缩,对图片宽和高各压缩2倍

        options.inSampleSize = 2

        //设置图片是否会被压缩,它的参数就是由Scale(转换率)去计算

        options.inScaled                 

        //为true不会得到bitMap的对象,而是可以的高宽、高的信息

        options.inJustDecodeBounds

        //代表资源图片的的宽

        options.outWidth;

        //代表资源图片的的高

        options.outHeight

        //代表BitMap可以复用

        options.inMutable

        //设置图片

        options.inBitmap

    Bitmap bitmap = BitmapFactory.decodeResource(getResources(),                    R.drawable.ic_launcher_background, options);

  8.内存优化之检测系统接口

    OnLowMemory:在系统内存不足,所有后台程序(优先级为background的进程,不是指后台运行的进程)都被杀死时,系统会调用OnLowMemory

    OnTrimMemory:系统会根据不同的内存状态来回调。系统提供的回调有:                              Application/Activity/Fragement/Service/ContentProvider      

    TRIM_MEMORY_COMPLETE:内存不足,并且该进程在后台进程列表最后一个,马上就要被清理

    TRIM_MEMORY_MODERATE:内存不足,并且该进程在后台进程列表的中部。

    TRIM_MEMORY_BACKGROUND:内存不足,并且该进程是后台进程。

    TRIM_MEMORY_UI_HIDDEN:内存不足,并且该进程的UI已经不可见了。      

    TRIM_MEMORY_RUNNING_CRITICAL:内存不足(后台进程不足3个),并且该进程优先级比较高,需要清理内存

    TRIM_MEMORY_RUNNING_LOW:内存不足(后台进程不足5个),并且该进程优先级比较高,需要清理内存

    TRIM_MEMORY_RUNNING_MODERATE:内存不足(后台进程超过5个),并且该进程

OnLowMemory和OnTrimMemory的比较

  a.OnLowMemory被回调时,已经没有后台进程;而onTrimMemory被回调时,还有后台进程。

  b.OnLowMemory是在最后一个后台进程被杀时调用,一般情况是low memory killer 杀进程后触发;而OnTrimMemory的触发更频繁,每次计算进程优先级时,只要满足条件,都会触发。

  c.通过一键清理后,OnLowMemory不会被触发,而OnTrimMemory会被触发一次。

三、性能优化

冷启动优化方式:异步(IntentService)、懒加载(Handler处理)、延迟(IdleHandler,cpu空闲时间加载数据)

1.异步方式:

     a.Thread : 不容易复用,频繁创建及开销大

  b.HandlerThread : 自带消息循环,适用于长时间运行,不断从队列中获取任务

       c.IntentService :  继承Service在内部创建一个HandlerThread ,异步,不占用主线程,优先级较高,不容易被系统kill

       d. AsyncTask : 无需处理线程切换,使用简单

            onPreExecute():异步任务开启之前回调,在主线程中执行

       doInBackground():执行异步任务,在线程池中执行

       onProgressUpdate():在主线程中执行

       onPostExecute():在异步任务执行之后回调,在主线程中执行

        onCancelled():在异步任务被取消时回调

         e.线程池:容易复用,减少频繁创建、销毁时间,  定时、任务队列、并发数控制

2.设置优先级

 Process.setThreadPriority() --------->这个可以设置优先级的高低

3. 采用IntentService启动。它是在异步线程中执行的,用完后它会自动释放对象的,它继承Serice也不容易被杀死。IntentService = Handler+HandlerThread,它的执行是有顺序的

4.BaseClassLoader里面有个Element的数组,里面就是所有的.dex的文件,它需要通过dexElement.findClass遍历整个数组,找到对应的.class才能启动,把我们要先启动的.class放在最前面,这样就可以加快启动速度。

5.SharedPreference的函数讲解

        1).apply和commit的区别        

            apply是没有返回值的,在往磁盘写入时候,它是开启子线程不需要等待返回结果

            commit是会返回boolean的,主线程一直等往磁盘写入成功后,才会去执行

        2).SharedPreference是存储在xml文件中,它线程安全性。

            有缓存机制原因,高并发情况袭多进程是不安全安全

        3).fileobserver多进程安全的原因

            startWatching和stopWatching里面都加了锁,并且锁的对象是修改的文件路径

四、ANR的产生和解决办法

     1、卡顿原理

        产生原因:主线程做耗时操作就会产生卡顿,卡顿超过阈值,触发ANR。

卡顿产生因素:UI层级嵌套过深、Handler处理消息太耗时(barrier--> msg.target == null,同步屏障消息)

      2、卡顿监控

          处理方式:Gradle Plugin+ASM,目前微信的Matrix 使用的卡顿监控方案就是字节码插桩

       3、ANR 原理

           1)、触发ANR的场景

            输入事件分发超时5s,包括按键和触摸事件

            比如前台广播在10s内未执行完成

            servive后台在20s内没有完成

           2)、servive、broadcast原理实现

            a)、Service的onCreate方法被调用时候调用mHandler.sendMessageDelayed发送延迟时间来处理事情

            b)、在任务完成前会调用 :mHandler.removeMessages把消息移除,这样剧不会产生ANR

            c)、任务没有前就会调用mAppErrors.appNotResponding,来告诉产生的原因,弹ANR Dialog

           3)、input原理实现

input来说即便某次事件执行时间超过timeout时长,只要用户后续在没有再生成输入事件,则不会触发ANR

       4、 ANR检测工具

          一般来说有四种,分别为BlockCanay、ANR-WatchDog、SafeLooper和FileObserver

        ANRWatchDog                 

           原理: 开启一个子线程mThreadRunnable,每隔1s会执行一次mThreadRunnable,单独现场向主线程发送一个变量+1操作,休眠过后判断变量是否+1完成,如果未完成则警告

        FileObserver

        Android手机发生ANR后,会把信息存储在/data/anr/traces.txt文件,我们只需要监听这个文件的变化就可以知道是否发生了ANR。

五、APP瘦身优化

    1.删:删除无用的代码和资源

    2.移:实在不行就抽离出,动态加载,动态下发。

    3.压:压缩代码和资源

buildTypes {

        release {

            // 源代码混淆开启

            minifyEnabled true

            // 启动资源压缩

            shrinkResources true

            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

        }

    }

    4.  图片优化 : 除了用PNG格式外,图片可以使用SVG(缩图),在gradle需要配置

        defaultConfig {

........

// 使用support-v7兼容5.0以上

            vectorDrawables.useSupportLibrary = true

.........

        }

SVG官方介绍:https://developer.android.google.cn/studio/write/vector-asset-studio#about

    5. 资源配置打包优化

defaultConfig {

            ........

            // 只保留指定和默认资源

            resConfigs('zh-rCN', 'ko')

// 配置so库架构(真机:arm,模拟器:x86)

 abiFilters('armeabi',armeabi-v7a')

            .........

        }

-----------------------------下面以后还需要再好好总结-----------------------------------------------------

    RecycleView的优化之回收机制

1.一级优化,优先优化item的列表,不需要重新绑定ViewHodler

    里面有两个ArryList的链表,一个是mAttachScrap,另一个是mChangedScrap,当调用notifyItemChanged方法,通知有新的item时候,之前显示保存在mAttachScrap里面会直接复用ViewHolder,数据不需要重新绑定,新加入放在mChangedScrap里面再数据绑定

2.二级和四级优化,Item列表优化缓存,mCacheView可以保存划出列表的item,调用setItemCacheSize方法可以确定缓存多少数据,超出这个数据会放在四级优化的recyclerPool这个缓存池里

OnLowMemory()是Android提供的API,在系统内存不足,所有后台程序(优先级为background的进程,不是指后台运行的进程)都被杀死时,系统会调用OnLowMemory

一般来说有四种,分别为BlockCanay、ANR-WatchDog、SafeLooper和FileObserver

相关文章

网友评论

    本文标题:程序员自我修养之性能优化篇

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