共用方式為


將零售示範 (RDX) 功能新增至您的應用程式

在您的 Windows 應用程式中包含零售示範模式,方便到店面體驗電腦和設備的顧客能立即開始使用。

當客戶在零售商店時,他們期望能夠試用計算機和裝置的示範。 他們經常透過 零售示範體驗(RDX),花上相當多的時間玩應用程式。

您可以設定您的應用程式在 一般零售 模式下,提供不同的體驗。 例如,如果您的 app 是以設定程序開始,您可以在零售模式中跳過設定過程,並使用範例資料和預設設定預先填入應用程式,以便使用者可以立即開始使用。

從客戶的觀點來看,只有一個應用程式。 為了協助客戶區分這兩種模式,我們建議您在您的應用程式處於零售模式時,它會在標題列或適當位置以醒目方式顯示「零售」一詞。

除了 Microsoft Store 對應用程式的需求之外,RDX 相容的應用程式也必須與 RDX 的設定、清理和更新程序相容,以確保客戶在零售商店有一致的正面體驗。

設計原則

  • 展示你最好的一面。 使用零售示範體驗來展示您的應用程式有多出色。 這可能是客戶第一次看到您的應用程式,所以請向他們顯示最佳作品!

  • 快速地顯示它。 客戶可能不耐煩 - 使用者越快就能體驗到您應用程式的實際價值,越好。

  • 讓故事保持簡單。 零售示範體驗是用來簡短推介您應用程式價值的機會。

  • 專注於體驗。 讓用戶有時間消化您的內容。 雖然讓他們快速進入最佳部分很重要,但設計適當的暫停可以幫助他們充分享受體驗。

技術需求

由於 RDX 感知應用程式旨在向零售客戶展示您應用程式的最佳功能,因此他們必須符合技術需求,並遵守 Microsoft Store 針對所有零售示範體驗應用程式所具備的隱私權法規。

這可用來做為檢查清單,協助您準備驗證程式,並在測試程式中提供清楚性。 請注意,這些需求必須維持,而不只是針對驗證過程,而是針對零售示範體驗應用程式的整個生命周期,只要您的應用程式持續在零售示範裝置上執行。

重要需求

不符合這些重要需求的 RDX 感知應用程式將會儘快從所有零售示範裝置中移除。

  • 不要要求個人標識資訊(PII)。 這包括登入資訊、Microsoft帳戶資訊或聯繫人詳細數據。

  • 無錯誤體驗。 您的應用程式必須執行,且沒有任何錯誤。 此外,不應向使用零售示範裝置的客戶顯示任何錯誤快顯或通知。 錯誤會對應用程式本身、您的品牌、裝置的品牌、裝置製造商的品牌,以及Microsoft的品牌產生負面影響。

  • 付費應用程式必須具有試用版模式。 您的應用程式必須是免費應用程式,或包含 試用版模式。 顧客並不想為在零售店的服務而付費。

高優先順序需求

不符合這些高優先順序需求的 RDX 感知應用程式需要立即進行修正和解決。 如果找不到立即修正程式,則此應用程式可能會從所有零售示範裝置中移除。

  • 令人難忘的離線體驗。 您的應用程式需要示範絕佳的離線體驗,因為大約50% 裝置在零售位置離線。 這是為了確保與您的應用程式在離線狀態下互動的客戶,仍然能夠獲得有意義且正面的體驗。

  • 更新的內容體驗。 您的應用程式在網路連線時絕不應提示更新。 如果需要更新,應該以靜默方式執行。

  • 沒有匿名通訊。 由於使用零售示範裝置的客戶是匿名使用者,因此他們不應該能夠從裝置傳送訊息或共用內容。

  • 使用清除程式提供一致的體驗。 當每位客戶走上零售示範裝置時,都應該有相同的體驗。 您的應用程式應該使用 清理過程,在每次使用後恢復到相同的預設狀態。 我們不希望下一個客戶看到最後一個客戶留下什麼。 這包括計分牌、成就和解鎖。

  • 符合年齡的內容。 所有應用程式內容都必須獲指派青少年或較低評等類別。 若要深入瞭解,請參閱 關於 IARC 為您的應用程式評等ESRB 評分

中優先順序需求

Windows 商店團隊可能會直接聯絡開發人員,以安排討論如何解決這些問題。

  • 能夠在一系列裝置上順利執行,。 應用程式必須在所有裝置上正常執行,包括具有低端規格的裝置。 如果應用程式安裝在不符合最低規格的裝置上,應用程式必須清楚告知使用者有關此情況。 必須知道最低裝置需求,讓應用程式一律能以高效能執行。

  • 符合零售商店應用程式大小需求,。 應用程式必須小於 800MB。 如果您的 RDX 感知應用程式不符合尺寸要求,請直接連絡 Windows 零售市集小組以進一步討論。

RetailInfo API:準備您的程式代碼以示範模式

是否啟用展示模式

RetailInfo 公用程式類別中的 IsDemoModeEnabled 屬性是 Windows 10 和 Windows 11 SDK 中 Windows.System.Profile 命名空間的一部分,可用作布林指標,以指定 app 執行所在的程式代碼路徑,即 一般 模式或 零售 模式。

using Windows.Storage;

StorageFolder folder = ApplicationData.Current.LocalFolder;

if (Windows.System.Profile.RetailInfo.IsDemoModeEnabled) 
{
    // Use the demo specific directory
    folder = await folder.GetFolderAsync("demo");
}

StorageFile file = await folder.GetFileAsync("hello.txt");
// Now read from file
using namespace Windows::Storage;

StorageFolder^ localFolder = ApplicationData::Current->LocalFolder;

if (Windows::System::Profile::RetailInfo::IsDemoModeEnabled) 
{
    // Use the demo specific directory
    create_task(localFolder->GetFolderAsync("demo").then([this](StorageFolder^ demoFolder)
    {
        return demoFolder->GetFileAsync("hello.txt");
    }).then([this](task<StorageFile^> fileTask)
    {
        StorageFile^ file = fileTask.get();
    });
    // Do something with file
}
else
{
    create_task(localFolder->GetFileAsync("hello.txt").then([this](StorageFile^ file)
    {
        // Do something with file
    });
}
if (Windows.System.Profile.retailInfo.isDemoModeEnabled) {
    console.log("Retail mode is enabled.");
} else {
    Console.log("Retail mode is not enabled.");
}

RetailInfo.Properties

IsDemoModeEnabled 傳回 true 時,您可以使用 RetailInfo.Properties 來查詢裝置的相關屬性集,以建置更自定義的零售示範體驗。 這些屬性包括 ManufacturerNameScreensizeMemory 等等。

using Windows.UI.Xaml.Controls;
using Windows.System.Profile

TextBlock priceText = new TextBlock();
priceText.Text = RetailInfo.Properties[KnownRetailInfo.Price];
// Assume infoPanel is a StackPanel declared in XAML
this.infoPanel.Children.Add(priceText);
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::System::Profile;

TextBlock ^manufacturerText = ref new TextBlock();
manufacturerText.set_Text(RetailInfo::Properties[KnownRetailInfoProperties::Price]);
// Assume infoPanel is a StackPanel declared in XAML
this->infoPanel->Children->Add(manufacturerText);
var pro = Windows.System.Profile;
console.log(pro.retailInfo.properties[pro.KnownRetailInfoProperties.price);

介面定義語言 (IDL)

//  Copyright (c) Microsoft Corporation. All rights reserved.
//
//  WindowsRuntimeAPISet

import "oaidl.idl";
import "inspectable.idl";
import "Windows.Foundation.idl";
#include <sdkddkver.h>

namespace Windows.System.Profile
{
    runtimeclass RetailInfo;
    runtimeclass KnownRetailInfoProperties;

    [version(NTDDI_WINTHRESHOLD), uuid(0712C6B8-8B92-4F2A-8499-031F1798D6EF), exclusiveto(RetailInfo)]
    [version(NTDDI_WINTHRESHOLD, Platform.WindowsPhone)]
    interface IRetailInfoStatics : IInspectable
    {
        [propget] HRESULT IsDemoModeEnabled([out, retval] boolean *value);
        [propget] HRESULT Properties([out, retval, hasvariant] Windows.Foundation.Collections.IMapView<HSTRING, IInspectable *> **value);
    }

    [version(NTDDI_WINTHRESHOLD), uuid(50BA207B-33C4-4A5C-AD8A-CD39F0A9C2E9), exclusiveto(KnownRetailInfoProperties)]
    [version(NTDDI_WINTHRESHOLD, Platform.WindowsPhone)]
    interface IKnownRetailInfoPropertiesStatics : IInspectable
    {
        [propget] HRESULT RetailAccessCode([out, retval] HSTRING *value);
        [propget] HRESULT ManufacturerName([out, retval] HSTRING *value);
        [propget] HRESULT ModelName([out, retval] HSTRING *value);
        [propget] HRESULT DisplayModelName([out, retval] HSTRING *value);
        [propget] HRESULT Price([out, retval] HSTRING *value);
        [propget] HRESULT IsFeatured([out, retval] HSTRING *value);
        [propget] HRESULT FormFactor([out, retval] HSTRING *value);
        [propget] HRESULT ScreenSize([out, retval] HSTRING *value);
        [propget] HRESULT Weight([out, retval] HSTRING *value);
        [propget] HRESULT DisplayDescription([out, retval] HSTRING *value);
        [propget] HRESULT BatteryLifeDescription([out, retval] HSTRING *value);
        [propget] HRESULT ProcessorDescription([out, retval] HSTRING *value);
        [propget] HRESULT Memory([out, retval] HSTRING *value);
        [propget] HRESULT StorageDescription([out, retval] HSTRING *value);
        [propget] HRESULT GraphicsDescription([out, retval] HSTRING *value);
        [propget] HRESULT FrontCameraDescription([out, retval] HSTRING *value);
        [propget] HRESULT RearCameraDescription([out, retval] HSTRING *value);
        [propget] HRESULT HasNfc([out, retval] HSTRING *value);
        [propget] HRESULT HasSdSlot([out, retval] HSTRING *value);
        [propget] HRESULT HasOpticalDrive([out, retval] HSTRING *value);
        [propget] HRESULT IsOfficeInstalled([out, retval] HSTRING *value);
        [propget] HRESULT WindowsVersion([out, retval] HSTRING *value);
    }

    [version(NTDDI_WINTHRESHOLD), static(IRetailInfoStatics, NTDDI_WINTHRESHOLD)]
    [version(NTDDI_WINTHRESHOLD, Platform.WindowsPhone), static(IRetailInfoStatics, NTDDI_WINTHRESHOLD, Platform.WindowsPhone)]
    [threading(both)]
    [marshaling_behavior(agile)]
    runtimeclass RetailInfo
    {
    }

    [version(NTDDI_WINTHRESHOLD), static(IKnownRetailInfoPropertiesStatics, NTDDI_WINTHRESHOLD)]
    [version(NTDDI_WINTHRESHOLD, Platform.WindowsPhone), static(IKnownRetailInfoPropertiesStatics, NTDDI_WINTHRESHOLD, Platform.WindowsPhone)]
    [threading(both)]
    [marshaling_behavior(agile)]
    runtimeclass KnownRetailInfoProperties
    {
    }
}

清理過程

清理會在購物者停止與裝置互動兩分鐘后開始。 零售示範會播放,而 Windows 會開始重設聯繫人、相片和其他應用程式中的任何範例數據。 視裝置而定,這可能需要 1 至 5 分鐘的時間,才能將所有設定完全重設為正常。 這確保在零售商店中的每位客戶都能隨時使用裝置,並在與裝置互動時得到相同的體驗。

步驟 1:清除

  • 所有 Win32 和商店應用程式都會關閉
  • 刪除已知資料夾中的所有檔案,例如 PicturesVideosMusicDocumentsSavedPicturesCameraRollDesktopDownloads 資料夾
  • 非結構化和結構化漫遊狀態會遭到刪除
  • 結構化的本機狀態已被刪除

步驟 2:設定

  • 離線裝置:資料夾維持空白
  • 在線裝置:零售示範資產可以從 Microsoft 市集推送至裝置

跨用戶會話儲存數據

若要跨使用者會話儲存數據,您可以將資訊儲存在 applicationData.Current.TemporaryFolder 中,因為預設清除程式不會自動刪除此資料夾中的數據。 請注意,使用 LocalState 儲存的資訊會在清理過程中被刪除。

自訂清理流程

若要自定義清除程式,請將 Microsoft-RetailDemo-Cleanup 應用程式服務實作到您的應用程式。

需要自定義清除邏輯的案例包括執行大量設定、下載和快取數據,或不想刪除 LocalState 數據。

步驟 1:在應用程式指令清單中宣告 Microsoft-RetailDemo-Cleanup 服務。

  <Applications>
      <Extensions>
        <uap:Extension Category="windows.appService" EntryPoint="MyCompany.MyApp.RDXCustomCleanupTask">
          <uap:AppService Name="Microsoft-RetailDemo-Cleanup" />
        </uap:Extension>
      </Extensions>
   </Application>
  </Applications>

步驟 2:使用下列範例範本,在 AppdataCleanup 案例函式底下實作自定義清除邏輯。

using System;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.Foundation.Collections;
using Windows.Storage;

namespace MyCompany.MyApp
{
    public sealed class RDXCustomCleanupTask : IBackgroundTask
    {
        BackgroundTaskCancellationReason _cancelReason = BackgroundTaskCancellationReason.Abort;
        BackgroundTaskDeferral _deferral = null;
        IBackgroundTaskInstance _taskInstance = null;
        AppServiceConnection _appServiceConnection = null;

        const string MessageCommand = "Command";

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // Get the deferral object from the task instance, and take a reference to the taskInstance;
            _deferral = taskInstance.GetDeferral();
            _taskInstance = taskInstance;
            _taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);

            AppServiceTriggerDetails appService = _taskInstance.TriggerDetails as AppServiceTriggerDetails;
            if ((appService != null) && (appService.Name == "Microsoft-RetailDemo-Cleanup"))
            {
                _appServiceConnection = appService.AppServiceConnection;
                _appServiceConnection.RequestReceived += _appServiceConnection_RequestReceived;
                _appServiceConnection.ServiceClosed += _appServiceConnection_ServiceClosed;
            }
            else
            {
                _deferral.Complete();
            }
        }

        void _appServiceConnection_ServiceClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
        {
        }

        async void _appServiceConnection_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            //Get a deferral because we will be calling async code
            AppServiceDeferral requestDeferral = args.GetDeferral();
            string command = null;
            var returnData = new ValueSet();

            try
            {
                ValueSet message = args.Request.Message;
                if (message.ContainsKey(MessageCommand))
                {
                    command = message[MessageCommand] as string;
                }

                if (command != null)
                {
                    switch (command)
                    {
                        case "AppdataCleanup":
                            {
                                // Do custom clean up logic here
                                break;
                            }
                    }
                }
            }
            catch (Exception e)
            {
            }
            finally
            {
                requestDeferral.Complete();
                // Also release the task deferral since we only process one request per instance.
                _deferral.Complete();
            }
        }

        private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            _cancelReason = reason;
        }
    }
}