想要完整的工作範例,請參考本資料庫中的 Flutter 範例 。
本指南示範如何在 Flutter 應用程式中使用 winapp CLI,加入套件識別碼,並將應用程式打包成 MSIX。
套件身份是 Windows app 模型中的核心概念。 它讓你的應用程式能存取特定的 Windows API(例如通知、安全、AI API 等)、乾淨安裝/卸載體驗等等。
標準的 Flutter Windows 版本沒有套件識別碼。 本指南說明如何新增它用於除錯,然後再打包以供發佈。
先決條件
Flutter SDK:請依 照官方指南安裝 Flutter。
winapp CLI:透過 Winget 安裝
winappCLI(若已安裝則更新):winget install Microsoft.winappcli --source winget
1. 建立新的 Flutter 應用程式
請依照官方 Flutter 文件的指南建立新應用程式並執行。
你應該會看到預設的 Flutter 計數器應用程式。
2. 更新代碼以驗證身份
我們會更新應用程式,以檢查它是否運行於具有套件識別的狀態。 我們會使用 Dart FFI 來呼叫 Windows GetCurrentPackageFamilyName API。
首先,加入 ffi 這個包裹:
flutter pub add ffi
接著,將 的內容 lib/main.dart 替換成以下程式碼。 此程式碼嘗試使用 Windows API 取得目前的套件識別碼。 若成功,則會在使用者介面中顯示套件族名稱;否則,會顯示「未包裝」。
import 'dart:ffi';
import 'dart:io' show Platform;
import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';
/// Returns the Package Family Name if running with package identity, or null.
String? getPackageFamilyName() {
if (!Platform.isWindows) return null;
final kernel32 = DynamicLibrary.open('kernel32.dll');
final getCurrentPackageFamilyName = kernel32.lookupFunction<
Int32 Function(Pointer<Uint32>, Pointer<Uint16>),
int Function(
Pointer<Uint32>, Pointer<Uint16>)>('GetCurrentPackageFamilyName');
final length = calloc<Uint32>();
try {
// First call to get required buffer length
final result =
getCurrentPackageFamilyName(length, Pointer<Uint16>.fromAddress(0));
if (result != 122) return null; // ERROR_INSUFFICIENT_BUFFER = 122
// Second call with buffer to get the name
final namePtr = calloc<Uint16>(length.value);
try {
final result2 = getCurrentPackageFamilyName(length, namePtr);
if (result2 == 0) {
return namePtr.cast<Utf16>().toDartString(); // ERROR_SUCCESS = 0
}
return null;
} finally {
calloc.free(namePtr);
}
} finally {
calloc.free(length);
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
late final String? _packageFamilyName;
@override
void initState() {
super.initState();
_packageFamilyName = getPackageFamilyName();
}
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.only(bottom: 24),
decoration: BoxDecoration(
color: _packageFamilyName != null
? Colors.green.shade50
: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: _packageFamilyName != null
? Colors.green
: Colors.orange,
),
),
child: Text(
_packageFamilyName != null
? 'Package Family Name:\n$_packageFamilyName'
: 'Not packaged',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.bodyLarge,
),
),
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
3. 無身份運行
現在,照常建立並執行應用程式:
flutter build windows
直接執行執行檔(如果不同,請替換 flutter_app 成你的專案名稱):
.\build\windows\x64\runner\Release\flutter_app.exe
小提示
建置輸出會放在 x64 資料夾裡,不管你的機器架構如何——這是 Flutter Windows 建置時預期的。
你應該會看到應用程式顯示橘色的「未包裝」指示。 這確認了標準執行檔在沒有任何套件身份的情況下執行。
4. 使用 Winapp CLI 初始化 Project
winapp init 指令一次設定你需要的所有東西:應用程式清單、資產,以及可選的 Windows 應用程式 SDK C++ 開發標頭。 清單定義了你應用程式的身份(名稱、發佈者、版本),Windows 用來授權 API 存取。
執行以下指令並依照提示操作:
winapp init
出現提示時:
- 套件名稱:按 Enter 接受預設名稱(源自你的專案名稱)
- Publisher name:按 Enter 接受預設或輸入你的名字
- 版本:按下 "Enter" 以接受 1.0.0.0
- Description:按 Enter 接受預設(Windows 應用程式)
- Setup SDKs:選擇「Stable SDK」下載 Windows 應用程式 SDK 並產生 C++ 標頭(步驟 6 需要)
這個命令將會執行以下作業:
- Create
Package.appxmanifest— 定義你應用程式身份的清單 - 建立
Assets資料夾 — MSIX 包裝與商店提交所需的圖示 - 建立一個
.winapp資料夾,裡面有Windows 應用程式 SDK標頭和函式庫 - 建立一個
winapp.yaml設定檔來鎖定 SDK 版本
你可以開啟 Package.appxmanifest 來進一步自訂屬性,例如顯示名稱、發佈者和功能。
5. 利用身份驗證除錯
若要測試需要身份識別的功能(例如通知),但不完全打包應用程式,你可以使用 winapp run。 這會註冊一個鬆散的版面套件(就像真正的 MSIX 安裝一樣),然後一步啟動應用程式。 除錯不需要憑證或簽署。
建立應用程式:
flutter build windows以身份行事:
winapp run .\build\windows\x64\runner\Release
小提示
winapp run 同時也會在你的系統上註冊包裹。 這也是為什麼當你在第 7 步嘗試安裝時,MSIX 可能會顯示為「已安裝」。 完成後,請使用 winapp unregister 清理開發套件。
你現在應該會看到應用程式顯示綠色指示燈:
Package Family Name: flutterapp.debug_xxxxxxxx
這能確認你的應用程式是以有效的套件身份執行!
小提示
關於進階除錯工作流程(附加除錯器、IDE 設定、啟動除錯),請參閱 除錯指南。
6. 使用 Windows 應用程式 SDK(可選)
如果你選擇在 winapp init 時設定 SDK,現在你就能在 .winapp/include 資料夾中存取Windows 應用程式 SDK C++ 標頭。 由於 Flutter 的 Windows 執行程式是 C++,你可以從原生程式碼呼叫 Windows 應用程式 SDK API,並透過方法通道將它們暴露給 Dart。 如果您只需要發佈時的套件識別,可跳至步驟 7。
我們來加一個簡單的範例,顯示 Windows 應用程式 執行時版本。
建立原生外掛
創建 windows/runner/winapp_sdk_plugin.h:
#ifndef RUNNER_WINAPP_SDK_PLUGIN_H_
#define RUNNER_WINAPP_SDK_PLUGIN_H_
#include <flutter/flutter_engine.h>
// Registers a method channel for querying Windows App SDK info.
void RegisterWinAppSdkPlugin(flutter::FlutterEngine* engine);
#endif // RUNNER_WINAPP_SDK_PLUGIN_H_
創建 windows/runner/winapp_sdk_plugin.cpp:
#include "winapp_sdk_plugin.h"
#include <flutter/method_channel.h>
#include <flutter/standard_method_codec.h>
#include <winrt/Microsoft.Windows.ApplicationModel.WindowsAppRuntime.h>
#include <string>
void RegisterWinAppSdkPlugin(flutter::FlutterEngine* engine) {
auto channel = std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
engine->messenger(), "com.example/winapp_sdk",
&flutter::StandardMethodCodec::GetInstance());
channel->SetMethodCallHandler(
[](const flutter::MethodCall<flutter::EncodableValue>& call,
std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
if (call.method_name() == "getRuntimeVersion") {
try {
// Flutter already initializes COM in main.cpp, so we skip
// winrt::init_apartment() here — the apartment is already set up.
auto version = winrt::Microsoft::Windows::ApplicationModel::
WindowsAppRuntime::RuntimeInfo::AsString();
std::string versionStr = winrt::to_string(version);
result->Success(flutter::EncodableValue(versionStr));
} catch (const winrt::hresult_error& e) {
result->Error("WINRT_ERROR", winrt::to_string(e.message()));
} catch (...) {
result->Error("UNKNOWN_ERROR",
"Failed to get Windows App Runtime version");
}
} else {
result->NotImplemented();
}
});
// prevent channel destruction by releasing ownership
channel.release();
}
更新 CMakeLists.txt
編輯 windows/runner/CMakeLists.txt 以進行三項更改。 找到該 add_executable 區塊並加入 "winapp_sdk_plugin.cpp" 原始檔案清單:
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"utils.cpp"
"win32_window.cpp"
"winapp_sdk_plugin.cpp" # <-- add this line
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
接著在檔案末尾加上這兩行,連結 WinRT 函式庫並包含 Windows 應用程式 SDK 標頭:
# Link Windows Runtime libraries for WinRT
target_link_libraries(${BINARY_NAME} PRIVATE "WindowsApp.lib")
# Windows App SDK headers from winapp CLI
target_include_directories(${BINARY_NAME} PRIVATE
"${CMAKE_SOURCE_DIR}/../.winapp/include")
註冊外掛
在 windows/runner/flutter_window.cpp中,將檔案頂端的 include 與其他 include 一起加入:
#include "winapp_sdk_plugin.h"
然後找到RegisterPlugins中的呼叫FlutterWindow::OnCreate(),並在它後面的那行上加上RegisterWinAppSdkPlugin:
RegisterPlugins(flutter_controller_->engine());
RegisterWinAppSdkPlugin(flutter_controller_->engine()); // <-- add this line
更新 main.dart
請在 lib/main.dart 的最上方新增以下匯入,並與現有的匯入並列:
import 'package:flutter/services.dart';
在現有getPackageFamilyName()函式下方(且確保不在任何類別內)加入此函式:
/// Queries the Windows App Runtime version via a native method channel.
Future<String?> getWindowsAppRuntimeVersion() async {
if (!Platform.isWindows) return null;
try {
const channel = MethodChannel('com.example/winapp_sdk');
final version = await channel.invokeMethod<String>('getRuntimeVersion');
return version;
} catch (_) {
return null;
}
}
在該 _MyHomePageState 類別中,在現有 _packageFamilyName欄位旁新增一個欄位:
late final String? _packageFamilyName;
String? _runtimeVersion; // <-- add this line
更新 initState() 以呼叫這個新函式:
@override
void initState() {
super.initState();
_packageFamilyName = getPackageFamilyName();
// Fetch the runtime version asynchronously
getWindowsAppRuntimeVersion().then((version) {
setState(() {
_runtimeVersion = version;
});
});
}
最後,在方法中顯示執行時版本 build 。 在Column子清單中加入這個小工具,緊接著Container顯示套件識別碼:
if (_runtimeVersion != null)
Padding(
padding: const EdgeInsets.only(bottom: 16),
child: Text(
'Windows App Runtime: $_runtimeVersion',
style: Theme.of(context).textTheme.bodyLarge,
),
),
建置並執行
重建應用程式:
flutter build windows
winapp run .\build\windows\x64\runner\Release
你現在應該會看到輸出如下:
Package Family Name: flutterapp.debug_xxxxxxxx
Windows App Runtime: 8000.731.1532.0
.winapp/include 目錄包含所有為 Windows 應用程式 SDK 所需的必要標頭。
-
winrt/- 用於存取Windows 執行階段 API 的 WinRT C++ 投影標頭 -
Microsoft.UI.*.h- 用於現代 UI 元件的 WinUI 3 標頭 -
MddBootstrap.h- Windows 應用程式 SDK 自助模式 -
WindowsAppSDK-VersionInfo.h- 版本資訊 - 以及更多 Windows 應用程式 SDK 元件
想了解更進階的Windows 應用程式 SDK使用,請參考 Windows 應用程式 SDK 文件。
7. MSIX 封裝
當你準備好發佈應用程式時,可以用相同的清單打包成 MSIX。
準備套件目錄
首先,先在發佈模式下建置你的應用程式:
flutter build windows
接著,建立一個包含你發行檔案的目錄:
mkdir dist
copy .\build\windows\x64\runner\Release\* .\dist\ -Recurse
Flutter Windows 建置輸出包含執行檔、flutter_windows.dll 以及 data 資料夾——這些都是必備的。
產生開發證書
在包裝前,你需要一份開發證書才能簽名。 如果你還沒生成,請生成一個:
winapp cert generate --if-exists skip
簽名與包裝
現在你可以打包並簽名:
winapp pack .\dist --cert .\devcert.pfx
注意:
pack指令會自動使用你目前目錄中的Package.appxmanifest,並在打包前將其複製到目標資料夾。
安裝憑證
在安裝 MSIX 套件之前,你需要先信任你機器上的開發憑證。 以管理員身份執行這個指令(你只需要在每個憑證上執行一次):
winapp cert install .\devcert.pfx
安裝並執行
小提示
如果你在步驟5使用 winapp run 過,包裹可能已經在你的系統上註冊了。 使用 winapp unregister 先移除開發註冊,然後再安裝正式發行套件。
請雙擊產生 .msix 的檔案,或使用 PowerShell 安裝套件:
Add-AppxPackage .\flutterapp.msix
小提示
MSIX 檔名包含版本與架構(例如 flutterapplication1_1.0.0.0_x64.msix)。 請檢查你的目錄,確認確切的檔名。 如果程式碼變更後需要重新打包,請在 Version 中增加 Package.appxmanifest — Windows 需要更高的版本號才能更新已安裝的套件。
Tips
- 一旦準備好分發,你可以用憑證授權中心的程式碼簽署憑證來簽署 MSIX,讓使用者不必安裝自簽憑證。
- Azure 信任簽署 服務是安全管理憑證並將簽約整合進 CI/CD 管線的絕佳方式。
- Microsoft Store 會幫你簽署 MSIX,提交前不需要簽名。
後續步驟
- 透過 winget 發佈:請將您的 MSIX 提交至 Windows 封裝管理員 社群倉庫
-
發佈至Microsoft Store:請使用
winapp store提交您的套件 -
設定 CI/CD:使用
setup-WinAppCliGitHub Action 在管線中自動化打包 - 探索Windows API:透過套件身份,你現在可以使用Notifications、on-device AI,以及其他依賴 身份相關的 API