对于.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.使用性能优化工具
可以使用性能分析工具来检测和优化组件的性能问题,找出性能瓶颈并进行优化。