在您的 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 來查詢裝置的相關屬性集,以建置更自定義的零售示範體驗。 這些屬性包括 ManufacturerName、Screensize、Memory 等等。
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 和商店應用程式都會關閉
- 刪除已知資料夾中的所有檔案,例如 Pictures、Videos、Music、Documents、SavedPictures、CameraRoll、Desktop 和 Downloads 資料夾
- 非結構化和結構化漫遊狀態會遭到刪除
- 結構化的本機狀態已被刪除
步驟 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;
}
}
}