Xamarin.Forms之跨平台性能

1、使用 Profiler

Profiling是一种用于确定代码优化在减少性能问题方面将发挥最大作用的技术。 探查器跟踪应用程序的内存使用情况,并记录应用程序中方法的运行时间。 这些数据有助于在应用程序的执行路径和代码的执行成本之间导航,从而可以发现最佳的优化机会。

Xamarin Profiler 简介。

分析应用时建议采用以下最佳做法:

  • 使用真机测试,而不是用模拟器,因为模拟器可能误报应用程序性能。
  • 使用多种不同型号 不同配置的真机测试。
  • 关闭所有其他应用程序,减少不必要的干扰

从历史上看,Mono具有功能强大的命令行分析器,用于收集有关在Mono运行时中运行的程序的信息,称为Mono日志分析器Xamarin Profiler是Mono日志探查器的图形界面,并支持对Mac上的Android,iOS,tvOS和Mac应用程序以及Windows上的Android,iOS和tvOS应用程序进行性能分析。

Xamarin Profiler有许多可用于性能分析的工具:分配、周期和时间分析器。 下面探讨了这些工具的测量内容以及它们如何分析您的应用程序,并阐明了每个屏幕上显示的数据的含义。

1.1、下载和安装

Xamarin Profiler是一个独立的应用程序,并且与Visual Studio for Mac和Visual Studio集成在一起,从而可以在IDE中进行性能分析。

下载适合您平台的安装软件包:

下载完成后,启动安装程序以将Xamarin Profiler添加到您的系统。

选择Android或者ios程序,在分析菜单中:

Xamarin.Forms之跨平台性能

1.2、Profilers and Profiling

Profiling是应用程序开发中的一个重要且通常被忽略的步骤,Profiling是动态程序分析的一种形式-它在程序运行和使用时对其进行分析。

Profiler是一种数据挖掘工具,它收集有关时间复杂性,特定方法的使用以及分配的内存的信息,Profiler使您能够深入研究和分析这些指标,以查明代码中的问题区域。

在设计和开发应用程序时,不要过早优化很重要; 也就是说,将时间花在很少访问的区域上来开发代码,这就是分析的力量。 探查器可洞悉代码库中最常用的部分,并帮助您确定应花时间进行改进的区域,开发人员应注意了解大部分时间在应用程序中所花的时间,以及应用程序如何使用内存。

概要分析对所有类型的开发都有帮助,但在移动开发中尤其重要。 在移动平台上,未优化的代码比在台式机上更加引人注目,而您的应用能否成功取决于能否有效运行的美观且经过优化的代码。

分析方法:

使用Xamarin Profiler剖析应用程序的方法有多种,包括内存剖析和统计采样,这些分别通过“分配”和“时间分析器”工具执行

探查器是与VS是分开的进程,因此,除了从Visual Studio中启动之外,它还可以用作独立的应用程序,以检查由mono log profiler生成的.exe和.mlpd文件。

注: you can only profile Debug configurations

1.3、Profiler Basics

在成功对应用程序进行配置文件之前,您需要在应用程序的“项目选项”中允许“Profiling” 

安装Profiler后,Android程序默认可以打开Xamarin.Profiler了,而IOS程序需要在项目属性中设置:

Xamarin.Forms之跨平台性能

它通过分配和时间探查器检测来实现此目标,我们将在下一节将对此进行详细探讨。

保存和加载探查器会话

这会将文件保存为 .mlpd_格式,这是一种特殊的高度压缩格式,用于分析数据。

安装后,可以在应用程序目录中找到 Xamarin Profiler 应用程序:

Xamarin.Forms之跨平台性能

.mlpd文件加载到探查器中。

1.4、探查器功能

Xamarin Profiler 由五个部分组成,

  • 工具栏-位于探查器顶部,这提供了启动/停止分析、选择目标进程、查看应用程序运行时间以及组成探查器应用程序的拆分视图的选项。
  • 检测列表–列出了为分析会话加载的所有工具。
  • 可以使用滑块(在时间探查器下显示)来更改刻度。
  • 在下面的部分中,我们将更详细地介绍这些视图。
  • 这些部分依赖于所选乐器,并包括:配置设置、统计信息、堆栈跟踪信息和根的路径。

1.4.1、分配

分配工具在创建和收集垃圾时提供有关应用程序中对象的详细信息。

探查器的顶部是分配图,该图显示了性能分析期间按固定间隔分配的内存量。 当前,分配图是分配的总数,而不是该时间点的堆大小。 从某种意义上说,它将永远不会下降,只会不断增加,这包括分配在堆栈上的对象。 根据使用的运行时版本,图表可能看起来有所不同-即使对于同一应用程序也是如此。

分配工具中有不同的数据视图,使开发人员可以分析其应用程序如何使用和释放内存。 这些视图如下所述:

  • 双击类将显示分配的内存:

检查器的“分配”视图提供用于对对象进行过滤和分组的选项,提供有关已分配内存和最高分配的统计信息,以及“堆栈跟踪”和“根路径”的视图。

  • 调用树:显示应用程序中所有线程的整个调用树,并包括有关在每个节点上分配的内存的信息。 在列表中选择一个元素时,所有同级节点将显示为灰色。 您可以展开树或双击该元素以对其进行深入研究。显示此数据视图时,可以使用显示设置检查器视图来更改其显示方式。 当前有两种选择:

反向调用树–这从上到下考虑堆栈跟踪。 这是一个方便的视图选项,因为它指示CPU花费时间的最深层方法。
按线程分隔–此选项按线程组织调用树。

  • 请注意,仅当实时分析应用程序时,才可以执行快照。

1.4.2、时间分析器

Time Profiler仪器精确地测量在应用程序中每个方法花费了多少时间。 应用程序会定期暂停,并在每个活动线程上运行堆栈跟踪。 检测详细信息区域中的每一行都显示了已遵循的执行路径。

如下图的屏幕截图所示,该绘图图显示了该应用在运行时收到的样本数量:

"调用树"-显示每个方法所用的时间量。

1.4.3、循环

此检测允许你查找这些对象,并显示应用程序中引用的循环。

2、释放 IDisposable 资源

IDisposable接口提供了一种释放资源的机制,它提供了Dispose方法,应实现该方法以显式释放资源,IDisposable不是析构函数,仅应在以下情况下实现:

  • 当类拥有非托管资源时,需要释放的典型非托管资源包括文件,流和网络连接
  • 当类拥有托管IDisposable资源时。

然后,类型使用者可以调用IDisposable.Dispose实现以在不再需要该实例时释放资源。 有两种方法可以实现此目的:

  • 通过将IDisposable对象包装在using语句中。
  • 通过将对IDisposable的调用包装起来,放在try / finally块中。

在 using 语句中包装 IDisposable 对象

public void ReadText (string filename)
{
  ...
  string text;
  using (StreamReader reader = new StreamReader (filename)) {
    text = reader.ReadToEnd ();
  }
  ...
}
View Code

StreamReader类实现IDisposable,并且using语句提供了一种方便的语法,该语法在超出范围之前在StreamReader对象上调用StreamReader.Dispose方法。 在using块中,StreamReader对象是只读的,无法重新分配。 using语句还确保即使发生异常也可以调用Dispose方法,因为编译器为try / finally块实现了中间语言(IL)。

在 Try/Finally 块中包装对 IDisposable.Dispose 的调用

public void ReadText (string filename)
{
  ...
  string text;
  StreamReader reader = null;
  try {
    reader = new StreamReader (filename);
    text = reader.ReadToEnd ();
  } finally {
    if (reader != null) {
      reader.Dispose ();
    }
  }
  ...
}
View Code

3、取消订阅事件

为了防止内存泄漏,应该在释放订阅者对象之前取消订阅事件。 在取消订阅事件之前,发布对象中事件的委托人具有对该委托的引用,该委托封装了订阅者的事件处理程序。 只要发布对象拥有此引用,垃圾回收就不会回收订阅对象的内存。

public class Publisher
{
  public event EventHandler MyEvent;

  public void OnMyEventFires ()
  {
    if (MyEvent != null) {
      MyEvent (this, EventArgs.Empty);
    }
  }
}


public class Subscriber : IDisposable
{
  readonly Publisher publisher;
  EventHandler handler;

  public Subscriber (Publisher publish)
  {
    publisher = publish;
    handler = (sender, e) => {
      Debug.WriteLine ("The publisher notified the subscriber of an event");
    };
    publisher.MyEvent += handler;
  }

  public void Dispose ()
  {
    publisher.MyEvent -= handler;
  }
}
View Code

handler字段维护对匿名方法的引用,并用于事件订阅和取消订阅。

4、 

在 iOS 中避免循环引用的文档,确保其应用高效使用内存。

5、延迟创建对象的开销

此方法主要用于提升性能、避免计算和降低内存需求。

出现以下 2 种情况时,建议使用延迟初始化来创建开销很大的对象:

void ProcessData(bool dataRequired = false)
{
  Lazy<double> data = new Lazy<double>(() =>
  {
    return ParallelEnumerable.Range(0, 1000)
                 .Select(d => Compute(d))
                 .Aggregate((x, y) => x + y);
  });

  if (dataRequired)
  {
    if (data.Value > 90)
    {
      ...
    }
  }
}

double Compute(double x)
{
  ...
}
View Code

延迟初始化在第一次访问Lazy<T>.Value属性时发生。 包装的类型将在首次访问时创建并返回,并存储以供将来访问。

有关延迟初始化的详细信息,请参阅延迟初始化

6、实施异步操作

.NET提供了许多API的异步版本。与同步API不同,异步API确保活动执行线程永远不会在相当长的时间内阻塞调用线程。因此,从UI线程调用API时,请使用异步API(如果有)。这将使UI线程保持畅通,这将有助于改善用户对应用程序的体验。

另外,应在后台线程上执行长时间运行的操作,以避免阻塞UI线程。 .NET提供了async和await关键字,这些关键字允许编写异步代码,该代码在后台线程上执行长时间运行的操作,并在完成时访问结果。但是,尽管可以使用await关键字异步执行长时间运行的操作,但这不能保证该操作将在后台线程上运行。相反,这可以通过将长时间运行的操作传递给Task.Run来实现,如以下代码示例所示:

public class FaceDetection
{
  ...
  async void RecognizeFaceButtonClick(object sender, EventArgs e)
  {
    await Task.Run(() => RecognizeFace ());
    ...
  }

  async Task RecognizeFace()
  {
    ...
  }
}
View Code

RecognizeFace方法在后台线程上执行,而RecognizeFaceButtonClick方法要等到RecognizeFace方法完成后再继续。

长时间运行的操作也应支持取消操作。 例如,如果用户在应用程序内导航,则继续长时间运行的操作可能变得不必要。 实现取消的模式如下:

有关详细信息,请参阅异步支持概述

7、缩减应用程序大小

务必了解每个平台上的编译流程,才能了解应用程序可执行文件大小的来源:

尽量不用泛型

然后,从应用程序中删除所有未使用的类型和方法。

链接器提供 3 个可用于控制其行为的设置:

因此,务必在更改链接器行为后全面测试应用程序。

如果测试结果表明链接器错误删除了某个类或方法,则可使用以下属性之一来标记未静态引用但应用程序需要的类型或方法:

此外,使用 XML 序列化可能需要保留类型的属性。

用于 Android 的链接器。

8、优化图像资源

高效加载大位图。

仅应在必要时创建图像,应用程序不再需要图像后应立即将其释放

9、缩短应用程序激活期限

此激活期限是用户对应用程序的第一印象,因此缩短激活期限和用户等待时间对于获得用户对应用程序的良好第一印象非常重要。

可通过进度栏或类似控件提供此确信通知。

这在激活逻辑延迟加载其他程序集时可起到帮助,因为程序集在首次使用时需完成加载。

10、减少Web服务通信

因此,限制应用程序和 Web 服务之间的带宽利用率是明智之选。

因此,决定是否通过网络移动压缩数据之前,应仔细对二者进行权衡。

对于移动应用程序,通常首选 JSON 格式

通常,传输较大数据负载的远程调用所需时间与仅传输小型数据负载的调用所需时间相差无几。

但是,采用此方法时,还应实现合适的缓存策略以便更新本地缓存中的数据(如果数据在 Web 服务中发生更改)。

相关推荐