基于Xamarin.Android的应用程序启动性能优化
背景
随着移动应?程序开发越来越流?,越来越多的应?程序浮现于市场。但是,开发移动应?程序并不是?个简单的过程,需要花费?量时间,尤其是如果想要?个可跨Apple、 Android 和 Windows 运?的可扩展移动应?程序。
然?,糟糕的性能可能会极?地损害?户体验。?户在任何时候都不希望看到10秒以上的启动画?。如果等待时间过?\他们可能会感到??、放弃购物、减少停留时间或完全卸载应?程序。
随着开发平台的普及,我们需要正确的?具和?法来满?不断增?的需求。
Xamarin就是这样?种框架,它?持在 Android、iOS 和 Windows 平台上共享单个代码库。
所以,我们将在 Xamarin.Android 应?程序中测试性能, 就像在 Android Studio 中使? Java 开发?样, 我们可以使?c#对性能进?测试, 从?优化启动时间。
测试总启动时间
?先测试程序在不同设备的启动时间, 此处?到的?具是友盟+推出的U-APM。 从图中可以看出应?程序在启动时间上还存在着?定的优化空间。
文章图片
在 Android 上,ActivityManager
系统进程会显示?条“初始显示时间”?志消息, 可以更好地了解整体启动时间。在命令?使?adb logcat
快速查看 Android 设备?志。 或者使?Visual Studio
中的Android 调试?志。
在 Windows 上,运?以下powershell
:
> adb logcat -d | Select-String Displayed
输出:
ActivityTaskManager: Displayed com.lgq.wood.expiramentation/.MainActivity:
上述?志消息是在 x86 Android 模拟器上从
Visual Studio
调试应?程序时捕获的。 启动/连接调试器会产??些额外的开销,并且缺少Debug
编译时的优化。如果我们简单地切换到
Release
配置并再次部署和运?应?程序:ActivityTaskManager: Displayed com.lgq.wood.expiramentation/.MainActivity:
如果我们在 Pixel 3 XL 设备上测试应?程序:
ActivityTaskManager: Displayed com.lgq.wood.expiramentation/.MainActivity:
因为我们最终?标是提?移动应?程序的性能, 那么第?步应该是实际测试卡顿函数的具体位置。 如果盲?地进?代码更改,最终可能会和我们推测的结果产?很?的分歧, 如果?些复杂的性能改进甚?会损害代码库的可维护性。这个过程应该是:测试,做出修改,再次测试,并且重复以上步骤。
采?U-APM测得卡顿位置主要出现于:
com.lgq.wood.expiramentation.apache.http.impl.exec.readRawTextFile
文章图片
诊断问题 好, ?前应?程序由于readRawTextFile很慢。 现在我该怎么办?
?先我们需要对以下?个组件有?个系统性的了解
安卓ART
Android 运?时 (ART) 是 Android 上的应?程序和系统服务使?的托管运?时。 ART 作为运?时执?
Dalvik
可执??件 (.dex ?件 - D alvik EX可执??件) , 这是?种?于存储 Dalvik
字节码的紧凑格式。ART 通过在安装应?程序时将整个应?程序编译为本机代码, 引?了提前 (AOT) 编译。 这带来 了更快的应?程序执?和改进的内存分配。 以及垃圾收集机制、更准确的分析等等。
为了实现这?点, ART使?
dex2oat
来创建?个ELF
(可执?和链接格式) 的可执??件。缺点是需要额外的时间来编译。 此外,应?程序会占??量磁盘内存来存储已编译的代码。AOT
Mono
运?时提供AOT功能。 Mono
将预编译程序集以最?化 JIT
时间并减少内存使? 。Mono
可以在?持它的平台(如 Android)上?成 ELF .so
?件。 然后它在原始程序集旁边存储?个预编译的图像。即
Mono.Android.dll → libaot-Mono.Android.dll.so
然后, 这些?件可以被
Mono
运?时使?, 并省略 JIT
开销启动跟踪
Mono 引?了?项功能,允许在应?程序上使?内置的 AOT 分析器来?成 AOT 配置?件。 分析器进?内存分析、执?时间分析,甚?是基于统计的抽样分析。这会?成?个 AOT 配置?件,当使?带有配置?件的 Mono 的 AOT 功能时,该配置?件可?于优化应?程序。
启动跟踪可?于Visual Studio 2019 版本 16.2或Visual Studio for Mac 2019 版本 8.2。
可以通过编辑 Android 项?的
.csproj
?件并在 Release
中添加以下属性来 开始使?启动跟踪:
也可以在项?设置的Android 选项中进?设置,Mono 的 AOT 编译器启?使?会默认配置?件 的启动跟踪, 并在部署时加快 Android 应?程序的启动时间。
实际分析
我们需要实际分析我们的代码并需要改进。切换回
Debug
配置, 并通过运?以下命令启?Mono 分析器:$ adb shell setprop debug.mono.profile log:calls
adb shell
在 Android 设备或模拟器上运?单个 shell
命令。 setprop
设置Android系统属性, 类似于其他平台上的环境变量。然后只需强制退出并重新启动应?程序。 下次启动时, Mono 会在 Android 应?程序的本地? 录中保存?个?件。
pro?le.mlpd
注意这?存在?个问题,该?件只能由应?程序本身访问,因此我们必须使?命令来定位?件:
run-as
$ adb shell run-as com.lgq.wood.expiramentation ls -l files/.__override__
-rw-rw-r-- 1 u0_a411 u0_a411 515303 2020-07-27 09:29 profile.mlpd
为了从设备上获取?件, 我使?了?个已知的可写?录, 例如:
/sdcard/Download/
$ adb shell run-as com.lgq.wood.expiramentation cp files/.__override__/prof
复制?件后,您可以使?adb pull将?件获取到您的台式计算机:
pro?le.mlpd
$ adb pull /sdcard/Download/profile.mlpd
/sdcard/Download/profile.mlpd: 1 file pulled, 0 skipped. 162.7 MB/s (515303
pro?le.mlpd
是?个?进制?件Windows ?户需要在?于 Linux 的 Windows ?系统中安装 Mono才能运?。
有了上?的?系列代码, 就会出现?些有趣的数字。
解决方案 我们通过前?的调?, 可以发现以下?个函数可能需要相当?的时间:
文章图片
还可以看到内存分配, 例如:
文章图片
请注意,如果您需要查看这些分配来?哪些?法,您可以传递到。
--tracesmprof-report
我们做出了多种尝试,也都收到了?定成效。但是我们最意想不到的是,下?这个简单的改动。 我们尝试将
string
直接从stream
中读取,?不是使?响应的内容创建,然后使?新的System.Text.Json
库来进?更?效的 JSON
解析:// At the top
using System.Text.Json;
//...
async Task GetSlides()
{
var response = await httpClient.GetAsync("https://httpbin.org/json");
response.EnsureSuccessStatusCode();
using (var stream = await response.Content.ReadAsStreamAsync())
{
return await JsonSerializer.DeserializeAsync(stream);
}
}
查看?法调?的差异, 我们可以看到?个明显的时间优化:
文章图片
文章图片
这?点, 和我们在U-APM中测试得到的瓶颈函数相吻合,瓶颈确实是处在
readRawTextFile
函数中,我们尝试了以下?种?法,也?定程度上缓解了启动问题但收益并没有U-APM中的 readRawTextFile 那么?。在此列出,仅供参考:- 我们可以缓存 Web 请求的结果
- 我们可以从磁盘上的?件加载之前的调?结果, ?如设置24 ?时内有效。
- 由于调?不是互相依赖, 我们可以同时进?异步调?
- 在服务器端, 我们可以进??个新的 API 调?, 在?个请求中返回所有调?的数据
推荐阅读
- 热闹中的孤独
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 一个人的旅行,三亚
- 布丽吉特,人生绝对的赢家
- 慢慢的美丽
- 尽力
- 一个小故事,我的思考。
- 家乡的那条小河
- 《真与假的困惑》???|《真与假的困惑》??? ——致良知是一种伟大的力量