Udostępnij za pośrednictwem


Korzystanie z interfejsu wiersza polecenia winapp z platformą Flutter

W tym przewodniku pokazano, jak używać interfejsu wiersza polecenia winapp z aplikacją Flutter w celu dodania tożsamości pakietu i spakowania aplikacji jako pliku MSIX.

Tożsamość pakietu jest podstawową koncepcją w modelu Windows app. Dzięki niej aplikacja może uzyskiwać dostęp do określonych interfejsów API systemu Windows (takich jak powiadomienia, zabezpieczenia, interfejsy API itp.), mieć czyste środowisko instalacji/odinstalowywania i wiele więcej.

Wymagania wstępne

  1. SDK Flutter: Zainstaluj Flutter zgodnie z oficjalnym przewodnikiem.

  2. winapp CLI: zainstaluj winapp CLI za pomocą winget

    winget install Microsoft.winappcli --source winget
    

1. Tworzenie nowej aplikacji Flutter

Postępuj zgodnie z przewodnikiem w oficjalnej dokumentacji aplikacji Flutter, aby utworzyć nową aplikację i uruchomić ją.

2. Aktualizowanie kodu w celu sprawdzenia tożsamości

ffi Dodaj pakiet:

flutter pub add ffi

Zastąp zawartość lib/main.dart następującym kodem, który sprawdza tożsamość pakietu przy użyciu interfejsu API systemu Windows GetCurrentPackageFamilyName za pośrednictwem narzędzia Dart FFI:

import 'dart:ffi';
import 'dart:io' show Platform;

import 'package:ffi/ffi.dart';
import 'package:flutter/material.dart';

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 {
    final result =
        getCurrentPackageFamilyName(length, Pointer<Uint16>.fromAddress(0));
    if (result != 122) return null; // ERROR_INSUFFICIENT_BUFFER = 122

    final namePtr = calloc<Uint16>(length.value);
    try {
      final result2 = getCurrentPackageFamilyName(length, namePtr);
      if (result2 == 0) {
        return namePtr.cast<Utf16>().toDartString();
      }
      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. Uruchamianie bez tożsamości

Skompiluj i uruchom aplikację:

flutter build windows
.\build\windows\x64\runner\Release\flutter_app.exe

Powinna zostać wyświetlona aplikacja z pomarańczowym wskaźnikiem "Nieopakowane".

4. Zainicjuj projekt za pomocą interfejsu wiersza polecenia winapp

winapp init

Po wyświetleniu monitu:

  • Nazwa pakietu: naciśnij klawisz Enter, aby zaakceptować wartość domyślną
  • Nazwa wydawcy: Naciśnij klawisz Enter, aby zaakceptować wartość domyślną lub wprowadź swoją nazwę
  • Wersja: Naciśnij klawisz Enter, aby zaakceptować 1.0.0.0
  • Punkt wejścia: Naciśnij klawisz Enter, aby zaakceptować wartość domyślną (flutter_app.exe)
  • Setup SDK: Wybierz pozycję "Stabilne zestawy SDK", aby pobrać Windows App SDK i wygenerować nagłówki języka C++

5. Debugowanie przy użyciu tożsamości

  1. Skompiluj aplikację:

    flutter build windows
    
  2. Zastosuj tożsamość debugowania:

    winapp create-debug-identity .\build\windows\x64\runner\Release\flutter_app.exe
    
  3. Uruchom plik wykonywalny:

    .\build\windows\x64\runner\Release\flutter_app.exe
    

Powinna być wyświetlona aplikacja z zielonym wskaźnikiem pokazującym nazwę rodziny pakietów.

Uwaga / Notatka

Po uruchomieniu flutter clean lub ponownym skompilowaniu należy ponownie uruchomić create-debug-identity, ponieważ plik wykonywalny zostanie zastąpiony.

6. Pakiet z plikiem MSIX

  1. Kompilacja do wydania:

    flutter build windows
    
  2. Przygotuj katalog pakietów:

    mkdir dist
    copy .\build\windows\x64\runner\Release\* .\dist\ -Recurse
    
  3. Generowanie certyfikatu programistycznego:

    winapp cert generate --if-exists skip
    
  4. Pakowanie i podpisywanie:

    winapp pack .\dist --cert .\devcert.pfx
    
  5. Zainstaluj certyfikat (uruchom jako administrator):

    winapp cert install .\devcert.pfx
    
  6. Zainstaluj pakiet:

    Add-AppxPackage .\flutter-app.msix
    

Wskazówka

  • Sklep Microsoft Store podpisuje plik MSIX, dlatego nie trzeba się podpisywać przed przesłaniem.
  • Azure zaufane podpisywanie to doskonały sposób bezpiecznego zarządzania certyfikatami na potrzeby pipelines ciągłej integracji/ciągłego wdrażania.