性能优化之优化 App 启动速度

主要面临的问题:
APP 冷启动白屏。

针对于这个问题,基本上主流的解决方法存在两个:

1. 主题替换。
2. 减少冷启动时主线程的工作量。

本文的重点不在解决问题方法,而在于解决问题的过程,包括影响启动速度的因素、时间统计工具、解决思路。

优化 App 启动速度主要是优化冷启动时的 App 启动速度,官方文档 (App startup time) 中指出优化冷启动方式的同时,温启动和热启动也会得到改善。

在分析过程之前,首先了解启动方式。

冷启动、温启动、热启动

  • 冷启动

    App 安装后第一次启动或者杀死 App 进程后重新启动称为冷启动。冷启动的启动成本较高,一切都要重新建立,包括新建应用进程、初始化应用以及首页的布局、渲染等操作。

  • 热启动

    App 所在进程存活,并且所有 Activity 均未被销毁,热启动做的只是把应用从后台切换至前台,如果 Activity 实例中的被回收的实例变量会重新创建。

    具体场景为在微信中切换到桌面,后马上回到微信。

  • 温启动

    Activity 被杀死但是 App 所在的进程在内存中依然存在。

启动时间组成

很多时候解决问题需要我们拥有上帝视角,全面把控事态的发展,跳出事件本身,以俯视的角度看待整个流程,找出问题的关键点。总的来说我们需要纵观全局来看待问题、解决问题。

从在 Launcher 中点击 App 的 icon 到 App 的第一面展示的时间差称为冷启动时间,那么这段时间整个系统都经历了什么。用上帝之眼看待整个流程,从而寻找解决问题的切入点,首先我们分析一下整个过程都经历了什么。

在 Launcher 中我们点击 App 图标,经过一系列过程,涉及 AMS、 Binder、IPC 等组件和过程。

在启动 App 的主页面之前,系统操作如下:

  1. 创建并启动 App 所在应用
  2. 启动 App 后会马上展示系统的 start window
  3. 创建 App 进程

在系统创建完成 App 所在的进程后,App 进程负责完成下列一系列步骤:

  1. 创建 App 对象
  2. 启动 App 主线程
  3. 创建主 Activity
  4. 加载 View 视图
  5. 布局屏幕
  6. 执行初始化绘制 View

在 App 进程完成第一帧的绘制后,start window 会被替换,此时在屏幕上展示的就是在主 Activity 加载的布局。

App 页面替换

根据 Colt McAnlis 在 Yutobe 上的关于 App 启动时间的描述,以上过程可以形象的图形化表示如下:

App 启动时间

为什么是白屏或黑屏

在清单文件中我们需要设置 Application 的 Theme,比如自己设置如下:

1
2
3
<application
...
android:theme="@style/sixcatTheme">

我们需要查看 sixcatTheme 主题的继承关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1. <style name="sixcatTheme" parent="Theme.AppCompat.Light.NoActionBar">

2. <style name="Theme.AppCompat.Light" parent="Base.Theme.AppCompat.Light"/>

3. <style name="Base.Theme.AppCompat.Light" parent="Base.V7.Theme.AppCompat.Light">

4. <style name="Base.V7.Theme.AppCompat.Light" parent="Platform.AppCompat.Light">

5. <style name="Platform.AppCompat.Light" parent="android:Theme.Holo.Light">

6. <style name="Theme.Holo.Light" parent="Theme.Light">


7. <style name="Theme.Light">
<item name="windowBackground">@drawable/screen_background_selector_light</item>
</style>

8. <style name="Theme">

层级自 1~8 自上而下具有继承关系。

可以看到在第 7 层中存在 windowBackground 属性,这是一路下来与背景唯一相关属性。

看一下 windowBackground 的 drawable 显示如何:

drawable

事实证明确实是这个属性决定着点击 App 后显示的白屏或黑屏,当然这也为解决此问题提供了一个思路。

官方图

拆解为两部分。

分析、找出优化点

启动时间统计

解决方案

更换主题

截图 + 解决方法 + 截图

延时加载

CPU profile
trace 文件
命令行
Displayed Time

方法一

1
<item name="android:windowIsTranslucent">true</item>

在原来的 MainActivity 设定的主题中添加以上代码。

带来的效果:
透明效果,即点击 App icon 后的白屏时间变为 透明 状态,避免了白屏,但是两者的时间几乎相同。给人的错觉时点击 icon 后一段时间后,app 才调起,给人的印象不好。

方法二 主题替换

在 App 启动时


App 启动时间统计

1
adb shell am start -W package/activity

Google Developer

Youtube 性能优化视频

https://www.zhihu.com/question/35487841

Binder基本原理

Binder 机制是 Android 特有的跨进程通信机制,本文为 Android 插件化开发 相关章节的阅读笔记。

00x01

Binder 分为 Client 端和 Server 端,在进行跨进程通信时,两端分别为两个不同的进程。发消息者为 Client,相应的接收消息者为 Server。根据消息的流向,一端可以同时为 Client 和 Server。

00x02

Binder 组成:

  • Client
  • Server
  • ServerManager(管理 Server )

Read More

Java 集合工具类--Collections

HashSet、TreeSet、ArrayList、ArrayDeque、LinkedList、HashMap 和 TreeMap 都是线程不安全的, 如果多个线程对用一个集合对象进行存、取、删等操作,不免会产生 线程同步问题

Java 提供了 Collections 工具类,使用 synchronizedxxx() 方法可以将集合类包装成线程安全的集合。

1
2
3
4
5
6
7
Collections collections = Collections.synchronizedCollection(new ArrayList());

List list = Collections.synchronizedList(new ArrayList());

Set set = Collections.synchronizedSet(new HashSet());

Map map = Collections.synchronizedMap(new HashMap());

同时 Collections 还提供了排序、查找、替换、设置不可变集合等功能,有空自己看 API。

Shell 基础学习三--正则表达式一

基本正则表达式

  1. 正则表达式与通配符

正则表达式用来在 文件中 匹配符合条件的 字符串,正则是 包含匹配。grep、awk、 sed等命令可以支持正则表达式。

通配符用来匹配 系统 中 符合条件的 文件名,通配符是 完全匹配。ls、find、cp 这些命令不支持正则表达式,所以只能使用shell自己 的通配符来进行匹配了。

  • ? []

* :前一个字符匹配 0 次或任意多次

注意是前一个字符。

grep “a*” file

匹配所有内容,包括空白行,所以没有任何意义。

grep “aa*” file

至少有一个 a 的字符串。

“.”: 匹配除换行符的任意一个字符

grep “s..d” test_rule.txt

“s..d”会匹配在s和d这两个字母之间一定有两个字符的单词

grep “s.*d” test_rule.txt

匹配在s和d字母之间有任意字符。

在正则表达式中 “.*” 代表任意字符。

grep “.*” test_rule.txt

匹配所有内容

”^“: 匹配行首,”$”: 匹配行尾。

grep “^M” test_rule.txt

匹配以大写“M”开头的行

grep “n$” test_rule.txt

匹配以小写“n”结尾的行

grep -n “^$” test_rule.txt
会匹配空白行

“[]”:匹配中括号中的任意一个字符,注意是只匹配一个

grep “s[ao]id” test_rule.txt

匹配s和i字母中,要不是a、要不是o

grep “[0-9]” test_rule.txt

匹配任意一个数字

grep “^[a-z]” test_rule.txt

匹配用小写字母开头的行

“[^]”:匹配中括号中以外的任意一个字符

grep “^[^a-z]” test_rule.txt

匹配不用小写字母开头的行

grep “^[^a-zA-Z]” test_rule.txt

匹配不用字母开头的行

“":使特殊符号失去特殊含义

grep “.$” test_rule.txt

匹配使用“.”结尾的行

“{n}”:表示其前面的字符恰好出现n次

grep “a{3}“ test_rule.txt

匹配a字母连续出现三次的字符串

grep “[0-9]{3}“ test_rule.txt

匹配包含连续的三个数字的字符串

“{n,}”表示其前面的字符出现不小于n次

grep “^[0-9]{3,}[a-z]” test_rule.txt

匹配最少用连续三个数字开头的行

“{n,m}”匹配其前面的字符至少出现n次, 最多出现m次

grep “sa{1,3}i” test_rule.txt

匹配在字母s和字母i之间有最少一个a,最多三个a

cut 字段提取命令

cut 命令

可以使用 cut 进行列提取,主要用途是配合 grep 提取相应的列。

printf 格式化输出

和 Java 等语言的格式化输出用法基本相同

awk 命令

awk ‘条件1{动作1} 条件2{动作2}…’ 文件名

条件:一般使用表达式作为条件。

动作:格式化输出 printf、流程控制语句。

awk ‘{printf $2 “\t” $6 “\n”}’ student

打印 student 文件的第 2 列和第 6 列,再格式化输出。

awk 在执行时,是一行一行的输入,通过条件判断是否执行动作。

awk 可以识别以空格为列间的分割,而 cut 只能识别以 tab 为列分割。

BEGIN 在所有的动作前,先执行一个动作。

awk ‘BEGIN{printf “This is a transcript \n” } {printf $2 “\t” $6 “\n”}’ student

awk 指定分隔符,使用 FS

awk ‘BEGIN {FS=”:”} {printf $1 “\t” $3 “\n”}’

END: 在所有的动作后,再执行一个动作。

awk ‘END{printf “The End \n” } {printf $2 “\t” $6 “\n”}’ student

awk 使用关系运算符

awk 是复杂的命令,具体细节,使用时再查。

cat student| grep -v Name | \ awk ‘$6 >= 87 {printf $2 “\n” }’

将第 6 列的数据是否大于数字打印出来。

Sed 命令

编辑器,用来将数据进行选取、替换、删除、新
增的命令。支持管道符操作,可以从管道符接收数据,不仅可以修改文件的内容,也可以修改命令的结果,这是 sed 与 vim 的主要区别。

sed 为 行操作

sed [选项] ‘[动作]’ 文件名

选项:

-n: 一般sed命令会把所有数据都输出到屏幕 , 如果加入此选择,则只会把经过sed命令处理的行输出到屏幕。
-e: 允许对输入数据应用多条sed命令编辑
-i: 用sed的修改结果直接修改读取数据的文件, 而不是由屏幕输出
-e: 允许对输入数据应用多条sed命令编辑
-i: 用sed的修改结果直接修改读取数据的文件, 而不是由屏幕输出

动作(单引号内):

a :追加,在当前行后添加一行或多行。添加多行时,除最后一行外,每行末尾需要用“\”代表数据未完结。
c :
行替换,用c后面的字符串替换原数据行,替换多行时,除最 后一行外,每行末尾需用“\”代表数据未完结。
i :插入,在当期行前插入一行或多行。插入多行时,除最后 一行 外,每行末尾需要用“\”代表数据未完结。
d:删除,删除指定的行。
p:打印,输出指定的行。
s: 字串替换,用一个字符串替换另外一个字符串。格式为“行范 围s/旧字串/新字串/g”(和vim中的替换格式类似)。

示例:

打印:

sed ‘2p’ student.txt

查看文件的第二行,但是会将整个文件打印出来。

sed -n ‘2p’ student.txt

只会讲第 2 行打印出来。

删除:

sed ‘2,4d’ student.txt

删除第二行到第四行的数据,但不修改文件本身。

追加:

sed ‘2a hello’ student.txt
在第二行后追加hello。

插入:

sed ‘2i hello \ world’ student.txt

在第二行前插入两行数据。

替换:

sed ‘2c No such person‘ student.txt

数据替换,将第2 行替换为具体内容。

字符串替换:

sed ‘s/旧字串/新字串/g’ 文件名

sed ‘3s/74/99/g’ student.txt

在第三行中,把74换成99。

sed -e ‘s/Liming//g ; s/Gao//g’ student.txt

同时进行多个替换动作。

写入文件:

sed -i ‘3s/74/99/g’ student.txt

将 sed 操作写入文件。

条件判断

流程控制

Kotlin 单例模式的实现

最简单的实现方式

1
2
3
object Singleton {
fun method() {}
}

调用单例

1
2
3
4
5
//在Kotlin里调用
Singleton.method()

//在Java中调用
Singleton.INSTANCE.method();

其实这种方式的实现为 Java 中的饿汉式,在 AS 的 Kotlin 相关的工具对 object 类进行编译和反编译,可以看到起对应的 Java 代码如下:

Read More