1. 在入口 Acitivity 中开启 IntentService 来下载广告页。 或者是其它异步下载操作。

  2. 在广告页图片 文件流完全写入后 记录图片大小,或者记录一个标识。

在下次的广告页加载中可以判断是否已经下载好了广告页图片以及图片是否完整,否则删除并且再次下载图片。

另外因为在闪屏页中仍然有 剩余展示时间,所以在这个时间段里如果用户已经下载好了图片并且图片完整,就可以显示广告页。否则进入主 Activity , 因为 IntentService 仍然在后台继续默默的下载并保存图片~

4. 优化效果


优化前 :

| Displayed | LaunchActivity | MainActivity |

| — | :-: | --: |

| | +2s526ms | +1s583ms |

| | +2s603ms | +1s533ms |

| | +2s372ms | +1s556ms |

优化后 :

| Displayed | LaunchActivity | MainActivity |

| — | :-: | --: |

| | +995ms | +1s191ms |

| | +911ms | +1s101ms |

| | +903ms | +1s187ms |

通过上面的几台手机的启动测试,发现优化后App冷启动的启动速度均提升了60% !!! ,并且我们可以再看一下手机冷启动时候的内存情况 :

优化前 : 伴随着大量对象的创建回收,15s内系统GC 5次。

内存使用波澜荡漾。

优化后 : 趋于平稳上升状态创建对象,15s内系统GC 2次。(后期业务拓展加入新功能,所以代码量增加。)之后总内存使用平缓下降。

**Other :**应用使用的系统不确定如何分类的内存。

**Code :**应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存。

Stack : 应用中的原生堆栈和 Java 堆栈使用的内存。 这通常与您的应用运行多少线程有关。

Graphics:图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。 (请注意,这是与 CPU 共享的内存,不是 GPU 专用内存。)

**Native :**从 C 或 C++ 代码分配的对象内存。即使应用中不使用 C++,也可能会看到此处使用的一些原生内存,因为 Android 框架使用原生内存代表处理各种任务,如处理图像资源和其他图形时,即使编写的代码采用 Java 或 Kotlin 语言。

**Java :**从 Java 或 Kotlin 代码分配的对象内存。

**Allocated :**应用分配的 Java/Kotlin 对象数。 它没有计入 C 或 C++ 中分配的对象。

更多查看 :

https://developer.android.google.cn/studio/profile/memory-profiler?hl=zh-cn

5. 启动窗口


优化完我们的代码后,分析一下启动窗口的源码。基于 android-25 (7.1.1)

启动窗口是由 WindowManagerService 统一管理的 Window窗口,一般作为冷启动页入口 Activity 的预览窗口,启动窗口由 ActivityManagerService 来决定是否显示的,并不是每一个 Activity 的启动和跳转都会显示这个窗口。

WindowManagerService 通过窗口管理策略类 PhoneWindowManager 来创建启动窗口。

拿我之前源码分析的文章中的启动流程图来看看大致 :

Launcher 启动 Activity 的工作过程

https://blog.csdn.net/qian520ao/article/details/78156214

直奔主题,在 ActivityStarter的startActivityUnchecked()方法中,调用了ActivityStack(Activity 状态管理)的startActivityLocked()方法。此时Activity 还在启动过程中,窗口并未显示。

先上一张流程图,展示了启动窗口的显示过程。

首先,由 Activity 状态管理者ActivityStack开始执行显示启动窗口的流程。

//ActivityStack

final void startActivityLocked(ActivityRecord r, boolean newTask, boolean keepCurTransition,

ActivityOptions options) {

if (!isHomeStack() || numActivities() > 0) {//HOME_STACK表示Launcher桌面所在的Stack

// 1.首先当前启动栈不在Launcher的桌面栈里,并且当前系统已经有激活过Activity

boolean doShow = true;

if (newTask) {

// 2.要将该Activity组件放在一个新的任务栈中启动

if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {

resetTaskIfNeededLocked(r, r);

doShow = topRunningNonDelayedActivityLocked(null) == r;

}

} else if (options != null && options.getAnimationType()

== ActivityOptions.ANIM_SCENE_TRANSITION) {

doShow = false;

}

if (r.mLaunchTaskBehind) {

//3. 热启动,不需要启动窗口

mWindowManager.setAppVisibility(r.appToken, true);

ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);

} else if (SHOW_APP_STARTING_PREVIEW && doShow) {

//4. 显示启动窗口

r.showStartingWindow(prev, showStartingIcon);

}

} else {

// 当前启动的是桌面Launcher (开机启动)

// If this is the first activity, don’t do any fancy animations,

// because there is nothing for it to animate on top of.

}

}

首先判断当前要启动的 Activity 不在Launcher栈里

要启动的 Activity 是否处于新的 Task 里,并且没有转场动画

如果是热/温启动则不需要启动窗口,直接设置App的Visibility

接下来调用ActivityRecord的showStartingWindow()方法来设置启动窗口并且改变当前窗口的状态。

如果 App 的应用进程创建完成,并且入口 Activity 准备就绪,就可以根据 mStartingWindowState 来判断是否需要关闭启动窗口。

//ActivityRecord

void showStartingWindow(ActivityRecord prev, boolean createIfNeeded) {

final CompatibilityInfo compatInfo =

service.compatibilityInfoForPackageLocked(info.applicationInfo);

final boolean shown = service.mWindowManager.setAppStartingWindow(

appToken, packageName, theme, compatInfo, nonLocalizedLabel, labelRes, icon,

logo, windowFlags, prev != null ? prev.appToken : null, createIfNeeded);

if (shown) {

mStartingWindowState = STARTING_WINDOW_SHOWN;

}

}

WindowManagerService 会对当前 Activity 的token和主题进行判断。

//WindowManagerService

@Override

public boolean setAppStartingWindow(IBinder token, String pkg,

int theme, CompatibilityInfo compatInfo,

CharSequence nonLocalizedLabel, int labelRes, int icon, int logo,

int windowFlags, IBinder transferFrom, boolean createIfNeeded) {

synchronized(mWindowMap) {

//1. 启动窗口也是需要token的

AppWindowToken wtoken = findAppWindowToken(token);

//2. 如果已经设置过启动窗口了,不继续处理

if (wtoken.startingData != null) {

return false;

}

if (theme != 0) {

AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,

com.android.internal.R.styleable.Window, mCurrentUserId);

//3. 一堆代码对主题判断,不符合要求则不显示启动窗口(如透明主题)

if (windowIsTranslucent) {

return false;

}

if (windowIsFloating || windowDisableStarting) {

return false;

}

}

//4. 创建StartingData,并且通过Handler发送消息

wtoken.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel,

labelRes, icon, logo, windowFlags);

Message m = mH.obtainMessage(H.ADD_STARTING, wtoken);

mH.sendMessageAtFrontOfQueue(m);

}

return true;

}

启动窗口也需要和 Activity 拥有同样令牌 token ,虽然启动窗口可能是白屏,或者一张图片,但是仍然需要走绘制流程已经通过WMS显示窗口。

StartingData对象用来表示启动窗口的相关数据,描述了启动窗口的视图信息。

如果当前 Activity 是透明主题或者是浮动窗口等,那么就不需要启动窗口来过渡启动过程,所以在上面视觉优化中的设置透明主题就没有显示白色的启动窗口。

显示启动窗口也是一件心急火燎的事情,WMS的内部类H (handler) 处于主线程处理消息,所以需要将当前Message放置队列头部。

PS : 为什么需要通过 Handler 发送消息 ?

你可以在各大服务Service中见到 Handler 的身影,并且它们可能都有一个很吊的命名 H ,因为可能调用这个服务的某个执行方法处于子线程中,所以 Handler 的职责就是将它们切换到主线程中,并且也可以统一管理调度。

更多 Handler 了解可以查阅文章 :

你真的了解Handler?

https://blog.csdn.net/qian520ao/article/details/78262289

//WindowManagerService --> H

public void handleMessage(Message msg) {

switch (msg.what) {

case ADD_STARTING: {

final AppWindowToken wtoken = (AppWindowToken)msg.obj;

final StartingData sd = wtoken.startingData;

View view = null;

try {

final Configuration overrideConfig = wtoken != null && wtoken.mTask != null

? wtoken.mTask.mOverrideConfig : null;

view = mPolicy.addStartingWindow(wtoken.token, sd.pkg, sd.theme,

sd.compatInfo, sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo,

sd.windowFlags, overrideConfig);

} catch (Exception e) {

Slog.w(TAG_WM, “Exception when adding starting window”, e);

}

} break;

}

在当前的handleMessage方法中,会处于主线程处理消息,拿到token和StartingData启动数据后,便通过mPolicy.addStartingWindow()方法将启动窗口添加到WIndow上。

mPolicy为PhoneWindowManager,控制着启动窗口的添加删除和修改。

在PhoneWindowManager对启动窗口进行配置,获取当前Activity设置的主题和资源信息,设置到启动窗口中。

//PhoneWindowManager

@Override

public View addStartingWindow(IBinder appToken, String packageName, int theme,

CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,

int icon, int logo, int windowFlags, Configuration overrideConfig) {

//可以通过SHOW_STARTING_ANIMATIONS设置不显示启动窗口

if (!SHOW_STARTING_ANIMATIONS) {

return null;

}

WindowManager wm = null;

View view = null;

//1. 获取上下文Context和主题theme以及标题

Context context = mContext;

if (theme != context.getThemeResId() || labelRes != 0) {

try {

context = context.createPackageContext(packageName, 0);

context.setTheme(theme);

} catch (PackageManager.NameNotFoundException e) {

// Ignore

}

}

//2. 创建PhoneWindow 用来显示

final PhoneWindow win = new PhoneWindow(context);

win.setIsStartingWindow(true);

//3. 设置当前窗口type和flag,源码注释中描述的很清晰…

win.setType(

WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);

win.setFlags(…);

view = win.getDecorView();

//4. WindowManager的绘制流程

wm.addView(view, params);

return view.getParent() != null ? view : null;

}

如果theme和labelRes的值不为0,那么说明开发者指定了启动窗口的主题和标题,那么就需要从当前要启动的Activity中获取这些信息,并设置到启动窗口中。

和其它窗口一样,启动窗口也需要通过PhoneWindow来设置布局信息DecorView。所以在上面视觉优化中的设置闪屏图片主题的启动窗口显示的就是图片内容。

启动窗口和普通窗口的不同之处在于它是 fake window ,不需要触摸事件

最后通过WindowManger走View的绘制流程(measure-layout-draw)将启动窗口显示出来,最后会请求WindowManagerService为启动窗口添加一个WindowState对象,真正的将启动窗口显示给用户,并且可以对启动窗口进行管理。

更多WindowManager的addView流程可以查阅 :

View的工作流程

https://blog.csdn.net/qian520ao/article/details/78657084

总结


至此应用程序的启动优化和启动窗口的源码分析已经总结完毕,在项目的开发中要知其然而之所以然 ,并且对源码的分析有助于我们了解原理和解决问题的根源。

资源分享

  • 最新大厂面试专题

这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等

  • 对应导图的Android高级工程师进阶系统学习视频
    最近热门的,NDK,热修复,MVVM,源码等一系列系统学习视频都有!

ManagerService为启动窗口添加一个WindowState对象,真正的将启动窗口显示给用户,并且可以对启动窗口进行管理。

更多WindowManager的addView流程可以查阅 :

View的工作流程

https://blog.csdn.net/qian520ao/article/details/78657084

总结


至此应用程序的启动优化和启动窗口的源码分析已经总结完毕,在项目的开发中要知其然而之所以然 ,并且对源码的分析有助于我们了解原理和解决问题的根源。

资源分享

  • 最新大厂面试专题

这个题库内容是比较多的,除了一些流行的热门技术面试题,如Kotlin,数据库,Java虚拟机面试题,数组,Framework ,混合跨平台开发,等

[外链图片转存中…(img-Xw1BlFuO-1643777394276)]

  • 对应导图的Android高级工程师进阶系统学习视频
    最近热门的,NDK,热修复,MVVM,源码等一系列系统学习视频都有!

[外链图片转存中…(img-muUMzmUi-1643777394276)]

下载方法:点赞+关注后 点击【Android高级工程师进阶学习】即可领取!

Android 性能优化—— 启动优化提升60,Android开发全套学习相关推荐

  1. 2022最新Android开发全套学习资料(知识笔记+技能图谱)3-5年开发者进阶提升

    前言 本人2013年由 java 转到 Android 开发,十年间,我从小厂打杂到进入到核心团队,再跳槽到大厂,在华为呆过一段时间,18年四月份进了阿里一直到现在.这期间,我见证过很多人的成败起落, ...

  2. Android 性能优化—— 启动优化提升60,android蓝牙开发实例

    其实这种方式并没有真正的加速应用进程的启动速度,而只是通过用户视觉效果带来的优化体验. _3_代码优化 当然上面使用设置主题的方式优化用户体验效果治标不治本,关键还在于对代码的优化. 首先我们可以统计 ...

  3. Android Studio 忽略_Android性能优化--启动优化

    1. 前言 一个应用App的启动速度能够影响用户的首次体验,启动速度较慢(感官上)的应用可能导致用户再次开启App的意图下降,或者卸载放弃该应用程序.本文会通过以下几个方面来介绍应用启动的相关指标和优 ...

  4. 【Android 性能优化】应用启动优化 ( 启动优化项目 | 界面启动时间 | 启动优化项目 | 方法追踪 MethodTracing )

    文章目录 一. 界面启动时间 二. 启动优化项目 三. 方法追踪 一. 界面启动时间 在 [Android 性能优化]应用启动优化 ( 启动白屏问题 | 应用启动时间测量 | 冷启动 | 热启动 | ...

  5. 关于android性能,内存优化 http://www.cnblogs.com/zyw-205520/archive/2013/02/17/2914190.html

     随着技术的发展,智能手机硬件配置越来越高,可是它和现在的PC相比,其运算能力,续航能力,存储空间等都还是受到很大的限制,同时用户对手机的体验要  求远远高于PC的桌面应用程序.以上理由,足以需要 ...

  6. Android高手笔记 - 启动优化

    启动, 打开APP的必经之路, 第一体验,关系到用户留存和转化率等核心数据: 启动分析 启动类型 Android Vitals可以对应用冷,热,温启动时间做监控. 通过adb shell am sta ...

  7. iOS性能优化 - 启动优化

    APP的启动可以分为2种 冷启动(Cold Launch):从零开始启动APP: 热启动(Warm Launch):APP已经在内存中,在后台存活着,再次点击图标启动APP. APP启动时间的优化,主 ...

  8. APP性能优化--启动优化

    1.APP启动方式 1.冷启动:从零开始启动APP; 2.热启动:APP已经在内存中,在后台存活着,再次单击图标启动APP. 2.APP启动时间的优化,主要是针对冷启动进行优化 3.Arguments ...

  9. 全志 Linux 系统启动优化 启动优化速度方式 优化启动流程 优化uboot 优化kernel等

    文章目录 1 概述 2 启动速度优化简介 2.1 启动流程 2.2 测量方法 2.2.1 printk time 2.2.2 initcall_debug 2.2.3 bootgraph. 2.2.4 ...

最新文章

  1. Java/Android基础-02
  2. Web服务器性能压力测试工具http_load、webbench、ab、Siege使用教程
  3. ibmm,让思维导图回归本质
  4. javaul材质包下载_只需一个水桶包 你就能装满时髦
  5. 【CASS精品教程】Win7+CAD2008+CASS9.1(含CASS3D)完美安装教程(附完整软件安装包下载)
  6. 在react项目中编写css,更好的在react项目中写css代码--emotion
  7. 一天就能打印一栋房子超大型3D打印机
  8. oracle获取sysdba权限,Oracle 学习笔记: SYSDBA登陆权限问题
  9. 让代码不运行的快捷键html5,使用 vscode 实现写代码双手不用离开键盘
  10. [代码审计]phpshe开源商城后台两处任意文件删除至getshell
  11. 分享一些直播软件的测试点
  12. 手工雕刻图纸_鬼斧神工--木雕手工雕刻技法
  13. 搭档之家:哭唧唧!暗地较劲得不偿失,美团暂停支付宝后被无情反超
  14. 绘制热力图seaborn.heatmap,cmap设置颜色的参数
  15. Linux 的常用系统及网络命令
  16. 啊哈算法---水管工游戏
  17. 利用cftool进行函数拟合
  18. v4l2 use V4L2_MEMORY_MMAP方式导出为 DMA BUF fd 方式使用
  19. 系统权限设计说明文档
  20. node 搭建本地服务

热门文章

  1. Spring源码深度解析(郝佳)-学习-源码解析-Spring MVC(一)
  2. 网络信息安全软考笔记(1)
  3. 基于QT实现的P2P聊天系统
  4. Oracle 创建DBLink方法
  5. 【Unity 实用工具】✨| Unity 十款 浏览器相关插件 整理(web view / browser)
  6. linux sendto recvfrom 异常退出,linux c学习笔记----UDP基础客户/服务编程(sendto,recvfrom)...
  7. uniqueResult的用法
  8. 急救盘linux加pe,教你怎样在PE系统盘里加入卡巴斯基急救盘1.doc
  9. Moto mt710打破对TD手机的信心危机
  10. 【OD统一考试(B卷)】最佳植树距离、种树,Python 解答