性能优化篇)

Posted 初一十五啊

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了性能优化篇)相关的知识,希望对你有一定的参考价值。

前言

之前更新了第一章,内容实在是太多了。还是编辑好让朋友发的。今天来更新一下第二章,性能优化篇。想看之前的可以看看辞职-旅游-疫情-闭关-复习-大厂offer(第一章)

整个复习稿分为如下几大部分:

1.android基础篇
2.性能优化篇
3.Framework篇
4.compose篇
5.音视频开发篇
6.架构篇
7.Flutter篇
8.kotlin篇

作为一个Android程序员,性能优化是无法避开的事情,并且性能优化也是Android中最有挑战的工作之一,更是每个工程师都需要掌握的核心技能。

性能问题和Bug不同,后者的分析和解决思路更清晰,很多时候从应用日志即可直接找到问题根源,而性能问题,其排查思路更为复杂一些。

对App进行性能优化,是一个系统性的工程,对工程师的技术广度和技术深度都有所要求。一个简单的应用,它不仅包含了应用代码本身,还和虚拟机、存储、网络等紧密相关,线上应用一旦出现了性能问题,需要我们从多方面去考虑。

好了废话不多说直接开始启动今天的更新

一丶启动优化

抖音 Android 性能优化系列:启动优化实践

二丶内存优化

基础知识回顾(看前面文章JVM详解):

jVM内存模型,除了程序计数器以外,别的都会出现 OOM


JAVA对象的生命周期,创建、运行、死亡。 GC对象可回收的判定:可达性分析。GC root(除了堆里的对象,虚拟机栈里的引用、方法区里的引用)。 强软弱虚四种引用。

GC回收算法:复制算法、标记清楚算法、标记整理算法

2.1.App内存组成以及限制

Android给每个App分配一个VM,让App运行在dalvik上,这样即使App崩溃也不会影响到系统。系统给VM分配了一定的内存大小,App可以申请使用的内存大小不能超过此硬性逻辑限制,就算物理内存富余,如果应用超出VM最大内存,就会出现内存溢出crash

由程序控制操作的内存空间在heap上,分java heapsizenative heapsize

  • Java申请的内存在vm heap上,所以如果java申请的内存大小超过VM的逻辑内存限制,就会出现内存溢出的异常。

  • native层内存申请不受其限制,native层受native process对内存大小的限制

总结: app运行在虚拟机上,手机给虚拟机分配内存是固定的,超出就oom。 分配的内存大部分都是在堆上。分为java堆和native层的堆。 java层的堆超过VM给的就oom。理论上native层无限制,但是底层实现native process对内存大小是有限制的。

2.2.查看系统给App分配的内存限制

不同手机给app分配的内存大小其实是不一样大的。

  1. 通过cmd指令 adb shell cat /system/build.prop

2.通过代码activityManager.getMemoryclass();

ActivityManager activityManager =(ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)

activityManager.getMemoryClass();//以m为单位

这些限制其实在 AndroidRuntime.cppstartVm里,我们改不了,手机厂商可以改

int androidRuntime:startvm(javavm** pjavaVm,Jnienv** penv,bool zygote)
   
   /*
    *The default staring and maximum size of the heap. larger
    *valuse should be  specified in a product property ovrride.
     */
       parseRuntimeoption("dalvik.vm.heapstarsize",heapstartsizeoptsbuf,"-xms","4m");
       parseRuntimeoption("dalvik.vm.heapsize",heapsizeoptsbuf,"-xmx","16m")
      
  • 修改platform/dalvik/+eclair-relese/vm/init.c
gDvm.heaosizestart = 2 *1024* 1024;  
  //spec says 16MB;too big for us.
 gDvm.heapsizeMax = 16 *1024* 1024;
  //spec says 75% physical mem

2.3.Android低内存杀进程机制

默认五个级别:空进程、后台进程、服务进程、可见进程、前台进程 所以在保活里有一个做法就是给app提升优先级,给他整成前台、可见这样的。

AMS里有一个oom_adj,会给各个进程进行评分,数字越大越容易被回收,前台进程是0,系统进程是负数,别的是正数

2.4.内存三大问题

1、内存抖动 profiler -> 内存波动图形呈 锯齿张、GC导致卡顿。 原因是内存碎片很严重。因为android虚拟机的GC算法是标记清楚算法,所以频繁的申请内存、释放内存会让内存碎片化很严重,连续的内存越来越少,让GC非常频繁,导致了内存抖动。 案例:在自定义View onDraw()new对象

2、内存泄漏 在当前应用周期内不再使用的对象被GC Roots引用,导致不能回收,使实际可使用内存变小。内存泄露如果越来越严重的话,最终会导致OOM。 案例:context被长生命周期的东西引用。没有释放listener

3、内存溢出OOMOOM时会导致程序异常。Android设备出厂以后,java虚拟机对单个应用的最大内存分配就确定下来了,超出这个值就会OOM。 案例:加载大图、内存泄露、内存抖动

除了程序计数器以外 别的JVM部分都会OOM

2.5.常见分析工具

  1. Memory Analyzer ToolsMAT

MATEclipse下的内存分析工具。 用法:用cmd指令或者android studio 自带的profiler 截取一段数据,然后下载下来。得到一个.hprof

![](https://upload-images.jianshu.io/upload_images/15706246-f4367289cac94474.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 

然后用AMT打开,就能看预测泄露地方,有一个图表,基本没用。还有看哪个线程调用这个对象、深浅堆等等。挺难用的。

2.android studio自带的profiler

谷歌官网:developer.android.google.cn/studio/prof…

官网超级详细,还有视频,直接看官网的就行。

选中一段区域,就能查看这段区域内存被具体哪个对象用了多少等等。 我感觉还是用来看大势的,大的内存上涨、下落、起伏图。

3.LeakCanary 超级推荐LeakCanary!!!永远滴神 具体内存泄露的检测,细节还得用LeakCanary

集成: build.gradle里添,跟集成第三方一样。 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'

然后直接跑APP,跑完以后一顿点,点完以后会给推送。写了发现几个对象有泄露。点一下推送蓝会自动下载下来。

比如这就是我的问题,期初我认为是activitycontext没有释放,其实是在dialog里使用了动画animation,但是动画的listener没有释放。

2.6.Android内存泄漏常见场景以及解决方案

1、资源性对象未关闭
对于资源性对象不再使用时,应该立即调用它的close()函数,将其关闭,然后再置为null。例如Bitmap 等资源未关闭会造成内存泄漏,此时我们应该在Activity销毁时及时关闭。

2、注册对象未注销
例如BraodcastReceiverEventBus未注销造成的内存泄漏,我们应该在Activity销毁时及时注销。

3、类的静态变量持有大数据对象
尽量避免使用静态变量存储数据,特别是大数据对象,建议使用数据库存储。

4.单例造成的内存泄漏
优先使用ApplicationContext,如需使用ActivityContext,可以在传入Context时使用弱引用进行封 装,然后,在使用到的地方从弱引用中获取Context,如果获取不到,则直接return即可。

5、非静态内部类的静态实例
该实例的生命周期和应用一样长,这就导致该静态实例一直持有该Activity的引用,Activity的内存资源 不能正常回收。此时,我们可以将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如 果需要使用Context,尽量使用Application Context,如果需要使用Activity Context,就记得用完后置 空让GC可以回收,否则还是会内存泄漏。

6、Handler临时性内存泄漏
Message发出之后存储在MessageQueue中,在Message中存在一个target,它是Handler的一个引用,MessageQueue中存在的时间过长,就会导致Handler无法被回收。如果Handler是非静态的, 则会导致Activity或者Service不会被回收。并且消息队列是在一个Looper线程中不断地轮询处理消息, 当这个Activity退出时,消息队列中还有未处理的消息或者正在处理的消息,并且消息队列中的Message 持有Handler实例的引用,Handler又持有Activity的引用,所以导致该Activity的内存资源无法及时回 收,引发内存泄漏。解决方案如下所示:
1、使用一个静态Handler内部类,然后对Handler持有的对象(一般是Activity)使用弱引用,这 样在回收时,也可以回收Handler持有的对象。
2、在ActivityDestroy或者Stop时,应该移除消息队列中的消息,避免Looper线程的消息队列中 有待处理的消息需要处理
Handler那篇有讲)

7、容器中的对象没清理造成的内存泄漏
在退出程序之前,将集合里的东西clear,然后置为null,再退出程序

8、WebView
WebView都存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。我们可以为 WebView开启一个独立的进程,使用AIDL与应用的主进程进行通信,WebView所在的进程可以根据业 务的需要选择合适的时机进行销毁,达到正常释放内存的目的。

9、使用ListView时造成的内存泄漏
在构造Adapter时,使用缓存的convertView

10、Bitmap
80%的内存泄露都是Bitmap造成的,BitmapRecycle()方法,不用时要及时回收。但是如果遇到要用Bitmap直接用Glide就完事了,自己写10有8.9得出错。

三丶启动优化速度

3.1.app启动流程

①点击桌面App图标,Launcher进程采用Binder IPCAMS进程发起startActivity请求;

AMS接收到请求后,向zygote进程发送创建进程的请求;

Zygote进程fork出新的子进程,即App进程;

App进程,通过Binder IPCAMS进程发起attachApplication请求;

AMS进程在收到请求后,进行一系列准备工作后,再通过binder IPCApp进程发送scheduleLaunchActivity请求;

App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;

⑦主线程在收到Message后,通过反射机制创建目标Activity,并回调Activity.onCreate()等方法。

⑧到此,App便正式启动,开始进入Activity生命周期,执行完onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。

3.2.启动状态

APP启动状态分为:冷启动、热启动、温启动。
冷启动:什么都没有,点击桌面图标,启动AppZygote fork进程…
热启动:app挂在后台,在点击图标切换回来。
温启动:两者之间。

3.3.启动耗时统计

1.打logdisplayed。有显示

2.cmd命令 adb shell am start -S -W +包名+Activity名

3.4.CPU profile

具体怎么优化呢?得用到Android Studio自带的分析的工具CPU Profile

  1. 打开CPU Profile,trace java Methods

2.跑项目,截开始的那段cpu运行图,得到启动的数据

3.得到具体哪个方法执行时间的情况图

Call Chart:黄、蓝、绿三色。黄色=系统、绿色=自己的、蓝色=第三方。自上而下表示调用顺序。越长就说明执行的时间越长。

如:我发现极光的初始化时间还是挺长的,如果要优化可以将极光延迟初始化。

还有onCreat占用时间也挺长,主要在setContentView里,看看能不能将布局优化一下。

Flame Chart:跟Call Chart差不多,就是反过来的图,又称火焰图

Top Down Tree:这个就不是图标了,是方法直接的调用关系,每个方法的调用时间。一直往下点,可以找到占用时间较长的,可以优化的地方。从上往下的调用。

Bottom Up Tree:跟top反向从下往上。

3.5.启动白屏

在主题里配置一个背景。

3.6.StrictMode严苛模式

可以在application里配置严苛模式,这样不规范的操作就会有log提示,或者闪退。

3.7.启动优化的点

1). 合理的使用异步初始化、延迟初始化、懒加载机制。
2). 启动过程避免耗时操作,如数据库 I/O操作不要放在主线程执行。
3). 类加载优化:提前异步执行类加载。
4). 合理使用IdleHandler进行延迟初始化。
5). 简化布局

四丶卡顿优化

4.1.分析工具

  1. SystraceAndroid提供的一款工具,需要运行在Python环境下。

Systrace主要是分析掉帧的情况的。帧:android手机一般都是60帧,所以1帧的时间=1000毫秒/60=16.6毫秒。也就是android手机16.6毫秒刷新一次,超过这个数就是掉帧了

会有三色球,绿=正常,黄=一点不正常,红=掉帧严重。 少几个没啥事,大面积的出现红、黄,就需要研究为啥掉帧了。

可以看上面有一条CPU的横轴,绿色=正在执行,蓝色=等待,可以运行。紫色=休眠。白色=休眠阻塞。如果是出现了紫色就说明IO等耗时操作导致掉帧。如果紫+蓝比较多,说明cpu拿不到时间片,cpu很忙。

4.2.CPU Profile

这个就能看那个方法运行多少时间等,所以可以直接用android studio自带的分析。

一般是在top down里一直点,耗时较多的,然后点到自己熟悉的地方,挨个分析。 这是一个漫长的耗时的过程,可能找半天只找到几个地方能优化,然后每个几毫秒,加起来也没有直观的变快,但是性能优化就是这样的一个过程,积少成多。 如:我经过查找就发现adapternotifyDataSetChanged因为不小心,有些地方多次调用了。 甚至还有没有在线程进行io操作。

五丶布局优化

经过上面的一顿操作,发现占时间大块的少不了setContentView。说明布局渲染视图还是挺费时的。

5.1.减少层级

自定义Viewmeasurelayoutdraw这三个过程,都需要对整个视图树自顶向下的遍历,而且很多情况都会多次触发整个遍历过程(Linearlayout weight等),所以如果层级太深,就会让整个绘制过程变慢,从而造成启动速度慢、卡顿等问题。 而onDraw在频繁刷新时可能多次触发,因此 onDraw更不能做耗时操作,同时需要注意内存抖动。对于布局性能的检测,依然可以使用systracetraceview按 照绘制流程检查绘制耗时函数。

工具Layout Inspector DecorView开始。content往下才是自己写的布局。

重复的布局使用include。 一个布局+到另一个上,如果加上以后,有两一样的ViewGroup,可以把被加的顶层控件的ViewGroup换成merge ViewStub:失败提示框等。需要展示的时候才创建,放在那不占位置。

5.2.过度渲染

一块区域内(一个像素),如果被绘制了好几次,就是过度渲染。 过度绘制不可避免,但是应该尽量的减少。 手机->开发者模式->GPU 过度绘制 打开以后能看到不同颜色的块,越红就说明过度绘制的越严重。对于严重的地方得减少过度绘制。

1.移除布局中不需要的背景。
2.降低透明度。
3.使视图层次结构扁平化。

5.3.布局加载优化

异步加载布局,视情况而用。

new AsyncLayoutInflater(this)
.inflate(R.layout.activity_main, null, new AsyncLayoutInflater.OnInflateFinishedListener() 
@Override
public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) 
setContentView(view);
//......

);

六丶崩溃优化

前言

开发人员碰到 APP 崩溃(闪退)什么办?不少人会说根据 Log,找到闪退的代码,捕获异常,“消化” 掉了所有 Java 崩溃。至于程序是否会出现其他异常表现,那是上帝要管的事情。是的,这种方法对于紧急情况下不失为一种解决办法,但闪退的真相是什么?是否从根源上解决问题呢?

6.1.崩溃

崩溃率是衡量一个应用质量高低的基本指标,那么,该怎样客观地衡量崩溃这个指标,以及又该如何看待和崩溃相关的稳定性。

Android 的两种崩溃:

  • Java 崩溃
  • Native 崩溃

简单来说,Java 崩溃就是在 Java 代码中,出现了未捕获异常,导致程序异常退出。那 Native 崩溃一般都是因为在 Native 代码中访问非法地址,也可能是地址对齐出现了问题,或者发生了程序主动 Abort,这些都会产生相应的 Signal 信号,导致程序异常退出。

6.1.1 崩溃的收集

“崩溃” 就是程序出现异常,而一个产品的崩溃率,跟我们如何捕获、处理这些异常有比较大的关系。对于很多中小型公司来说,可以选择一些第三方的服务。目前各种平台也是百花齐放,包括阿里的友盟、腾讯的 Bugly、网易云捕、GoogleFirebase 等等。要懂得借力!

6.1.2 ANR

崩溃率是不是就能完全等价于应用的稳定性呢?答案是肯定不行。处理了崩溃,我们还会经常遇到 ANRApplication Not Responding,程序没有响应)这个问题。

出现 ANR 的时候,系统还会弹出对话框打断用户的操作,这是用户非常不能忍受的。

ANR 处理方法:
使用 FileObserver 监听 /data/anr/traces.txt 的变化。非常不幸的是,很多高版本的 ROM,已经没有读取这个文件的权限了。这个时候你可能只能思考其他路径,海外可以使用 Google Play 服务,而国内微信利用 Hardcoder 框架(HC 框架是一套独立于安卓系统实现的通信框架,它让 App 和厂商 ROM 能够实时 “对话” 了,目标就是充分调度系统资源来提升 App 的运行速度和画质,切实提高大家的手机使用体验)向厂商获取了更大的权限。也可以将手机 ROOT 掉,然后取得 traces.txt 文件。

6.1.3.应用退出

除了常见的崩溃,还有一些会导致应用异常退出的情况,例如:

  • 主动自杀。Process.killProcess ()exit ()
  • 崩溃。出现了 JavaNative 崩溃
  • 系统重启。系统出现异常、断电、用户主动重启等,我们可以通过比较应用开机运行时间是否比之前记录的值更小
  • 被系统杀死。被 low memory killer 杀掉、从系统的任务管理器中划掉等
  • ANR

我们可以在应用启动的时候设定一个标志,在主动自杀或崩溃后更新标志,这样下次启动时通过检测这个标志就能确认运行期间是否发生过异常退出。对应上面的五种退出场景,我们排除掉主动自杀和崩溃(崩溃会单独的统计)这两种场景,希望可以监控到剩下三种的异常退出,理论上这个异常捕获机制是可以达到 100% 覆盖的。

通过这个异常退出的检测,可以反映如 ANRlow memory killer、系统强杀、死机、断电等其他无法正常捕获到的问题。当然异常率会存在一些误报,比如用户从系统的任务管理器中划掉应用。对于线上的大数据来说,还是可以帮助我们发现代码中的一些隐藏问题。

根据应用的前后台状态,我们可以把异常退出分为前台异常退出和后台异常退出。“被系统杀死” 是后台异常退出的主要原因,当然我们会更关注前台的异常退出的情况,这会跟 ANROOM 等异常情况有更大的关联。

6.2.崩溃处理

我们每天工作也会遇到各种各样的疑难问题,“崩溃” 就是其中比较常见的一种问题。解决问题跟破案一样需要经验,我们分析的问题越多越熟练,定位问题就会越快越准。当然这里也有很多套路,比如对于 “案发现场” 我们应该留意哪些信息?怎样找到更多的 “证人” 和 “线索” ? “侦查案件” 的一般流程是什么?对不同类型的 “案件” 分别应该使用什么样的调查方式?

要相信 “真相永远只有一个”,崩溃也并不可怕。

6.2.1.崩溃现场

崩溃现场是我们的 “第一案发现场”,它保留着很多有价值的线索。现在可以挖掘到的信息越多,下一步分析的方向就越清晰,而不是去靠盲目猜测。

崩溃信息

从崩溃的基本信息,我们可以对崩溃有初步的判断。进程名、线程名。崩溃的进程是前台进程还是后台进程,崩溃是不是发生在 UI 线程。

崩溃堆栈和类型。崩溃是属于 Java 崩溃、Native 崩溃,还是 ANR,对于不同类型的崩溃关注的点也不太一样。特别需要看崩溃堆栈的栈顶,看具体崩溃在系统的代码,还是 APP 代码里面。

关键字:FATAL

FATAL EXCEPTION: main
 Process: com.cchip.csmart, PID: 27456
 java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(int)' on a null object reference
    at com.cchip.alicsmart.activity.SplashActivity$1.handleMessage(SplashActivity.java:67)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at android.os.Looper.loop(Looper.java:179)
    at android.app.ActivityThread.main(ActivityThread.java:5672)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:674)

系统信息

系统的信息有时候会带有一些关键的线索,对我们解决问题有非常大的帮助。

Logcat。这里包括应用、系统的运行日志。由于系统权限问题,获取到的 Logcat 可能只包含与当前 APP 相关的。其中系统的 event logcat 会记录 APP 运行的一些基本情况,记录在文件/system/etc/event-log-tags中。

//system logcat:
10-25 17:13:47.788 21430 21430 D dalvikvm: Trying to load lib ... 

//event logcat:
10-25 17:13:47.788 21430 21430 I am_on_resume_called: 生命周期
10-25 17:13:47.788 21430 21430 I am_low_memory: 系统内存不足
10-25 17:13:47.788 21430 21430 I am_destroy_activity: 销毁 Activty
10-25 17:13:47.888 21430 21430 I am_anr: ANR 以及原因
10-25 17:13:47.888 21430 21430 I am_kill: APP 被杀以及原因

机型、系统、厂商、CPU、ABI、Linux 版本等。通过采集多达几十个维度,这对寻找共性问题会很有帮助。

内存信息

OOMANR、虚拟内存耗尽等,很多崩溃都跟内存有直接关系。如果把用户的手机内存分为 “2GB 以下” 和 “2GB 以上” 两个区,就会发现 “2GB 以下” 用户的崩溃率是 “2GB 以上” 用户的几倍。

系统剩余内存。关于系统内存状态,可以直接读取文件 /proc/meminfo。当系统可用内存很小(低于 MemTotal 的 10%)时,OOM、大量 GC、系统频繁自杀拉起等问题都非常容易出现。

应用使用内存。包括 Java 内存、RSSResident Set Size)、PSSProportional Set Size),我们可以得出应用本身内存的占用大小和分布。PSSRSS 通过 /proc/self/smap 计算,可以进一步得到例如 apkdexso 等更加详细的分类统计。

虚拟内存。虚拟内存可以通过 /proc/self/status 得到,通过 /proc/self/maps 文件可以得到具体的分布情况。有时候我们一般不太重视虚拟内存,但是很多类似 OOMtgkill 等问题都是虚拟内存不足导致的。

一般来说,对于 32 位进程,如果是 32 位的 CPU,虚拟内存达到 3GB 就可能会引起内存申请失败的问题。如果是 64 位的 CPU,虚拟内存一般在 3~4GB 之间。当然如果我们支持 64 位进程,虚拟内存就不会成为问题。Google Play 要求 2019 年 8 月一定要支持 64 位,在国内虽然支持 64 位的设备已经在 90% 以上了,但是商店都不支持区分 CPU 架构类型发布,普及起来需要更长的时间。

资源信息

有的时候会发现应用堆内存和设备内存都非常充足,还是会出现内存分配失败的情况,这跟资源泄漏可能有比较大的关系。

文件句柄 fd。文件句柄的限制可以通过 /proc/self/limits 获得,一般单个进程允许打开的最大文件句柄个数为 1024。但是如果文件句柄超过 800 个就比较危险,需要将所有的 fd 以及对应的文件名输出到日志中,进一步排查是否出现了有文件或者线程的泄漏。

线程数。当前线程数大小可以通过上面的 status 文件得到,一个线程可能就占 2MB 的虚拟内存,过多的线程会对虚拟内存和文件句柄带来压力。根据我的经验来说,如果线程数超过 400 个就比较危险。需要将所有的线程 id 以及对应的线程名输出到日志中,进一步排查是否出现了线程相关的问题。

threads count 412:
 1820 com.xmamiga.crashsdk
 1844 ReferenceQueueD
 1869 FinalizerDaemon
 ...

JNI。使用 JNI 时,如果不注意很容易出现引用失效、引用爆表等一些崩溃。

应用信息

除了系统,其实我们的应用更懂自己,可以留下很多相关的信息。崩溃场景。崩溃发生在哪个 ActivityFragment,发生在哪个业务中;关键操作路径,不同于开发过程详细的打点日志,我们可以记录关键的用户操作路径,这对我们复现崩溃会有比较大的帮助。其他自定义信息。不同的应用关心的重点可能不太一样。

6.2.2.崩溃分析

有了这么多现场信息之后,就可以开始真正的 “破案” 之旅了。绝大部分的 “案件” 只要肯花功夫,最后都能真相大白。不要畏惧问题,经过耐心和细心地分析,总能敏锐地发现一些异常或关键点,并且还要敢于怀疑和验证。

第一步:确定重点

确认和分析重点,关键在于终过日志中找到重要的信息,对问题有一个大致判断。一般来说,我建议在确定重点这一步可以关注以下几点。

  • 确认严重程度。解决崩溃也要看性价比,我们优先解决 Top 崩溃或者对业务有重大影响,例如主要功能的崩溃。不要花几天去解决了一个边角的崩溃,有可能下个版本就把功能删除了。
  • 崩溃基本信息。确定崩溃的类型以及异常描述,对崩溃有大致的判断。
    一般来说,大部分的简单崩溃经过这一步已经可以得到结论。

Java 崩溃。Java 崩溃类型比较明显,比如 NullPointerException 是空指针,OutOfMemoryError 是资源不足,这个时候需要去进一步查看日志中的 “内存信息” 和 “资源信息”。

Native 崩溃。需要观察 signalcodefault addr 等内容,以及崩溃时 Java 的堆栈。关于各 signal 含义的介绍,你可以查看崩溃信号介绍。比较常见的是有 SIGSEGVSIGABRT,前者一般是由于空指针、非法指针造成,后者主要因为 ANR 和调用 abort () 退出所导致。

ANR。先看看主线程的堆栈,是否是因为锁等待导致。接着看看 ANR 日志中 iowaitCPUGCsystem server 等信息,进一步确定是 I/O 问题,或是 CPU 竞争问题,还是由于大量 GC 导致卡死。

第二步:查找共性

如果使用了上面的方法还是不能有效定位问题,我们可以尝试查找这类崩溃有没有什么共性。找到了共性,也就可以进一步找到差异,离解决问题也就更进一步。

机型、系统、ROM、厂商、ABI,这些采集到的系统信息都可以作为维度聚合,共性问题例如是不是只出现在 x86 的手机,是不是只有三星这款机型,是不是只在 Android 8.0 的系统上。应用信息也可以作为维度来聚合,比如正在打开的链接、正在播放的视频、国家、地区等。

找到了共性,可以对你下一步复现问题有更明确的指引。

第三步:尝试复现

如果我们已经大概知道了崩溃的原因,为了进一步确认更多信息,就需要尝试复现崩溃。如果我们对崩溃完全没有头绪,也希望通过用户操作路径来尝试重现,然后再去分析崩溃原因。

“只要能本地复现,我就能解”,相信这是很多开发跟测试说过的话。有这样的底气主要是因为在稳定的复现路径上面,我们可以采用增加日志或使用 DebuggerGDB 等各种各样的手段或工具做进一步分析。

我们可能会遇到了各种各样的奇葩问题。比如某个厂商改了底层实现、新的 Android 系统实现有所更改,都需要去 Google、翻源码,有时候还需要去抠厂商的 ROM 或手动刷 ROM。很多疑难问题需要我们耐得住寂寞,反复猜测、反复发灰度、反复验证。–但这种问题还是要看问题的严重程序,不可捡了芝麻丢了西瓜。

6.2.3.系统崩溃

系统崩溃常常令我们感到非常无助,它可能是某个 Android 版本的 Bug,也可能是某个厂商修改 ROM 导致。这种情况下的崩溃堆栈可能完全没有我们自己的代码,很难直接定位问题。能做的有:

  • 查找可能的原因。通过上面的共性归类,我们先看看是某个系统版本的问题,还是某个厂商特定 ROM 的问题。虽然崩溃日志可能没有我们自己的代码,但通过操作路径和日志,可以找到一些怀疑的点。
  • 尝试规避。查看可疑的代码调用,是否使用了不恰当的 API,是否可以更换其他的实现方式规避。
  • Hook 解决。这里分为 Java HookNative Hook。它可能只出现在 Android 7.0 的系统中,参考 Android 8.0 的做法,直接 catch 住这个异常。
    如果做到了上面说的这些,以上大部分的崩溃应该都能解决或者规避,大部分的系统崩溃也是如此。当然总有一些疑难问题需要依赖到用户的真实环境,这些需要具备类似动态跟踪和调试的能力。

6.3.总结

崩溃攻防是一个长期的过程,我们尽可能地提前预防崩溃的发生,将它消灭在萌芽阶段。作为技术人员,我们不应该盲目追求崩溃率这一个数字,应该以用户体验为先,如果强行去掩盖一些问题往往更加适得其反。我们不应该随意使用 try catch 去隐藏真正的问题,要从源头入手,了解崩溃的本质原因,保证后面的运行流程。在解决崩溃的过程,也要做到由点到面,不能只针对这个崩溃去解决,而应该要考虑这一类崩溃怎么解决和预防。

七丶启动源码深度剖析

Android应用启动全流程分析(源码深度剖析)

篇幅过长,一时半会更新不完

从事互联网开发,最主要的是要学好技术
而学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的
必须养成平时努力学习的习惯,更加需要准确的学习方向达到有效的学习效果

希望大家都能够拿到心仪的offer,一起升职加薪😊!

以上是关于性能优化篇)的主要内容,如果未能解决你的问题,请参考以下文章

.NET遗留应用改造——性能优化篇

分布式技术专题「系统服务优化系列」Web应用服务的性能指标优化开发指南(基础篇)

vue-router和webpack懒加载,页面性能优化篇

Django性能提升篇

性能优化

uniapp 性能优化篇