MASA MAUI 低端设备性能优化方案

对于.NET MAUI Blazor 应用支持的平台需要 Android 7.0 (API 24) 或更高版本,但是早期安卓版本设备硬件的性能有限,导致实际无法流畅运行应用。

本文章是使用MASA Blazor组件在MAUI框架开发低端安卓PDA应用的性能优化方案。.

以下是开发人员根据Blazor官方文档中的《ASP.NET Core Blazor 性能最佳做法》(https://learn.microsoft.com/zh-cn/aspnet/core/blazor/performance?view=aspnetcore-7.0)进行呈现效率优化的举例说明:

1. 减少组件的复杂性

1.1. 减少嵌套组件

将循环中的子组件直接写到父组件,避免呈现的子组件过多的重复开销

//原代码 <SelectOrderItem>子组件的代码需要直接写在当前页<MVirtualScroll Items="_alls" TItem="RunningOrderDto" Height="@("100%")" Width="@("100%")">    <ItemContent>        <SelectOrderItem Item="context" OnCheckedChange="OnItemSelected" /> //此处待优化    </ItemContent></MVirtualScroll>  //新代码 <MVirtualScroll Items="_alls" TItem="RunningOrderDto" Height="@("100%")" Width="@("100%")">    <ItemContent>         <div Class="@($"mx-0 px-0 mt-2 {GetClass(Item.Disable,Item.Selected)}")" Style="border-top:solid 1px" @onclick="OnCheck">            <div Class="mx-1 px-0 py-1 d-flex justify-space-between">                <div class="pa-0 py-0">                    <div class="pa-0 py-0 font-3 text-left">物料名称:@Item.Name</div>                    <div class="pa-0 py-0 mt-2 font-3 text-left">工单编号:@Item.ProdId</div>                </div>                <MIcon Class="@($"mt-4 {GetColor(Item.Selected)}")" Size="24">mdi-checkbox-marked-circle-outline</MIcon>            </div>        </div>    </ItemContent></MVirtualScroll> @code{    [Parameter]    public Action<RunningOrderDto> OnCheckedChange { get; set; }     private async void OnCheck()    {        Item.Selected = !Item.Selected;        if (OnCheckedChange != null)            OnCheckedChange.Invoke(Item);    }}

1.2. 减少非必要的组件使用

  • 替换非必要的<MList>列表组件和<MCheckbox>复选框组件的联合使用

//原代码<MList Flat ThreeLine Class="pt-0">    <MListItemGroup>        @foreach (var line in _list)        {        <MListItem>            <MListItemAction>                <MCheckbox @bind-Value="line.isSelect" Disabled="@(item.posted==NoYes.Yes)" OnClick="()=>checkboxChange()"></MCheckbox>            </MListItemAction>            <MListItemContent>                <MListItemTitle>@line.itemId                <MChip Small Class="@getStatusColor(line)">@(line.isReceive ? "已收" : line.isSend ?"已发": "未发")</MChip>                </MListItemTitle>                <MListItemSubtitle>@line.itemName</MListItemSubtitle>                <MListItemSubtitle>@line.inventLocationId - @line.inventLocationIdTo</MListItemSubtitle>            </MListItemContent>        </MListItem>        <MDivider></MDivider>    }    </MListItemGroup></MList> //新代码         @foreach (var line in _list) {           <div Class="mx-0 px-0 mt-2">            <div Class="@($"{getCheckColor(context)} mx-1 px-0 py-1 d-flex justify-space-between")"                 @onclick="()=>checkboxChange(context)">                <div>                    <MIcon Class="mt-6" Size="24">mdi-checkbox-marked-circle-outline</MIcon>                </div>                <div>                    <div class="text-left text-h6">@context.itemId                        <MChip Small Class="@getStatusColor(context)">@(context.isReceive ? "已收" : context.isSend ? "已发" : "未发")</MChip>                    </div>                    <div class="text-left">@context.itemName</div>                    <div class="text-left">@context.inventLocationId - @context.inventLocationIdTo</div>                </div>            </div>        </div>        <MDivider></MDivider>}
  • 替换非必要的<MRow><MCol>行列组件,如果 Cols=12 那Row Col组件是没必要使用的

//原代码<MRow Class="ma-0 pa-0 mt-2">    <MCol Cols="12" Class="pa-1">        <PMobileDateTimePicker Precision="DateTimePrecision.Hour" Value="@_request.StartDate"            ValueChanged="async (start)=>await OnChangeSearchAsync(start:start)">            <ActivatorContent>                <MButton Color="primary" Class="text-capitalize" Text @attributes="@context.Attrs">                    @(_request.StartDate.Format("yyyy/M/d H:m"))                </MButton>            </ActivatorContent>        </PMobileDateTimePicker>    </MCol>    <MCol Cols="12" Class="pa-1">        <PMobileDateTimePicker Precision="DateTimePrecision.Hour" Value="@_request.EndDate"            ValueChanged="async (end)=>await OnChangeSearchAsync(end:end)">            <ActivatorContent>                <MButton Color="primary" Class="text-capitalize" Text @attributes="@context.Attrs">                    @(_request.EndDate.Format("yyyy/M/d H:m"))                </MButton>            </ActivatorContent>        </PMobileDateTimePicker>    </MCol></MRow><MRow Class="pa-0 ma-0 py-2">    @if (Items == null || !Items.Any())    {        <MCol Cols="12" Class="text-center pa-0 ma-0 header-footer-content">            @("没有数据")        </MCol>    }</MRow>  //新代码<div Class="ma-0 pa-0 mt-2 d-flex justify-space-between">    <div Class="pa-1">        <PMobileDateTimePicker Precision="DateTimePrecision.Hour" Value="@_request.StartDate"            ValueChanged="async (start)=>await OnChangeSearchAsync(start:start)">            <ActivatorContent>                <MButton Color="primary" Class="text-capitalize" Text @attributes="@context.Attrs">                    @(_request.StartDate.Format("yyyy/M/d H:m"))                </MButton>            </ActivatorContent>        </PMobileDateTimePicker>    </div>    <div Class="pa-1">        <PMobileDateTimePicker Precision="DateTimePrecision.Hour" Value="@_request.EndDate"            ValueChanged="async (end)=>await OnChangeSearchAsync(end:end)">            <ActivatorContent>                <MButton Color="primary" Class="text-capitalize" Text @attributes="@context.Attrs">                    @(_request.EndDate.Format("yyyy/M/d H:m"))                </MButton>            </ActivatorContent>        </PMobileDateTimePicker>    </div></div>@if (Items == null || !Items.Any()){     <div class="text-center pa-0 ma-0 header-footer-content">            @("没有数据")     </div>}

2. 关闭过渡动画

在低端设备中,关闭过渡动画可以有效减少组件卡顿的视觉效果。例如 Menu 组件,通过参数置空 Transition="" 设置。

<MMenu Transition="">    <ActivatorContent>        <MButton Color="secondary" Class="ma-2" @attributes="context.Attrs">            Scroll Y Transition        </MButton>    </ActivatorContent>     <ChildContent>        <MList>            <MListItem>                <MListItemTitle>                    @("Item")                </MListItemTitle>            </MListItem>        </MList>    </ChildContent></MMenu>

3. 使用虚拟滚动

对于需要展示大量数据的组件,可以使用 MVirtualScroll 组件,只渲染可见区域的数据,减少渲染时间和内存占用。

<MVirtualScroll Items="_items" TItem="Item" Height="@("100%")" Width="@("100%")">    <ItemContent>        <div>            代码:列表内容        </div>    </ItemContent></MVirtualScroll>

4. 使用PPageContainer组件

对于一些非业务数据相关的活动页面缓存到DOM中,每次进入页面立即载入缓存,可以避免重复计算和渲染,快速响应。使用 PageTabs 页面选项卡组件的 一个容器组件 PPageContainer,其中 ExcludedPattners 属性接受一个匹配路径的正则表达式列表,匹配成功的路径对应的页面的内容将不会在离开页面后被缓存在DOM中。

<MMain>    <MErrorHandler ShowDetail>        <ChildContent>            <PPageContainer ExcludedPatterns="s_excludedPatterns">                @Body            </PPageContainer>        </ChildContent>    </MErrorHandler></MMain> @code {    /// <summary>    /// 不使用DOM缓存    /// </summary>    private static readonly string[] s_excludedPatterns =    {        "/Login"    }}

5.使用性能优化工具

可以使用性能分析工具来检测和优化组件的性能问题,找出性能瓶颈并进行优化。