.NET MAUI

.NET MAUI

  • What is .NET MAUI?
    • MAUI可以用于创建移动端和桌面端应用,技术上基于C#和XAML。 可以运行与Android、iOS、macOS和Windows。
    • 多平台的代码都集中在一个工程中。 可以在不同不同共享UI 布局和设计,还有代码、测试以及业务逻辑。
    • How .NET MAUI works

    • Supported platforms for .NET MAUI apps
      • .NET Multi-platform App UI (.NET MAUI) apps
            Android 5.0 (API 21) or higher.
            iOS 10 or higher.
            macOS 10.15 or higher, using Mac Catalyst.
            Windows 11 and Windows 10 version 1809 or higher, using Windows UI Library (WinUI) 3.
        
      • .NET MAUI Blazor apps
            Android 7.0 (API 24) or higher is required
            iOS 14 or higher is required.
            macOS 11 or higher, using Mac Catalyst.
        
      • Blazor对WebView有额外的要求,参考ASP.NET Core Blazor supported platforms
      • iOS开发需要Mac支持。
  • Get started
    • Installation
      • 对VS的要求:
        • Visual Studio 2022 17.3或以上
        • Visual Sudio 2022 17.4 Preview
      • 开发iOS应用,还需要:
      • VS需要安装MAUI的工作负载。
    • Build your first app
      • VS中创建一个.NET MAUI App。在Debug Target中确保选中的是net6.0-windows10.0xxxxx之后就可以编译运行了。
      • 也就是基于XAML的,还是蛮熟悉的。
    • Resources for learning .NET MAUI
      • 列举了一些资源。
    • Migrate your app from Xamarin.Forms
    • Android
      • Emulator
        • How to enable hardware acceleration
          • 开启硬件加速有助于提高运行x86-64或x86虚拟设备镜像的性能
          • 有几种模式:HAXM、WHPX、Hypervisor.Framework
          • Accelerating Android emulators on Windows

            • WHPX是推荐的加速器,次优是HAXM
          • Accelerating with Hyper-V

            • Verifying support for Hyper-V

              • 硬件要求
                • 64位intel或者AMD Ryzen CPU,支持Second Level Address Translation(SLAT)
                • CPU支持VM Monitor Mode Extension (IntelCPU的话是VT-c)
                • 最小4GB内存
              • BIOS中必须开启虚拟化技术,硬件督用的Data Execution Prevention
              • 系统要求:Win 11或者Win10 1909
                • 可以通过systeminfo查看
            • Enabling Hyper-V acceleration in Windows and the emulator

              • (过)
            • (剩余略)
        • Manage and create virtual devices
          • 如何使用Android Device Manager来配置Android Virtual Devices (AVDs)
          • Android Device Manager on Windows(过)

          • Requirements

            • 需要以下条目
              • Visual Studio 2022
              • Android SDK API Level 30或以上,安卓SDK最好安装在C:\Program Files (x86)\Android\android-sdk
              • 以及其他一些料包,见文中
          • Open the device manager

            • 使用说明
          • Troubleshooting

        • Troubleshoot common problems
          • (过)
    • Devices
      • How to set up a physical device
        • Enable developer mode on the device

          • Settings -> About Phone -> 点击Build Number至7次
        • Enable USB debugging

          • Settings -> Developer options -> 开启USB debugging
      • Connect the device to the computer

        • 俺要安装Google USB Driver
      • Enable WiFi debugging

        • (略)
  • Fundamentals
    • App lifecycle
      • Cross-platform lifecycle events

        • Created、Activated、Deactivated、Stopped、Resumed、Destroying、Backgrounding(特定)
        • 可以安排事件委托,或者派生对应的方法
  • User interface
    • Controls
      • Views
        • Present data
    • Images
      • App icons
        • MAUI会在构建的时候,自动将图标适配成目标平台合适的分辨率
          • 位图类型的图片不会被缩放(所以难道只支持矢量图?)
          • SVG文件会被转为PNG文件,于是在XAML或者C#中,应以.png引用之
            • 只有在工程文件中才引用.svg
        • Change the icon

          • 在工程文件中(csproj)的ItemGroup标签中指定
            • <MauiIcon Include="Resources\AppIcon\appicon.svg" />
            • 只认第一个<MauiIcon>
            • 需要把图标文件的建构方式设为MauiIcon
            • 可能要清空工程,以及卸载已安装App,来使之生效
          • Composed icon

            • 图标可以为两个图片合成,一个代表背景,一个代表前景
              • <MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" />
              • 背景图片必须,前景图片可选
          • Set the base size

            • 可以设置一个基本尺寸(可比实际图片大),尺寸必须能够被8整除
            • <MauiIcon Include="Resources\AppIcon\appicon.png" BaseSize="128,128" />
            • 对于矢量图,可以设置Resize为false,来避免转换为PNG时的缩放
        • Recolor the background

          • 如果背景图片采用了透明,可以改变其颜色
            • <MauiIcon Include="Resources\AppIcon\appicon.svg" Color="#FF0000" />
        • Recolor the foreground

          • 如果采用了前景图片,前景颜色可以被Tinted
          • <MauiIcon Include="Resources\AppIcon\appicon.png" ForegroundFile="Resources\AppIcon\appiconfg.svg" TintColor="Yellow" />
        • Use a different icon per platform

          • 通过设置Condition来为不同的平台设置不同的图标
            <MauiIcon Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'"
                Include="Resources\AppIcon\backicon.png" ForegroundFile="Resources\AppIcon\appiconfg.svg" TintColor="#40FF00FF" />
            
          • 目标支持
            • ‘ios’
            • ‘maccatalyst’
            • ‘android’
            • ‘windows’
        • Platform-specific configuration

          • (略)
      • Images
        • (暂略)
      • Splash screen
        • Android以及iOS,MAUI应用会在启动结束之后,展示一个splash screen。
          • 当应用交互就绪时,splash screen会被清掉
        • 同样地,可以指定SVG格式,会被自动转为PNG格式
        • 存放在Resources\Splash目录下
        • 在csproj中引用:<MauiSplashScreen Include="Resources\Splash\splashscreen.svg" />
        • 为了遵循Android资源命名要求,splash screen必须
          • lowercase, start and end with a letter character, and contain only alphanumeric characters or underscores
        • 可以设置BasSize属性成能被8整除的值
          • 还可以设置TintColor和Color等属性
        • 在建构的时候,会被调整成目标平台所需的大小和分辨率
  • Platform integration
    • Overview
      • (略)
    • Application model
      • App actions
        • 通过IAppActions来响应App shortcuts,是作为额外的启动方式
          • 例如作为邮件App,可有两个Action
            • 启动到inbox
            • 启动到日历当天
        • (剩余略)
      • App information
        • IAppInfo可以用来读取应用信息
        • (剩余略)
      • Browser
        • IBrowser在系统浏览器中打开网页链接
        • (剩余略)
      • Launcher
        • ILauncher,通过URI来打开另一个应用
        • (剩余略)
      • Main thread
        • UI操作由专门的UI thread负责,此线程也叫做main thread, user-interface thread或直接UI thread
        • 对应Microsoft.Maui.ApplicationModel.MainThread
        • When is it required

          • Run code on the UI thread

            • 示例:MainThread.BeginInvokeOnMainThread(() => ...)
        • Determine if invocation is required

          • MainThread.IsMainThread
          • 非必须,因为BeginInvokeOnMainThread会检测此项
        • Additional methods

          • (略)
      • Map
        • 通过IMap来打开地图
        • (剩余略)
      • Permissions
        • Microsoft.Maui.ApplicationModel.Permissoin可用于检测应用获取的许可
        • (剩余略)
      • Version tracking
        • IVersionTracking可以用于获取应用版本信息,并且判断应用是否首次允许
        • (剩余略)
    • Device features
      • Device display
        • 使用接口IDeviceDisplay来获取屏幕尺寸
        • 可以通过DeviceDisplay.Current来获取符合此接口的实现
        • 包含这些的名字空间是Microsoft.Maui.Devices
        • Main display info

          • IDeviceDisplay.MainDisplayInfo返回
          • IDeviceDisplay也提供了MainDisplayInfoChanged事件,用以处理屏幕度量发生变化
        • Keep the screen on

          • 把IDeviceDisplay.KeepScreenOn设为true即可
        • Platform differences

          • 只有iOS\macOS上要求DeviceDisplay在主线程运行,否则抛出异常
            • MainThread.BeginInvokeOnMainThread可以在主线程运行代码
    • Platform specific
    • Storage
      • File system helpers
        • 介绍IFileSystem,其实现可从FileSystem.Current辖属获取,二者同在Microsoft.Maui.Storage名字空间
        • Using file system helpers

          • Cache directory

            • 用于存储临时性没那么强,但又不是必须的数据,操作系统或许会清空之
            • string cacheDir = FileSystem.Current.CacheDirectory;
          • App data directory

            • 此目录中的数据会被自动同步
            • string mainDir = FileSystem.Current.AppDataDirectory;
        • Bundled files

          • 可用OpenAppPackageFileAsync来打开一个应用料包中的文件
          • Writing from a bundled file to the app data folder

            • 给了一个例子,将绑垛中的文件写入应用数据目录
        • Platform differences

      • Preferences
        • IPreferences可以以键项对的方式存储用户首选项,从Preferences.Default获得
        • 接口和实现均在Microsoft.Maui.Storage名字空间下
        • Storage types

          • 键值为String
          • 项值可以为Boolean, Double, Int32, Single, Int64, Int64, String, DateTime
            • DateTime存储再64位的二进制格式中,由ToBinary以及FromBinary方法来相互转化
        • Set preferences

          • 可以用Preferences.Set方法
            • 例如:Preferences.Default.Set("first_name", "John");
        • Get preferences

          • 获取值示例:string firstName = Preferences.Default.Get("first_name", "Unknown");
        • Check for a key

          • 例子:bool hasKey = Preferences.Default.ContainsKey("my_key");
        • Remove one or all keys

          • Preferences.Default.Remove("first_name");
          • Preferences.Default.Clear();
        • Shared keys

          • 可以共享给其他扩展或者手表应用
          • 需要再Set, Get, Remove, Clear传入一个sharedName
        • Integrate with system settings

          • 参考原生文档
        • Platform differences

          • (过)
        • Persistence

          • 卸载应用会抹掉所有偏好设置
            • Android 6.0 (API level 23其后)有Auto Backup可以备份偏好设置
        • Limitations

          • 存储大量数据会降低性能
    • Configure multi-targeting
      • 单个工程可以对标多个平台
      • 工程包含一个Platforms目录,里面的子目录针对每个平台设定
      • 也可以根据文件名和目录结构来对标多平台
      • Configure filename-based multi-targeting

      • Configure folder-based multi-targeting

      • Combine filename and folder multi-targeting

    • Invoke platform code
      • Conditional compilation

        • 可以针对目标平台使用条件编译
          • #if ANDROID
          • #if IOS
          • ……
      • Partial classes and methods

        • Platforms目录盛放平台特定的代码,针对特定目标平台编译时,只会加入相关的代码
        • 这个功能叫做multi-targeting,可以和可分标类以及可分方法结合
          • 将跨平台API定义成一个可分标类,其中定义可分方法
          • 在各平台上实现该API
          • 应调之
        • Define the cross-platform API

        • Implement the API per platform

        • Invoke the cross-platform API

        • Configure multi-targeting

          • 一个标准的multi-targeting的模式时将平台名划入文件名,如DeviceOrientationService.Android.cs
          • 另一个模式是将平台相关文件放到子目录中,比如Android
    • Invoke platform code
    • Native embedding
      • 典型情况下,一个MAUI应用中的页面包含布局(比如grid),布局包含试图(比如Button)
        • 页面、布局、试图等皆是从Eelment派生
      • 在原生应用使用NET的过程
        • 开启MAUI支持,工程中设置<UseMaui>true</UseMaui>
        • 通过调用UseMauiEmbedding初始化MAUI
        • 添加MAUI相关代码
        • 通过ToPlatform扩展方法,将MAUI控件转化为原生类型
        • (原生嵌套时,MAUI的数据绑定引擎依然工作。但是页面导航要使用原生API)
      • (剩余略)
  • Deployment
    • Publish
      • Android
        • Publish with the .NET CLI
          • 形式有Apk(Android Package)以及aab(Android App Bundle)
          • Validate package settings

            • 重要信息包括料包标识符及版本,在 .\Platforms\Android\AndroidManifest.xml中指明,最终使用的版本是通过建构生成的
            • 工程文件中,必须通过在PropertyGroup中声明有
              • <ApplicationId>com.companyname.myproject</ApplicationId>
              • <ApplicationVersion>1</ApplicationVersion>
            • 参考https://developer.android.com/guide/topics/manifest/manifest-intro
          • Create a keystore file

            • Java/Android SDKs包含keytool可以用于签记
            • 命令行如下:
              • keytool -genkey -v -keystore myapp.keystore -alias key -keyalg RSA -keysize 2048 -validity 10000
              • 上述命令会生成myapp.keystore
            • 密钥的信息要填完整,不要为空或Unknown,否则会上传的时候会出现You uploaded an APK or Android App Bundle that was signed in debug mode. You need to sign your APK or Android App Bundle in release mode.

        • Add a reference to the keystore file

          • 工程上要做一些配置
        • Publish

          • 仅支持从.NET命令行发布
          • 执行dotnet publish,可以带以下参数
            • -f--framework,可以是net7.0-android
            • -c--configuratoin,可以是Release
            • /p:AndroidSigningKeyPass
            • /p:AndroidSigningStorePass
          • 一个例子:dotnet publish -f:net7.0-android -c:Release /p:AndroidSigningKeyPass=mypassword /p:AndroidSigningStorePass=mypassword
            • 会在bin\Release\net7.0-android\publish 下生成aab和apk文件
          • 参考https://developer.android.com/studio/publish/upload-bundle
      • Windows
        • Overview
        • Publish with the .NET CLI
          • 含有分叉内容(Publish到不同平台)
          • Create a signing certificate

            • 可以通过Get-ChildItem "Cert:\CurrentUser\My" | Format-Table Subject, FriendlyName, Thumbprint来获取可用的签名
          • Configure the project build settings

            • 工程文件中的内容,其实可以在命令行通过/p:name=value指定
            • 需要在工程文件中添加一些辖属
          • Publish

            • 需要在Developer Command Prompt for VS 2022中操作
            • dotnet publish -f net7.0-windows10.0.19041.0 -c Release /p:RuntimeIdentifierOverride=win10-x64
              • 可能要加上PackageCertificateThumbprint,比如/p:PackageCertificateThumbprint=A10612AF095FD8F8255F4C6691D88F79EF2B135E
          • Installing the app

            • 如何sideload
          • Current limitations

            • 一些限制
              • 无法直接在发布目录以外直接运行可执行文件
              • 必须通过MSIX文件安装后运行
        • Publish with Visual Studio to a folder

What’s New

Enterprise Application Patterns Using .NET MAUI

  • Dependency injection
    • 在构造的时候,以参数的形式传入所需参数
    • 依赖可表现为接口,依赖管理器注册有接口到依赖的映射
    • 有不常用其他形式的依赖注入,比如辖属设置注入,以及方法调用注入等等
    • Introduction to dependency injection

      • 依赖注入时控制反转(IoC)的特定形式
      • 举了一个ProfileViewModel的例子
      • 依赖注入器提供了一个设施,用于例现化标类,以及管理它们的生命周期
      • 使用依赖注入器的一些有点
        • 标类不需要主动定位自己的依赖,以及管理依赖的生命周期
        • 可以给标类提供不同的依赖实现,不影响标类自身
        • 可以提供依赖冒刻
        • 增加可维护性
      • .NET支持多种依赖注入器
      • .NET MAUI
        • MauiProgram调用CreateMauiApp来创建MauiAppBuilder,随有一个Services辖属,类型为IServiceCollection。
        • 任何注册给Sercies辖属的组件会被提供给依赖注入器,时机是MauiAppBuilder.Build调用之时
      • 依赖注入器需要
        • 决定如何例现化一个实现某依赖接口的对象,此为注册(registration)
        • 例现化实现了所需接口的对象,此为化用(resolution)
    • Registration

      • 注册方式有两种
        • 注册类型到注入器
        • 注册现有对象(作为单现例)到注入器
      • 相关方法
        • AddSingleton<T>,将创建一个单现例,在应用的生命周期存续
          • 可以指定具体实现:AddSingleton<TService, TImplementation>
        • AddTransient<T>,将创建一个临时的现例,没有预定义的生命周期,通常随宿主
          • 可以指定具体实现:AddTransient<TService, TImplementation>
      • 所有服务都注册好了之后,可以调用MauiAppBuilder.Build来创建MauiApp
        • Build调用之后,已注册的依赖就固定了
    • Resolution

      • 当一个类型被化用之后,会发生下面之一:
        • 如果类型未注册,则抛出异常
        • 如果类型注册为单现例,会创建之并返回,或者直接返回已有现例
        • 如果类型注册为即时的,注入器会返回一个新现例,但不保存其引用
      • MAUI提供不同化用的办法
      • (剩余略)
    • Summary

      • (过)

其他

Async Programming : Patterns for Asynchronous MVVM Applications: Services

  • 关于MVVM的async和await操作的第三篇
  • Asynchronous Interfaces

    • IMyService接口声明了一个方法Task<int> DownloadAndCountBytesAsync(string url);
    • MyService实现定义了一个方法public async Task<int> DownloadAndCountBytesAsync(string url)
    • 一些lesson
      • awaitable的不是方法,而是类型
      • async是实现细节,(不必出现在接口中)
  • Asynchronous Unit Testing

    • 在非async算函中,可以通过Task.FromResult返回结果
    • 测试async算函需要处理
      • 结果是成功或者失败
      • 结果的返回时同步还是异步
      • 通常处理异步成功、异步失败、同步成功就够了
  • Asynchronous Factories

    • 构造器不能为异步,但是静态的方法可以。所以可以从静态方法异步制造对象。
    • IoC和DI尚不支持此异步制造方法
  • Asynchronous Resources

    • (略)
  • A Misstep

    • (略)
  • The Asynchronous Initialization Pattern

    • IoC/DI frameworks, Activator.CreateInstance等都是基于类型反射的,都假设你的类型有一个构造算函
    • 首先定义一个接口
      public interface IAsyncInitialization
      {
        /// <summary>
        /// The result of the asynchronous initialization of this instance.
        /// </summary>
        Task Initialization { get; }
      }
      
    • 实现中,可以将Initialization的set设为private。
    • 在构造算函中,用异步方法来初始化Initialization辖属
    • 使用者必须await service.Initialization;
    • 更成熟的处理是
      var asyncService = service as IAsyncInitialization;
      if (asyncService != null)
        await asyncService.Initialization;
      
  • Composing the Asynchronous Initialization Pattern

    • 需要一个辅助函数来等待所有依赖的异步初始化
  • Wrapping Up

    • 此模式可以用于ASP.NET或者控制台,以及MVVM应用。

dotnet-maui-check

由于VS自带的Android Device Manger无视代理设置,所以无法下载system image。 这个问题竟然被[dotnet-maui-check]帮着解决了。

安装: dotnet tool install -g Redth.Net.Maui.Check

使用: maui-check

至于下载后安装到何处,尚不明确。 Android Studio默认安装到AppData\Local\Android\Sdk\system-images,不过此中并没有maui-check下载的内容。

只晓得AVD的配置文件可以从.android\avd查看。

dotnet-maui-check

dotnet config

dotnet的可执行文件支持从*.exe.config中读取配置。

[The remote server returned an error: (407) Proxy Authentication Required](The remote server returned an error: (407) Proxy Authentication Required)提到可以添加

<system.net>
    <defaultProxy useDefaultCredentials="true" >
    </defaultProxy>
</system.net>

另一个参考是How to set proxy settings for my application in .NET

具体的规范参考 Element (Network Settings)

但是还是不能让Android SDK Manage或Android Device Manager通过代理访问网络。

Can’t open AVD manager in Visual Studio, internet connection error message看,可以选择Full List Repo,从中安装配件。

Working in Visual Studio behind the Firewall说明怎么在防火墙之后使用Visual Studio。给出的配置例子:

<system.net>
    <settings>
        <ipv6 enabled="true"/>
    </settings>
    <defaultProxy useDefaultCredentials="true" enabled="true">
        <proxy bypassonlocal="true" proxyaddress="http://proxy-server:3128" />
    </defaultProxy>
</system.net>

VS的AndroidSDKManage路径在Microsoft Visual Studio\2022\Community\Common7\IDE\Extensions\Xamarin\AndroidSdkManager

Xamarin\AndroidSdkManager does not work behind a proxy提到可以使用%Userprofiele%.android\androidtool.cfg配置,其中包含内容:

http.proxyPort=8080
http.proxyHost=10.4.103.143
http.proxyLogin=ret2fe@XXXXXXX
sdkman.enable.previews=false
sdkman.ask.adb.restart=false
sdkman.show.update.only=false
sdkman.force.http=true
sdkman.use.dl.cache=true

其参考为https://learn.microsoft.com/en-us/archive/blogs/peterhauge/visual-studio-2015-install-failures-android-sdk-setup-behind-a-proxy

参考链接

(底部)