惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
DataBreaches.Net
T
Threatpost
N
News and Events Feed by Topic
PCI Perspectives
PCI Perspectives
V2EX - 技术
V2EX - 技术
D
Docker
G
Google Developers Blog
Microsoft Security Blog
Microsoft Security Blog
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Google Online Security Blog
Google Online Security Blog
The GitHub Blog
The GitHub Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
Y
Y Combinator Blog
M
MIT News - Artificial intelligence
Blog — PlanetScale
Blog — PlanetScale
博客园 - 司徒正美
T
Troy Hunt's Blog
Webroot Blog
Webroot Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
量子位
Apple Machine Learning Research
Apple Machine Learning Research
H
Help Net Security
F
Full Disclosure
B
Blog
O
OpenAI News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
博客园_首页
Google DeepMind News
Google DeepMind News
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Engineering at Meta
Engineering at Meta
大猫的无限游戏
大猫的无限游戏
Forbes - Security
Forbes - Security
Know Your Adversary
Know Your Adversary
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
Scott Helme
Scott Helme
T
The Exploit Database - CXSecurity.com
博客园 - 聂微东
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The Last Watchdog
The Last Watchdog
Recorded Future
Recorded Future
IT之家
IT之家
Project Zero
Project Zero
Stack Overflow Blog
Stack Overflow Blog
小众软件
小众软件
Attack and Defense Labs
Attack and Defense Labs
L
Lohrmann on Cybersecurity
SecWiki News
SecWiki News
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com

Android Performance

SmartPerfetto 架构文章 Q&A:8 个深度技术问答 从 Trace 到洞察:SmartPerfetto AI Agent 的 Harness Engineering 实战 OpenClaw 常见问题解答:Token 消耗、能干什么、本地模型、隐私安全、使用体验 我把 OpenClaw 跑在本地三周后,发现它根本不是聊天机器人 Android Perfetto 系列 10 - Binder 调度与锁竞争 Android Perfetto 系列 9 - CPU 信息解读 Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析 Android Perfetto 系列 7 - MainThread 和 RenderThread 解读 Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战 Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程 Android Perfetto 系列 4:使用命令行在本地打开超大 Trace Android ANR 系列 3 :ANR 案例分享 Android ANR 系列 2 :ANR 分析套路和关键 Log 介绍 Android ANR 系列 1 :理解 Android ANR 设计思想 Android Perfetto 系列 3:熟悉 Perfetto View Android Perfetto 系列 2:Perfetto Trace 抓取 Android Perfetto 系列 1:Perfetto 工具简介 Android Perfetto 系列目录 2023 年的方方面面 关于 The Android Performance 知识星球介绍 The Performance Design Of OS OS 设计之性能设计 当 App 有了系统权限,真的可以为所欲为? The Performance 星球茶话会 - 第一期 Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇 Systrace 线程 CPU 运行状态分析技巧 - Running 篇 Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇 Android 性能优化的术、道、器 Techniques, Philosophy, and Tools for Android Performance Optimization 回顾 2021 一本讲 Android 流畅性的书,应该有什么内容? Android 系统开发系列(1):Android 12 源代码下载、编译和刷机 Android Systrace 响应速度实战 3 :响应速度延伸知识 Android Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例 Android Systrace 响应速度实战 1 :了解响应速度原理 Android Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问 Android Systrace 流畅性实战 2 :案例分析 - MIUI 桌面滑动卡顿分析 Android Systrace 流畅性实战 1 :了解卡顿原理 华为手机刷微博体验更好?技术角度的一些分析和思考 一个「闰」字引发的事故 - 三星系统重启分析 Android App 链式唤醒分析 Android Systrace 基础知识 - SurfaceFlinger 解读 Android 开发者学习路线(2020 版本) 我的 2020 年读书单 Android Systrace 基础知识 - CPU Info 解读 Android Systrace 基础知识 - Triple Buffer 解读 Android Systrace 基础知识 - Binder 和锁竞争解读 「置顶」博客文章目录 Android Systrace 基础知识 - Vsync 解读 Android App 启动优化全记录 Android Systrace 基础知识 - MainThread 和 RenderThread 解读 Android Systrace 基础知识 - Input 解读 Android 中的“后台无效动画“行为分析 Android 基于 Choreographer 的渲染机制详解 Android 中的卡顿丢帧原因概述 - 低内存篇 Android 桌面被杀问题分析案例 Android 中的卡顿丢帧原因概述 - 应用篇 Android 中的卡顿丢帧原因概述 - 系统篇 Android 中的卡顿丢帧原因概述 - 方法论 Android 中的 Activity Launch Mode 详解 Android 中的 Hardware Layer 详解 Android Systrace 基础知识 -- 分析 Systrace 预备知识 Android Systrace 基础知识 - SystemServer 解读 Android Systrace 基础知识 -- Systrace 简介 Android Systrace 基础知识 -- Why 60 fps ? Android Systrace -- 系列文章目录 Android 新的流畅体验,90Hz 漫谈 利器 - 高效工具推荐 Android 无障碍服务导致的整机卡顿案例分析 2018 年度好物推荐 - 给辛勤工作的自己一点奖励 Android 系统开发源码环境搭建 陆奇:除了好代码,工程师怎样才算优秀? 程序员的修炼-08-阅读之美 程序员的修炼-07-游戏与编程 程序员的修炼-06-互联网那些事 程序员的修炼-05-了解你的用户 程序员的修炼-04-关于测试的一些思考 程序员的修炼-03-Web 设计原则 程序员的修炼-02-编程之道 程序员的修炼-01-绝地反击之术 Android 系统不释放内存吗? 关于 Android 系统流畅性的一些思考 知乎 救救你的 StartingWindow 「置顶」Android 性能优化必知必会 2017 年度好物推荐 - 给辛勤工作的自己一点奖励 2017 Android Bottom navigation 规范二:样式、行为与规格 Android Bottom Navigation 规范一:使用方法 Android 中如何计算 App 的启动时间? Android 应用启动优化 - 一种 DelayLoad 的实现和原理(上篇) Android hwui 中 RenderThread 工作流程 Java7 HashMap 源码分析 Android 代码内存优化建议 - OnTrimMemory 优化 Android 代码内存优化建议 - Android 资源篇 Android 代码内存优化建议 - Android 官方篇 Android 代码内存优化建议 - Java 官方篇 Nexus6 with Android M 开启多窗口模式 细说 Java 单例模式 Android 性能优化典范 - Profile GPU Rendering Android 性能优化典范 - Understanding VSYNC
Android 应用启动优化:一种 DelayLoad 的实现和原理(下篇)
Gracker · 2015-12-29 · via Android Performance

上一篇文章我们使用第三种方法来实现延迟加载。不过上一篇写的比较简单,只是讲解了如何去实现,这一篇就来讲一下为何要这么做,以及这么做后面的原理。
其中会涉及到一些 Android 中的比较重要的类,以及 Activity 生命周期中比较重要的几个函数。
其实这个其中的原理比较简单,不过要弄清楚其实现的过程,还是一件蛮好玩的事情,其中会用到一些工具,自己加调试代码等,一步一步下来,自己对 Activity 的启动的理解又深了一层,希望大家读完之后也会对大家有一定的帮助。

下文对 DecorView.post、MessageQueue、VSYNC 和 Activity 启动顺序的分析仍然可以帮助理解“为什么首帧之后再做事”。现在落地时要再补一层验证:用 Macrobenchmark StartupTimingMetric 量化首帧/启动耗时,用 Perfetto/System Trace 看延后任务是否落在首帧之后、是否影响后续几帧、是否引入 Binder/I/O/GC 抖动。只有测量结果和时间线都支持,DelayLoad 才算优化;否则它可能只是把启动慢变成进入页面后的卡顿。

上一篇中我们最终使用的 DelayLoad 的核心方法是在 Activity 的 onCreate 函数中加入下面的方法 :

1
2
3
4
5
6
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});

我们一一来看涉及到的类和方法

1. Activity.getWindow 及 PhoneWindow 的初始化时机

Activity 的 getWindow 方法获取到的是一个 PhoneWindow 对象:

1
2
3
public Window getWindow() {
return mWindow;
}

这个 mWindow 就是一个 PhoneWindow 对象,其初始化的时机为这个 Activity attach 的时候:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
attachBaseContext(context);

mFragments.attachActivity(this, mContainer, null);

mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
........


public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}

}

这里需要注意 Activity 的 attach 方法很早就会调用的,是要早于 Activity 的 onCreate 方法的。

总结:

  • PhoneWindow 与 Activity 是一对一的关系,通过上面的初始化过程你应该更加清楚这个概念
  • Android 中对 PhoneWindow 的注释是 :Android-specific Window ,可见其重要性
  • PhoneWindow 中有很多大家比较熟悉的方法,比如 setContentView / addContentView 等 ; 也有几个重要的内部类,比如:DecorView ;

2. PhoneWindow.getDecorView 及 DecorView 的初始化时机

上面我们说到 DecorView是 PhoneWindow 的一个内部类,其定义如下:

1
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker

那么 DecorView 是什么时候初始化的呢?DecorView 是在 Activity 的父类的 onCreate 方法中被初始化的,比如我例子中的 MainActivity 是继承自 android.support.v7.app.AppCompatActivity ,当我们调用 MainActivity 的 super.onCreate(savedInstanceState); 的时候,就会调用下面的

1
2
3
4
5
protected void onCreate(@Nullable Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}

由于我们导入的是 support.v7 包里面的AppCompatActivity, getDelegate() 得到的就是AppCompatDelegateImplV7 ,其 onCreate 方法如下:

1
2
3
4
5
6
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

mWindowDecor = (ViewGroup) mWindow.getDecorView();
......
}

就是这里的 mWindow.getDecorView() ,对 DecorView 进行了实例化:

1
2
3
4
5
6
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}

第一次调用 getDecorView 的时候,会进入 installDecor 方法,这个方法对 DecorView 进行了一系列的初始化 ,其中比较重要的几个方法有:generateDecor / generateLayout 等,generateLayout 会从当前的 Activity 的 Theme 提取相关的属性,设置给 Window,同时还会初始化一个 startingView,添加到 DecorView上,也就是我们所说的 startingWindow。

总结

  • Decor 有装饰的意思,DecorView 官方注释为 “This is the top-level view of the window, containing the window decor”, 我们可以理解为 DecorView 是我们当前 Activity 的最下面的布局。所以我们打开 DDMS 查看 Tree Overview 的时候,可以发现最根部的那个 View 就是 DecorView:
    DelayLoad
  • 应用从桌面启动的时候,在主 Activity 还没有显示的时候,如果主题没有设置窗口的背景,那么我们就会看到白色(这个和手机的Rom也有关系),如果应用启动很慢,那么用户得看好一会白色。如果要避免这个,则可以在 Application 或者 Activity 的 Theme 中设置 WindowBackground, 这样就可以避免白色(当然现在各种大厂都是SplashActivity+广告我也是可以理解的)

3. Post

当我们调用 DecorView 的 Post 的时候,其实最终会调用 View 的 Post ,因为 DecorView 最终是继承 View 的:

1
2
3
4
5
6
7
8
9
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}

ViewRootImpl.getRunQueue().post(action);
return true;
}

注意这里的 mAttachInfo ,我们调用 post 是在 Activity 的 onCreate 中调用的,那么此时 mAttachInfo 是否为空呢?答案是 mAttachInfo 此时为空。

这里有一个点就是 Activity 的各个回调函数都是干嘛的?是不是平时自己写应用的时候,貌似在 onCreate 里面搞定一切就OK了, onResume ? onStart?没怎么涉及到嘛,其实不然。
onCreate 顾名思义就是 Create ,我们在前面看到,Activity 的 onCreate 函数做了很多初始化的操作,包括 PhoneWindow/DecorView/StartingView/setContentView等,但是 onCreate 只是初始化了这些对象.
真正要设置为显示则在 Resume 的时候,不过这些对开发者是透明了,具体可以看 ActivityThread 的 handleResumeActivity 函数,handleResumeActivity 中除了调用 Activity 的 onResume 回调之外,还初始化了几个比较重要的类:ViewRootImpl / ThreadedRenderer。

ActivityThread.handleResumeActivity:

1
2
3
4
5
6
7
8
9
10
11
12
13
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}

主要是 wm.addView(decor, l); 这句,将 decorView 与 WindowManagerImpl联系起来,这句最终会调用到 WindowManagerGlobal 的 addView 函数,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
ViewRootImpl root;
View panelParentView = null;
......
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}


try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
......
}
}

我们知道 ViewRootImpl 是 View 系统的一个核心类,其定义如下:

1
2
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks

ViewRootImpl 初始化的时候会对 AttachInfo 进行初始化,这就是为什么之前的在 onCreate 的时候 attachInfo 为空。ViewRootImpl 里面有很多我们比较熟悉也非常重要的方法,比如 performTraversals / performLayout / performMeasure / performDraw / draw 等。
我们继续 addView 中的root.setView(view, wparams, panelParentView); 传入的 view 为 decorView,root 为 ViewRootImpl ,这个函数中将 ViewRootImpl 的mView 变量 设置为传入的view,也就是 decorView。
这样来看,ViewRootImpl 与 DecorView 的关系我们也清楚了。

扯了一圈,我们再回到大标题的 Post 函数上,前面有说这个 Post 走的是 View 的Post 函数,由于 在 onCreate 的时候 attachInfo 为空,所以会走下面的分支:ViewRootImpl.getRunQueue().post(action);
注意这里的 getRunQueue 得到的并不是 Looper 里面的那个 MessageQueue,而是由 ViewRootImpl 维持的一个 RunQueue 对象,其核心为一个 ArrayList :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();

void post(Runnable action) {
postDelayed(action, 0);
}

void postDelayed(Runnable action, long delayMillis) {
HandlerAction handlerAction = new HandlerAction();
handlerAction.action = action;
handlerAction.delay = delayMillis;

synchronized (mActions) {
mActions.add(handlerAction);
}
}

void executeActions(Handler handler) {
synchronized (mActions) {
final ArrayList<HandlerAction> actions = mActions;
final int count = actions.size();

for (int i = 0; i < count; i++) {
final HandlerAction handlerAction = actions.get(i);
handler.postDelayed(handlerAction.action, handlerAction.delay);
}

actions.clear();
}
}

当我们执行了 Post 之后 ,其实只是把 Runnable 封装成一个 HandlerAction 对象存入到 ArrayList 中,当执行到 executeActions 方法的时候,将存在这里的 HandlerAction 再通过 executeActions 方法传入的 Handler 对象重新进行 Post。
那么 executeActions 方法是什么时候执行的呢?传入的 Handler 又是哪个 Handler 呢?

4. PerformTraversals

我们之前讲过,ViewRootImpl 的 performTraversals 方法是一个很核心的方法,每一帧绘制都会走一遍,调用各种 measure / layout / draw 等 ,最终将要显示的数据交给 hwui 去进行绘制。
我们上一节讲到的 executeActions ,就是在 performTraversals 中执行的:

1
2

getRunQueue().executeActions(mAttachInfo.mHandler);

可以看到这里传入的 Handler 是 mAttachInfo.mHandler ,上一节讲到 mAttachInfo 是在 ViewRootImpl 初始化的时候一起初始化的:

1
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);

这里的 mHandler 是一个 ViewRootHandler 对象:

1
2
3
4
5
final class ViewRootHandler extends Handler{
......
}
......
final ViewRootHandler mHandler = new ViewRootHandler();

我们注意到 ViewRootHandler 在创建的时候并没有传入一个 Looper 对象,这意味着此 ViewRootHandler 的 Looper 就是 mainLooper。

这下我们就清楚了,我们在 onCreate 中 Post 的 runnable 对象,最终还是在第一个 performTraversals 方法执行的时候,加入到了 MainLooper 的 MessageQueue 里面了。

绕了一圈终于我们终于把文章最前面的那句话解释清楚了,当然中间还有很多的废话,不过我估计能耐着性子看到这里的人会很少,所以如果你看到了这里,可以在底下的评论里面将 index ++ ;这里 index = 0 ;就是看看几个人是真正认真看了这篇文章的。

5. UpdateText

接着 performTraversals 我们继续说,话说在第一篇文章 我们有讲到,Activity 在启动时,会在第二次执行 performTraversals 才会去真正的绘制,原因在于第一次执行 performTraversals 的时候,会走到 Egl 初始化的逻辑,然后会重新执行一次 performTraversals 。
所以前一篇文章的评论区有人问为何在 run 方法里面还要 post 一次,如果在 run 方法里面直接执行 updateText 方法 ,那么 updateText 就会在第一个 performTraversals 之后就执行,而不是在第一帧绘制完成后才去执行,所以我们又 Post 了一次 。所以大概的处理步骤如下:

第一步:Activity.onCreate –> Activity.onStart –> Activity.onResume

第二步:ViewRootImpl.performTraversals –>Runnable

第三步:Runnable –> ViewRootImpl.performTraversals

第四步:ViewRootImpl.performTraversals –> UpdateText

第五步:UpdateText

6. 总结

其实一路跟下来发现其实原理很简单,其实 DelayLoad 其实只是一个很小的点,关键是教大家如何去跟踪一个自己不认识的知识点或者优化,这里面主要用到了两个工具:Systrace 和 Method Trace, 以及源码编译和调试。
关于 Systrace 和 Method Trace 的使用,之后会有详细的文章去介绍,这两个工具非常有助于理解源码和一些技术的实现。

Systrace

Systrace

Method Trace

Method Trace

源码编译与调试

源码编译与调试

代码

本文章所所涉及到的代码我放到了Github上:
https://github.com/Gracker/DelayLoadSample

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球 : 欢迎加入,多谢支持~

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫