启动器

浏览示例。 浏览示例

本文介绍如何使用 .NET Multi-platform App UI (.NET MAUI) 接口。 此接口使应用程序能够通过系统打开 URI。 当深层链接到另一个应用程序的自定义 URI 方案时,通常使用这种打开应用程序的方式。

ILauncher 接口的默认实现可通过 Launcher.Default 属性获得。 ILauncher 接口和 Launcher 类都包含在 Microsoft.Maui.ApplicationModel 命名空间中。

重要

若要使用浏览器访问网站,请改用 Browser API。

入门

若要访问启动器功能,需要以下特定于平台的设置。

如果要使用深层链接打开其他 Android 应用,则应在应用中定义意向筛选器。 这可以通过将以下 XML 添加到 平台/Android/AndroidManifest.xml 文件来实现:

<activity android:name="appName" android:exported="true">
    <intent-filter>
       <action android:name="android.intent.action.VIEW" />
       <category android:name="android.intent.category.DEFAULT" />
       <category android:name="android.intent.category.BROWSABLE" />
       <data android:scheme="lyft"/>
       <data android:scheme="fb"/>
       </intent-filter>
</activity>

<data> 元素是在您的应用中预注册的 URI 方案/协定。 不能使用意向筛选器中未定义的方案。

若要让应用程序可以在其他应用中进行浏览,请通过使用带有 android:scheme 属性的 <data> 元素来声明。

<data android:scheme="appName"/>

打开另一个应用

若要使用 Launcher 功能,请调用 ILauncher.OpenAsync 方法,并传入表示要打开的应用的 StringUri。 (可选)可以使用 ILauncher.CanOpenAsync(Uri) 方法来检查 URI 方案是否可以由设备上的应用处理。 以下代码演示如何检查 URI 方案是否受支持,然后打开 URI:

bool supportsUri = await Launcher.Default.CanOpenAsync("lyft://");

if (supportsUri)
    await Launcher.Default.OpenAsync("lyft://ridetype?id=lyft_line");

可以使用 TryOpenAsync(Uri)简化前面的代码示例,该示例检查是否可以打开 URI 方案,然后再打开它:

bool launcherOpened = await Launcher.Default.TryOpenAsync("lyft://ridetype?id=lyft_line");

if (launcherOpened)
{
    // Do something fun
}

通过文件打开另一个应用

启动器还可用于使用所选文件打开应用。 .NET MAUI 会自动检测文件类型(MIME),并打开该文件类型的默认应用。 如果向文件类型注册了多个应用,则会向用户显示应用选择弹出窗口。

下面的代码示例将文本写入文件,并使用启动器打开文本文件:

string popoverTitle = "Read text file";
string name = "File.txt";
string file = System.IO.Path.Combine(FileSystem.CacheDirectory, name);

System.IO.File.WriteAllText(file, "Hello World");

await Launcher.Default.OpenAsync(new OpenFileRequest(popoverTitle, new ReadOnlyFile(file)));

文件位置控制

重要

本部分仅适用于 Android。

在 Android 上的某些方案中,例如,当文件位于专用存储中时,可以将其复制到应用缓存中,然后通过 Android FileProvider进行共享。 但是,这可能会无意中将整个缓存和应用程序的数据暴露给攻击者。 这可以通过在应用中添加一个文件提供者路径覆盖文件,并确保在共享之前将文件复制到该文件中指定的位置,从而防止出现此问题。

若要将文件提供程序文件路径替代文件添加到应用,请将名为 microsoft_maui_essentials_fileprovider_file_paths.xml 的文件添加到应用中的 Platforms\Android\Resources\xml 文件夹中。 因此,项目的完整相对文件名应 平台\Android\Resources\xml\microsoft_maui_essentials_fileprovider_file_paths.xml。 然后,为所需的路径将 XML 添加到文件中:

 <?xml version="1.0" encoding="UTF-8" ?>
 <paths>
    <external-path name="external_files" path="sharing-root" />
    <cache-path name="internal_cache" path="sharing-root" />
    <external-cache-path name="external_cache" path="sharing-root" />  
 </paths>

有关文件提供程序路径的详细信息,请参阅 developer.android.com 上的 FileProvider

在共享文件之前,应确保先将其写入到替代文件中某个位置中的 共享根 文件夹中:

// Write into the specific sub-directory
var dir = Path.Combine(FileSystem.CacheDirectory, "sharing-root");  
Directory.CreateDirectory(dir);
var file = Path.Combine(dir, "mydata.txt");
await File.WriteAllTextAsync(file, $"My data: {count}");

// Share the file
await Launcher.OpenAsync(new OpenFileRequest
{
   Title = "My data",
   File = new ReadOnlyFile(file),
});

如果共享 URI 排除共享根目录,则可以验证文件是否正确共享。 例如,如果共享文件 <CacheDirectory>/sharing-root/mydata.txt 并且共享 URI content://com.companyname.overwritefileproviderpaths.fileProvider/internal_cache/sharing-root/mydata.txt 则文件提供程序未使用正确的路径。 如果共享 URI content://com.companyname.overwritefileproviderpaths.fileProvider/internal_cache/mydata.txt,则文件提供程序正在使用正确的路径。

警告

共享文件时,如果收到 Java.Lang.IllegalArgumentException,并且消息类似于“找不到包含 /data/data/com.companyname.overwritefileproviderpaths/cache/some-non-sharing-path/mydata.txt”的内容,则很可能是您正在共享超出了共享根的文件。

设置启动器位置

重要

本部分仅适用于 iPadOS。

在 iPadOS 上请求共享或打开启动器时,可以在弹出窗口中显示它。 这指定弹出窗口的显示位置,并将箭头直接指向。 此位置通常是启动动作的控件。 可以使用 PresentationSourceBounds 属性指定位置:

await Share.RequestAsync(new ShareFileRequest
    {
        Title = Title,
        File = new ShareFile(file),
        PresentationSourceBounds = DeviceInfo.Platform == DevicePlatform.iOS && DeviceInfo.Idiom == DeviceIdiom.Tablet
                                ? new Rect(0, 20, 0, 0)
                                : Rect.Zero
    });
await Launcher.OpenAsync(new OpenFileRequest
    {
        File = new ReadOnlyFile(file),
        PresentationSourceBounds = DeviceInfo.Platform == DevicePlatform.iOS && DeviceInfo.Idiom == DeviceIdiom.Tablet
                                ? new Rect(0, 20, 0, 0)
                                : Rect.Zero
    });

此处所述的一切同样适用于 ShareLauncher

下面是一种扩展方法,可帮助计算视图的边界:

public static class ViewHelpers
{
    public static Rect GetAbsoluteBounds(this Microsoft.Maui.Controls.View element)
    {
        Element looper = element;

        var absoluteX = element.X + element.Margin.Top;
        var absoluteY = element.Y + element.Margin.Left;

        // Add logic to handle titles, headers, or other non-view bars

        while (looper.Parent != null)
        {
            looper = looper.Parent;
            if (looper is Microsoft.Maui.Controls.View v)
            {
                absoluteX += v.X + v.Margin.Top;
                absoluteY += v.Y + v.Margin.Left;
            }
        }

        return new Rect(absoluteX, absoluteY, element.Width, element.Height);
    }
}

然后,调用 RequestAsync时可以使用:

public Command<Microsoft.Maui.Controls.View> ShareCommand { get; } = new Command<Microsoft.Maui.Controls.View>(Share);

async void Share(Microsoft.Maui.Controls.View element)
{
    try
    {
        await Share.Default.RequestAsync(new ShareTextRequest
        {
            PresentationSourceBounds = element.GetAbsoluteBounds(),
            Title = "Title",
            Text = "Text"
        });
    }
    catch (Exception)
    {
        // Handle exception that share failed
    }
}

触发 Command 时,您可以传入调用元素:

<Button Text="Share"
        Command="{Binding ShareWithFriendsCommand}"
        CommandParameter="{Binding Source={RelativeSource Self}}"/>

有关 ViewHelpers 类的示例,请参阅 GitHub 上托管的.NET MAUI 示例。

平台差异

本部分介绍与启动器 API 的平台特定差异。

CanOpenAsync 返回的 Task 会立即完成。