0
点赞
收藏
分享

微信扫一扫

Android性能优化,这一篇有你想要的所有答案!,熬夜整理Android高频面试题

我阿霆哥 2022-03-20 阅读 59

复制代码

各个平台的差别如下:

| 平台 | 说明 |

| --- | --- |

| armeabi-v7a | arm 第 7 代及以上的处理器,2011 年后的设备基本都是 |

| arm64-v8a | arm 第 8 代 64 位处理器设备 |

| armeabi | arm 第 5、6 代处理器,早期的机器都是这个平台 |

| x86 | x86 32 位平台,平板和模拟器用的多 |

| x86_64 | x86 64 位平台 |

[]( )如何减少代码资源大小

  1. 一个功能尽量用一个库

比如加载图片库,不要 glide 和 fresco 混用,因为功能是类似的,只是使用的方法不一样,用了多个库来做类似的事情,代码肯定就变多了。

  1. 混淆

混淆的话,减少了生成的 class 大小,这样积少成多,也可以从一定层度减少 apk 的大小。

  1. R 文件内联

通过把 R 文件里面的资源内联到代码中,从而减少 R 文件的大小。

可以使用 [shrink-r-plugin]( ) 工具来做 R 文件的内联

[]( )参考文档

[Android App包瘦身优化实践]( )

[]( )启动速度

====================================================================

[]( )启动的类型

一般分为,冷启动和热启动

冷启动:启动时,后台没有任何该应用的进程,系统需要重新创建一个进程,并结合启动参数启动该应用。

热启动:启动时,系统已经有该应用的进程(比如按 home 键临时退出该应用)下启动该应用。

[]( )如何获取启动时间

  1. adb 命令

adb shell am start -S -W 包名/启动类的全名

adb shell am start -S -W xxx/xxxActivity

Stopping: xxx

Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=xxx/xxxActivity }

Status: ok

Activity: xxx/xxxActivity

ThisTime: 770

TotalTime: 770

WaitTime: 848

Complete

复制代码

ThisTime: 表示最后一个 Activity 启动时间

TotalTime: 表示启动过程中,所有的 Activity 的启动时间

WaitTime: 表示应用进程的创建时间 + TotalTime

一般我们关注 TotalTime 就好了。

另外,谷歌在 Android4.4(API 19)上也提供了测量方法,在 logcat 中过滤 Displayed 字段也可以看到启动时间

2021-04-06 19:25:52.803 2210-2245 I/ActivityManager: Displayed xxx/xxxActivity: +623ms

+623ms 就是Activity 的启动时间。

  1. 时间戳

时间戳的方法基于以下的 2 个知识点。

  • 应用进程刚创建,会调用 Application 的 onCreate 方法。

  • 首次进入一个 Activity 后会在 onResume() 方法后面调用 onWindowsFocusChange 方法。

结合这 2 个特性,我们可以在 A Application 的 onCreate() 方法和 Activity 的 onWindowsFocusChange 方法里面,通过时间戳来获取应用的冷启动时间。

[]( )如何监控启动过程

  1. systrace

systrace 是一个功能很强大的工具,除了可以查看卡顿问题,也可以用来查看应用的启动问题。使用示例如下:

python $ANDROID_HOME/platform-tools/systrace/systrace.py gfx view wm am pm ss dalvik app sched -b 90960 -a 你的包名 -o test.log.html

用 Google 浏览器打开 test.log.html 就可以看到详细的启动信息。

  1. Debug 接口

package android.os;

...

class Debug {

...

public static void startMethodTracingSampling(String tracePath, int bufferSize, int intervalUs) {

}

public static void startMethodTracing(String tracePath, int bufferSize) {

}

}

复制代码

利用 Debug 类的这两个方法,可以生成一个 trace 文件,这个 trace 文件,可以直接在 AS 里面打开,可以看到从 startMethodTracingSamplingstartMethodTracing 过程中的方法调用等信息,也可以较好的分析启动问题。

[]( )一般有那些优化方法

  1. 耗时操作放到异步进程

比如文件解压、读写等耗时 IO 操作可以新开一个线程来执行。

  1. 延时初始化

即暂时不适用的工具类等延后到使用的时候再去初始化。比如从 xml 里面读取颜色,可以考虑在使用的时候再去读取和解析。

  1. 线程优化

线程的创建需要消耗较多的系统系统资源,减少线程的创建。可以考虑共用一个线程池。

如何检测线程的创建,可以参考我个开源库 [performance]( )

[]( )稳定性优化

=====================================================================

[]( )APP 稳定性的维度

app 稳定一般指的是 app 能正常运行, app 不能正常运行的情况分为两大类,分别是 CrashANR

Crash:运行过程中发生的错误,是无法避免的。

ANR:应用再运行时,由于无法再规定的时间段内响应完,系统做出的一个操作。

[]( )如何治理 Crash

应用发生 Crash 是由于应用在运行时,应用产生了一个未处理的异常(就是没有被 try catch 捕获的异常)。这会导致 app 无法正常运行。

如果需要解决的话,就需要知道这个未处理的异常是在哪里产生的,一般是通过分析未处理的异常的方法调用堆栈来解决问题。

Android APP 可以分为 2 层,Java 层和 Native 层。所以如何捕获需要分开说。

[]( )Java 层获取未处理的异常的调用堆栈

这个需要了解 Java 虚拟机是如何把一个未捕获的异常报上来的。

未捕获的异常,会沿着方法的调用链依次上抛,直到 ThreadGroupuncaughtException 方法

public void uncaughtException(Thread t, Throwable e) {

if (parent != null) {

// 递归调用,可以忽略

parent.uncaughtException(t, e);

} else {

// 交给了 Thread.getDefaultUncaughtExceptionHandler() 来处理未捕获的异常

Thread.UncaughtExceptionHandler ueh =

Thread.getDefaultUncaughtExceptionHandler();

if (ueh != null) {

ueh.uncaughtException(t, e);

} else if (!(e instanceof ThreadDeath)) {

System.err.print("Exception in thread \""

  • t.getName() + "\" ");

e.printStackTrace(System.err);

}

}

}

复制代码

查阅代码发现,发现 ThreadGroup 最终会给 ThreaddefaultUncaughtExceptionHandler 处理。

private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

复制代码

上面的代码显示:ThreaddefaultUncaughtExceptionHandlerThread 类的一个静态变量。

看到这里,如何捕获 Java 层未处理的异常就很清晰了,给 Thread 设置一个新的 defaultUncaughtExceptionHandler,在这个新的 defaultUncaughtExceptionHandler 里面收集需要的信息就可以了。

需要注意的一点是 旧的 defaultUncaughtExceptionHandler 需要保存下来,然后新的 defaultUncaughtExceptionHandler 收集信息后,需要再转给旧的 defaultUncaughtExceptionHandler 继续处理。

[]( )Native 层获取未处理的异常的相关信息

Java 层如何收集未处理的异常的信息说过了,我们来看看 Native 层发生未处理的异常的话,是如何处理的。 Native 层的处理,需要掌握 linux 的一些知识,由于本人不是特别了解 linux ,这里就直接参考别人的文章了。如果有错误,还请指正。

本人通过查阅资料发现,Native 层如果发生未处理的异常(注:如果 Native 层捕获了异常,是可以通过 JNI 抛到 Java 层去处理的) ,系统会发出信号给 Native 层,在 Native 层如果要收集未处理的异常信息,就需要注册对应信号的处理函数。当发生异常的时候,Native 层会收到信息,然后通过处理器来收集信息。

注册信号处理函数如下:

#include <signal.h>

int sigaction(int signum,const struct sigaction act,struct sigaction oldact));

复制代码

  • signum:代表信号编码,可以是除SIGKILL及SIGSTOP外的任何一个特定有效的信号,如果为这两个信号定义自己的处理函数,将导致信号安装错误。

  • act:指向结构体sigaction的一个实例的指针,该实例指定了对特定信号的处理,如果设置为空,进程会执行默认处理。

  • oldact:和参数act类似,只不过保存的是原来对相应信号的处理,也可设置为NULL。

有了信号处理函数,后面还要做的事情就是收集信息了,由于本人不是很熟悉 Native 的开发,这里就不展开说了了,大家可以参考 [Android 平台 Native 代码的崩溃捕获机制及实现]( )。

[]( )如何治理 ANR

ANRApplicatipon No Response 的简称。如果应用卡死或者响应过慢,系统就会杀死应用。为什么要杀死应用?其实也很好理解,如果不杀死应用,大家会以为系统坏了。

那我们如何监控 ANR 呢?以及我们如何分析 ANR 的问题呢?常见的导致 ANR 的原因有哪些呢?

首先,ANR 的原理是 AMSUI 操作开始的时候,会根据 UI 操作的类型开启一个延时任务,如果这个任务被触发了,就表示应用卡死或者响应过慢。这个任务会在 UI 操作结束的时候被移除。

然后,如何分析 ANR 问题呢?

一般 ANR 发生的时候, logcat 里面会打印 ANR 相关的信息,过滤关键字 ANR 就可以看到,这里不做详细分析,可以参考后面的文章。

然后一般会在 /data/anr 目录下面生成 traces.txt 文件,里面一般包含了 ANR 发生的时候,系统和所有应用的线程等信息(需要注意的是,不同的 rom 可能都不一样),通过 logcat 打印的信息和 traces.txt 里面的信息,大部分的 ANR 可以分析出原因,但是呢,也有相当一部分的 ANR 问题无法分析,因为 logcattraces.txt 提供的信息有限,有时候甚至没有特别有用的信息,特别是 Android 的权限收紧, traces.txt 文件在高 Android 版本无法读取,给 ANR 问题的分析增加了不少的困难。不过好在最近发现头条给 ANR 写了一个系列的文章,里面对 ANR 问题的治理方法,个人觉得很好,这里引用一下。

  • [今日头条 ANR 优化实践系列 - 设计原理及影响因素]( )

  • [今日头条 ANR 优化实践系列 - 监控工具与分析思路]( )

  • [今日头条 ANR 优化实践系列分享 - 实例剖析集锦]( )

  • [今日头条 ANR 优化实践系列 - Barrier 导致主线程假死]( )

本人之前写过一个小的[性能监测的工具]( ),其中有监控 UI 线程 Block 的功能,考虑后续加入头条的 ANR 监测机制,等后续完成了,在做一个详细的总结吧。这次的总结就写到这里。

[]( )内存的优化

=====================================================================

硬件的内存总是有限的,所有每个应用分到的内存也是有限的,所有内存的优化很有必要,否则应用

Android性能优化,这一篇有你想要的所有答案!,熬夜整理Android高频面试题

就没有足够的内存使用了,这个时候就会 Crash 。

[]( )内存都消耗在哪里了

优化内存的话,需要了解内存在哪里消耗了了,针对内存消耗大的场景做优化,对症下药,才可以有一个好的优化效果。

Android Studio 里面的 Profiler 工具是一个很好用的工具,通过里面的 memory 工具可以实时监控 APP 运行过程中的内存分配。

内存性能分析器

dump APP 内存堆栈后,还可以看到各个类占用的内存情况。

有关每个已分配对象的详细信息显示在右侧的 Instance View 中

可以查看每个对象的详细信息。

Android Studio 里面的 Profiler 工具的具体使用教程请参考[官方教程]( ),这里就不做详细介绍了。

[]( )如何合理使用内存

利用上面的方法,找到内存消耗大的场景,就需要做优化了,主要做法就是想办法减少特定场景下的内存的使用。个人总结了一下平时可能会做的优化。

  • 图片相关的优化

图片是我目前做的应用里面占用内存比较大的一块了,也碰到了一些问题,我主要是通过以下的方法来做优化。

  1. 暂时用不上的图片不加载,比如说,有个网络加载异常的图,不要一开始就初始化,等到真的有异常了需要展示的时候再初始化

  2. 加载图片的时候,尽量加载指定大小的图片,因为有时候会碰到控件的大小小于实际图片尺寸的情况,这个时候,会浪费一些内存。有需要的话,可以让后台返回不同尺寸的图片。

  3. 根据不同的图片格式

  4. 不显示的图片,可以考虑先释放。
  • 尽可能少地创建对象

毫无疑问,如果对象少,内存肯定也消耗的少,那平时需要注意哪些呢?

  1. 自定义 view 的时候,不要在 onDraw 方法里面频繁创建对象。因为 onDraw 方法可能会频繁调用,这个时候就会创建大量的对象。从而造成浪费,同时也会导致 gc 触发的频率升高,造成卡顿。

  2. 尽量少创建线程,创建线程其实是比较消耗资源的,创建一个空的线程,大概会占用 1-2 M 内存。同时一般异步任务很快就会执行完,如果频繁创建线程来做异步任务,除了内存使用的多,还可能 GC 造成卡顿。执行异步任务的话,一般建议用线程池来执行,但是需要注意线程池的使用。

  3. 尽量用 StringBuilder 或者 StringBuffer 来拼接字符串。平时发现的问题主要是在打印 logcat 的时候和拼接后台返回的数据的时候会创建大量的 String,所以如果有类似的情况也可以考虑做一些优化。

[]( )内存泄漏是什么

内存泄漏指的是本应该释放的内存,由于一些原因,被 GC ROOT 对象持有,从而而无法在 GC 的时候释放,这样可能会导致的一个问题就是,重复操作以后,APP 没有足有的内存使用了,这个时候系统会杀死 APP 。所以内存泄漏是需要排查的。

[]( )如何监控和分析内存泄漏问题

上一个小结总结了上面是内存泄漏,是因为某些 GC ROOT 对象持有了期望释放的对象,导致期望释放的内存无法及时释放。所以如何监控和分析内存泄漏问题就成了如何找到 GC ROOT 的问题。

一般手动分析的步骤是:重复操作怀疑有内存泄漏的场景,然后触发几次 GC 。等几秒钟后,把 APP 的内存堆栈 dump 下来(可以使用 as 的工具 dump),然后用 sdk 里面的 cover 工具转换一下,然后用 MAT 工具来分析内存泄漏的对象到 GC ROOT 的引用链。

手动分析总是很麻烦的,一个好消息是,有一个特别好用的自动监控和分析内存泄漏的工具,这个工具就是 leakcanary ,它可以自动监控并给出内存泄漏的对象到 GC ROOT 的引用链。

使用很简单,只需要在 APP 的 build.gradle 下面新增

dependencies {

// debugImplementation because LeakCanary should only run in debug builds.

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'

}

复制代码

leakcanary 比较核心的一个原理就是利用了弱引用的一个特性,这个特性就是:

在创建弱引用的时候,可以指定一个 RefrenceQueue ,当弱引用引用的对象的可达性发生变化的时候,系统会把这个弱引用引用的对象放到之前指定的 RefrenceQueue 中等待处理。

所以 GC 后,引用对象仍然没有出现在 RefrenceQueue 的时候,说明可能发生了内存泄漏,这个时候 leakcanary 就会 dump 应用的 heap ,然后用 shark 库分析 heap ,找出一个到 GC ROOT 的最短引用链并提示。

[]( )常见的内存泄漏的场景

个人总结了下工作中碰到内存泄漏的一些场景,现记录下来,大家可以参考下。

最后

一个零基础的新人,我认为坚持是最最重要的。我的很多朋友都找我来学习过,我也很用心的教他们,可是不到一个月就坚持不下来了。我认为他们坚持不下来有两点主要原因:

他们打算入行不是因为兴趣,而是因为所谓的IT行业工资高,或者说完全对未来没有任何规划。

刚开始学的时候确实很枯燥,这确实对你是个考验,所以说坚持下来也很不容易,但是如果你有兴趣就不会认为这是累,不会认为这很枯燥,总之还是贵在坚持。

技术提升遇到瓶颈了?缺高级Android进阶视频学习提升自己吗?还有大量大厂面试题为你面试做准备!

提升自己去挑战一下BAT面试难关吧

Android性能优化,这一篇有你想要的所有答案!,熬夜整理Android高频面试题

对于很多Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。

不论遇到什么困难,都不应该成为我们放弃的理由!

如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!

最后祝各位新人都能坚持下来,学有所成。

举报

相关推荐

0 条评论