Android应用启动优化:一种DelayLoad的实现和原理(转)

简介:

转载请注明出处王亟亟的大牛之路

在逛帖子的时候看到的一个不错的文章,转给大家,原文地址:http://androidperformance.com/2015/11/18/Android-app-lunch-optimize-delay-load.html

应用启动优化概述
在 Android 开发中,应用启动速度是一个非常重要的点,应用启动优化也是一个非常重要的过程.对于应用启动优化,其实核心思想就是在启动过程中少做事情,具体实践的时候无非就是下面几种:
1.异步加载
2.延时加载
3.懒加载

不用一一去解释,做过启动优化的估计都使用过,本篇文章将详细讲解一下一种延时加载的实现以及其原理.
其实这种加载的实现是非常简单的,但是其中的原理可能比较复杂,还涉及到Looper/Handler/MessageQueue/VSYNC等.以及其中碰到的一些问题,还会有一些我自己额外的思考.

优化后的DelayLoad的实现
一提到DelayLoad,大家可能第一时间想到的就是在 onCreate 里面调用 Handler.postDelayed方法, 将需要 Delay 加载的东西放到这里面去初始化, 这个也是一个比较方便的方法. Delay一段时间再去执行,这时候应用已经加载完成,界面已经显示出来了, 不过这个方法有一个致命的问题: 延迟多久?
大家都知道,在 Android 的高端机型上,应用的启动是非常快的 , 这时候只需要 Delay 很短的时间就可以了, 但是在低端机型上,应用的启动就没有那么快了,而且现在应用为了兼容旧的机型,往往需要 Delay 较长的时间,这样带来体验上的差异是很明显的.
这里先说优化方案:

1。首先 , 创建 Handler 和 Runnable 对象, 其中 Runnable 对象的 run方法里面去更新 UI 线程.


private Handler myHandler = new Handler();
private Runnable mLoadingRunnable = new Runnable() {
@Override
public void run() {
updateText(); //更新UI线程
}
};

2.在主 Activity 的 onCreate 中加入下面的代码

getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
myHandler.post(mLoadingRunnable);
}
});

其实实现的话非常简单,我们来对比一下三种方案的效果.

2. 三种写法的差异对比
为了验证我们优化的 DelayLoad的效果,我们写了一个简单的app , 这个 App 中包含三张不同大小的图片,每张图片下面都会有一个 TextView , 来标记图片的显示高度和宽度. MainActivity的代码如下:

public class MainActivity extends AppCompatActivity {
private static final int DEALY_TIME = 300 ;

private ImageView imageView1;
private ImageView imageView2;
private ImageView imageView3;
private TextView textView1;
private TextView textView2;
private TextView textView3;

private Handler myHandler = new Handler();
private Runnable mLoadingRunnable = new Runnable() {
@Override
public void run() {
updateText();
}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

imageView1 = (ImageView) findViewById(R.id.image1);
imageView2 = (ImageView) findViewById(R.id.image2);
imageView3 = (ImageView) findViewById(R.id.image3);

textView1 = (TextView) findViewById(R.id.text1);
textView2 = (TextView) findViewById(R.id.text2);
textView3 = (TextView) findViewById(R.id.text3);

//      第一种写法:直接Post
myHandler.post(mLoadingRunnable);

//      第二种写法:直接PostDelay 300ms.
//        myHandler.postDelayed(mLoadingRunnable, DEALY_TIME);

//      第三种写法:优化的DelayLoad
//      getWindow().getDecorView().post(new Runnable() {
//            @Override
//            public void run() {
//                myHandler.post(mLoadingRunnable);
//            }
//        });

// Dump当前的MessageQueue信息.
getMainLooper().dump(new Printer() {
@Override
public void println(String x) {
Log.i("Gracker",x);
}
},"onCreate");
}

private void updateText() {
TraceCompat.beginSection("updateText");
textView1.setText("image1 : w=" + imageView1.getWidth() +
" h =" + imageView1.getHeight());
textView2.setText("image2 : w=" + imageView2.getWidth() +
" h =" + imageView2.getHeight());
textView3.setText("image3 : w=" + imageView3.getWidth() +
" h =" + imageView3.getHeight());
TraceCompat.endSection();
}

我们需要关注几点:
updateText 这个函数是什么时候被执行的?
App 启动后,三个图片的长宽是否可以被正确地显示出来?
是否有 Delay Load 的效果?

2.1 第一种写法

1.updateText执行的时机?
下面是第一种写法的Trace图:
这里写图片描述
可以看到 updateText 是在 Activity 的 onCreate/onStart/onResume三个回调执行完成后才去执行的.

2.图片的宽高是否正确显示?

这里写图片描述

从图片看一看到,宽高并没有显示. 这是为什么呢? 这个问题就要从Activity 的 onCreate/onStart/onResume三个回调说起了. 其实Activity 的 onCreate/onStart/onResume三个回调中,并没有执行Measure和Layout操作, 这个是在后面的performTraversals中才执行的. 所以在这之前宽高都是0.

3.是否有 Delay Load 的效果?
并没有. 因为我们知道, 应用启动的时候,要等两次 performTraversals 都执行完成之后才会显示第一帧, 而 updateText 这个方法在第一个 performTraversals 执行之前就执行了. 所以 updateText 方法的执行时间是算在应用启动的时间里面的.

2.2 第二种写法

第二种写法我们Delay了300ms .我们来看一下表现.
1.updateText执行的时机?

这里写图片描述

可以看到,这种写法的话,updateText是在两个performTraversals 执行完成之后(这时候 APP 的第一帧才显示出来)才去执行的, 执行完成之后又调用了一次 performTraversals 将 TextView 的内容进行更新.

2.图片的宽高是否正确显示?

这里写图片描述

从上图可以看到,图片的宽高是正确显示了出来. 原因上面已经说了,measure/layout执行完成后,宽高的数据就可以获取了.

3.是否有 Delay Load 的效果?
不一定,取决于 Delay的时长.
从前面的 Trace 图上我们可以看到 , updateText 方法由于 Delay 了300ms, 所以在应用第一帧显示出来170ms之后, 图片的文字信息才进行了更新. 这个是有 Delay Load 的效果的.
但是这里只是一个简单的TextView的更新, 如果是较大模块的加载 , 用户视觉上会有很明显的 “ 空白->内容填充” 这个过程, 或者会附加”闪一下”特效…这显然是我们不想看到的.
有人会说:可以把Delay的时间减小一点嘛,这样就不会闪了. 话是这么说,但是由于 Android 机器的多元性(其实就是有很多高端机器,也有很多低端机器) , 在这个机子上300ms的延迟算是快,在另外一个机子上300ms算是很慢.
我们将Delay时间调整为50ms, 其Trace图如下:
第二种写法:Delay 50ms

这里写图片描述

可以看到,updateText 方法在第一个 performTraversals 之后就执行了,所以也没有 Delay Load 的效果(虽然宽高是正确显示了,因为在第一个 performTraversals 方法中就执行了layout和measure).

2.3 第三种写法

经过前两个方法 , 我们就会想, 如果能不使用Delay方法, updateText 方法能在 第二个performTraversals 方法执行完成后(即APP第一帧在屏幕上显示),马上就去执行,那么即起到了 Delay Load的作用,又可以正确显示图片的宽高.
第三种写法就是这个效果:
1.updateText执行的时机?

这里写图片描述

可以看到这种写法. updateText 在第二个 performTraversals 方法执行完成后马上就执行了, 然后下一个 VSYNC 信号来了之后, TextView就更新了.

2.图片的宽高是否正确显示?
当然是正确显示的.如图:

这里写图片描述

3. 一些思考
关于优化的 Delay Load 的实现,从代码层面来看其实是非常简单的.其带来的效果也是很赞的.
但是实现之后我们还需要思考一下,为何这么做就可以实现这种功能呢?很显然要回答这个问题,我们需要知道更底层的一些东西.这个还涉及到 Handler/Message/MessageQueue/Looper/VSYNC/ViewRootImpl等知识. 往大里说应该还涉及到AMS/WMS等.由于涉及到的东西比较多,我就不在这一篇里面阐述了, 下一篇文章将会从从原理上讲解一下为何优化的 Delay Load 会起作用.

目录
相关文章
|
3天前
|
Java API 开发工具
如何将python应用编译到android运行
【6月更文挑战第27天】本文介绍在Ubuntu 20上搭建Android开发环境,包括安装JRE/JDK,设置环境变量,添加i386架构,安装依赖和编译工具。并通过`p4a`命令行工具进行apk构建和清理。
20 6
如何将python应用编译到android运行
|
12天前
|
XML 存储 数据库
如何使用Android Studio创建一个基本的音乐播放器应用
如何使用Android Studio创建一个基本的音乐播放器应用
28 0
|
11天前
|
ARouter IDE 开发工具
Android面试题之App的启动流程和启动速度优化
App启动流程概括: 当用户点击App图标,Launcher通过Binder IPC请求system_server启动Activity。system_server指示Zygote fork新进程,接着App进程向system_server申请启动Activity。经过Binder通信,Activity创建并回调生命周期方法。启动状态分为冷启动、温启动和热启动,其中冷启动耗时最长。优化技巧包括异步初始化、避免主线程I/O、类加载优化和简化布局。
28 3
Android面试题之App的启动流程和启动速度优化
|
3天前
|
安全 网络协议 算法
Android网络基础面试题之HTTPS的工作流程和原理
HTTPS简述 HTTPS基于TCP 443端口,通过CA证书确保服务器身份,使用DH算法协商对称密钥进行加密通信。流程包括TCP握手、证书验证(公钥解密,哈希对比)和数据加密传输(随机数加密,预主密钥,对称加密)。特点是安全但慢,易受特定攻击,且依赖可信的CA。每次请求可能复用Session ID以减少握手。
13 2
|
9天前
|
缓存 JSON 网络协议
Android面试题:App性能优化之电量优化和网络优化
这篇文章讨论了Android应用的电量和网络优化。电量优化涉及Doze和Standby模式,其中应用可能需要通过用户白名单或电池广播来适应限制。Battery Historian和Android Studio的Energy Profile是电量分析工具。建议减少不必要的操作,延迟非关键任务,合并网络请求。网络优化包括HTTPDNS减少DNS解析延迟,Keep-Alive复用连接,HTTP/2实现多路复用,以及使用protobuf和gzip压缩数据。其他策略如使用WebP图像格式,按网络质量提供不同分辨率的图片,以及启用HTTP缓存也是有效手段。
29 9
|
10天前
|
XML 监控 安全
Android App性能优化之卡顿监控和卡顿优化
本文探讨了Android应用的卡顿优化,重点在于布局优化。建议包括将耗时操作移到后台、使用ViewPager2实现懒加载、减少布局嵌套并利用merge标签、使用ViewStub减少资源消耗,以及通过Layout Inspector和GPU过度绘制检测来优化。推荐使用AsyncLayoutInflater异步加载布局,但需注意线程安全和不支持特性。卡顿监控方面,提到了通过Looper、ChoreographerHelper、adb命令及第三方工具如systrace和BlockCanary。总结了Choreographer基于掉帧计算和BlockCanary基于Looper监控的原理。
20 3
|
19小时前
|
安全 Java 数据处理
Android多线程编程实践与优化技巧
Android多线程编程实践与优化技巧
|
20小时前
|
Android开发 开发者 UED
使用AlarmManager实现Android应用中的定时任务
使用AlarmManager实现Android应用中的定时任务
|
6天前
|
人工智能 API 语音技术
探索Gemini Pro AI在智能Android应用中的魅力
探索Gemini Pro AI在智能Android应用中的魅力
11 0
|
12天前
|
XML BI 数据库
一个基于Android Studio的简易记事本应用
一个基于Android Studio的简易记事本应用
20 0