Freigeben über


Verwenden Sie das Continuity SDK, um die geräteübergreifende Wiederaufnahme (Cross Device Resume, XDR) für Android- und Windows-Anwendungen zu implementieren.

Dieser Artikel enthält umfassende Richtlinien für Entwickler von Erstanbietern und Drittanbietern, wie Features mithilfe des Continuity SDK in Ihre Anwendungen integriert werden können. Das Continuity SDK ermöglicht eine nahtlose geräteübergreifende Benutzererfahrung, sodass Benutzer Aktivitäten auf verschiedenen Plattformen fortsetzen können, einschließlich Android und Windows.

Anhand dieser Anleitung können Sie eine reibungslose und integrierte Benutzeroberfläche auf mehreren Geräten erstellen, indem Sie den XDR mithilfe des Continuity SDK nutzen.

Von Bedeutung

Onboarding zum Fortsetzen in Windows

Resume ist ein eingeschränktes Access Feature (LAF). Um access zu dieser API zu erhalten, müssen Sie die Genehmigung von Microsoft erhalten, um mit dem Paket "Link zu Windows" auf mobilen Android-Geräten zu arbeiten.

Um Zugriff anzufordern, senden Sie eine E-Mail an wincrossdeviceapi@microsoft.com mit den unten aufgeführten Informationen:

  • Eine Beschreibung Ihrer Benutzererfahrung
  • Screenshot Ihrer Anwendung, in dem ein Benutzer nativ auf Web oder Dokumente zugreift
  • Die PackageId Ihrer Anwendung
  • Die Google Play Store-URL für Ihre Anwendung

Wenn die Anforderung genehmigt wurde, erhalten Sie Anweisungen zum Entsperren des Features. Genehmigungen basieren auf Ihrer Kommunikation, sofern Ihr Szenario die beschriebenen Szenarioanforderungen erfüllt.

Voraussetzungen

Stellen Sie für Android-Anwendungen sicher, dass die folgenden Anforderungen erfüllt sind, bevor Sie das Continuity SDK integrieren:

  • Mindest-SDK-Version: 24
  • Kotlin Version: 1.9.x
  • Link zu Windows (LTW): 1.241101.XX

Stellen Sie für Windows-Anwendungen sicher, dass die folgenden Anforderungen erfüllt sind:

  • Mindestversion von Windows: Windows 11
  • Entwicklungsumgebung: Visual Studio 2019 oder höher

Hinweis

iOS-Anwendungen werden zurzeit nicht für die Integration mit dem Continuity SDK unterstützt.

Konfigurieren Ihrer Entwicklungsumgebung

Die folgenden Abschnitte enthalten schrittweise Anleitungen zum Einrichten der Entwicklungsumgebung für Android- und Windows-Anwendungen.

Android-Setup

Führen Sie die folgenden Schritte aus, um die Entwicklungsumgebung für Android einzurichten:

  1. Um das Bundle einzurichten, laden Sie die .aar-Datei mithilfe von Bibliotheken herunter, die in den folgenden freigegebenen Versionen bereitgestellt werden: Windows Cross-Device SDK-Versionen.

  2. Fügen Sie die Metatags in der AndroidManifest.xml Datei Ihrer Android-Anwendung hinzu. Der folgende Codeausschnitt veranschaulicht, wie die erforderlichen Metatags hinzugefügt werden:

    <meta-data 
        android:name="com.microsoft.crossdevice.resumeActivityProvider" 
        android:value="true" /> 
    
    <meta-data 
        android:name="com.microsoft.crossdevice.trigger.PartnerApp" 
        android:value="4" /> 
    

API-Integrationsschritte

Nach den Manifestdeklarationen können App-Entwickler ihren App-Kontext ganz einfach senden, indem Sie einem einfachen Codebeispiel folgen.

Die App muss:

  1. Initialisieren/Deinitialisieren des Continuity SDKs:
    1. Die App sollte den geeigneten Zeitpunkt für den Aufruf der Initialize- und DeInitialize-Funktionen bestimmen.
    2. Nach dem Aufrufen der Initialize-Funktion sollte ein Rückruf, der IAppContextEventHandler implementiert, ausgelöst werden.
  2. AppContext senden/löschen:
    1. Nach der Initialisierung des SDK deutet ein Aufruf von onContextRequestReceived darauf hin, dass die Verbindung hergestellt ist. Die App kann dann AppContext (einschließlich Erstellen und Aktualisieren) an LTW senden oder AppContext aus LTW löschen.
    2. Wenn keine Verbindung zwischen dem Telefon und dem PC besteht und die App App AppContext an LTW sendet, empfängt die App "onContextResponseError " mit der Meldung "PC ist nicht verbunden".
    3. Wenn die Verbindung erneut hergestellt wird, wird "onContextRequestReceived " erneut aufgerufen. Die App kann dann den aktuellen AppContext an LTW senden.
    4. Nachdem onSyncServiceDisconnected oder der SDK-Deinitialisierung sollte die App keinen AppContext senden.

Nachfolgend sehen Sie ein Codebeispiel. Alle erforderlichen und optionalen Felder in AppContext finden Sie in der AppContext-Beschreibung.

Der folgende Android-Codeausschnitt veranschaulicht, wie API-Anforderungen mithilfe des Continuity SDK ausgeführt werden:

import android.os.Bundle 
import android.util.Log 
import android.widget.Button 
import android.widget.TextView 
import android.widget.Toast 
import androidx.activity.enableEdgeToEdge 
import androidx.appcompat.app.AppCompatActivity 
import androidx.core.view.ViewCompat 
import androidx.core.view.WindowInsetsCompat 
import androidx.lifecycle.LiveData 
import androidx.lifecycle.MutableLiveData 
import androidx.lifecycle.Observer 
import com.microsoft.crossdevicesdk.continuity.AppContext 
import com.microsoft.crossdevicesdk.continuity.AppContextManager 
import com.microsoft.crossdevicesdk.continuity.ContextRequestInfo 
import com.microsoft.crossdevicesdk.continuity.IAppContextEventHandler 
import com.microsoft.crossdevicesdk.continuity.IAppContextResponse 
import com.microsoft.crossdevicesdk.continuity.LogUtils 
import com.microsoft.crossdevicesdk.continuity.ProtocolConstants 
import java.util.UUID 

  

class MainActivity : AppCompatActivity() { 

    //Make buttons member variables --- 
    private lateinit var buttonSend: Button 
    private lateinit var buttonDelete: Button 
    private lateinit var buttonUpdate: Button 

    private val appContextResponse = object : IAppContextResponse { 
        override fun onContextResponseSuccess(response: AppContext) { 
            Log.d("MainActivity", "onContextResponseSuccess") 
            runOnUiThread { 
                Toast.makeText( 
                    this@MainActivity, 
                    "Context response success: ${response.contextId}", 
                    Toast.LENGTH_SHORT 
                ).show() 
            } 
        } 

        override fun onContextResponseError(response: AppContext, throwable: Throwable) { 
            Log.d("MainActivity", "onContextResponseError: ${throwable.message}") 
            runOnUiThread { 
                Toast.makeText( 
                    this@MainActivity, 
                    "Context response error: ${throwable.message}", 
                    Toast.LENGTH_SHORT 
                ).show() 

                // Check if the error message contains the specific string 
                if (throwable.message?.contains("PC is not connected") == true) { 
                    //App should stop sending intent once this callback is received 
                }
            } 
        } 
    } 

    private lateinit var appContextEventHandler: IAppContextEventHandler 

    private val _currentAppContext = MutableLiveData<AppContext?>() 

    private val currentAppContext: LiveData<AppContext?> get() = _currentAppContext 

    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
        enableEdgeToEdge() 
        setContentView(R.layout.activity_main) 
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> 
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) 
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) 
            insets 
        } 

        LogUtils.setDebugMode(true) 
        var ready = false 
        buttonSend = findViewById(R.id.buttonSend) 
        buttonDelete = findViewById(R.id.buttonDelete) 
        buttonUpdate = findViewById(R.id.buttonUpdate) 
        setButtonDisabled(buttonSend) 
        setButtonDisabled(buttonDelete) 
        setButtonDisabled(buttonUpdate) 

        buttonSend.setOnClickListener { 
            if (ready) { 
                sendResumeActivity() 
            } 
        } 

        buttonDelete.setOnClickListener { 
            if (ready) { 
                deleteResumeActivity() 
            } 
        } 

        buttonUpdate.setOnClickListener { 
            if (ready) { 
                updateResumeActivity() 
            }
        } 

        appContextEventHandler = object : IAppContextEventHandler { 

            override fun onContextRequestReceived(contextRequestInfo: ContextRequestInfo) { 
                LogUtils.d("MainActivity", "onContextRequestReceived") 
                ready = true 
                setButtonEnabled(buttonSend) 
                setButtonEnabled(buttonDelete) 
                setButtonEnabled(buttonUpdate) 

            } 

  

            override fun onInvalidContextRequestReceived(throwable: Throwable) { 
                Log.d("MainActivity", "onInvalidContextRequestReceived") 

            } 

  

            override fun onSyncServiceDisconnected() { 
                Log.d("MainActivity", "onSyncServiceDisconnected") 
                ready = false 
                setButtonDisabled(buttonSend) 
                setButtonDisabled(buttonDelete) 
            } 
        } 

        // Initialize the AppContextManager 
        AppContextManager.initialize(this.applicationContext, appContextEventHandler) 

        // Update currentAppContext text view. 
        val textView = findViewById<TextView>(R.id.appContext) 

        currentAppContext.observe(this, Observer { appContext -> 
            appContext?.let { 
                textView.text = 
                    "Current app context: ${it.contextId}\n App ID: ${it.appId}\n Created: ${it.createTime}\n Updated: ${it.lastUpdatedTime}\n Type: ${it.type}" 
                Log.d("MainActivity", "Current app context: ${it.contextId}") 
            } ?: run { 
                textView.text = "No current app context available" 
                Log.d("MainActivity", "No current app context available") 
            } 
        }) 
    } 


    // Send resume activity to LTW 
    private fun sendResumeActivity() { 
        val appContext = AppContext().apply { 
            this.contextId = generateContextId() 
            this.appId = applicationContext.packageName 
            this.createTime = System.currentTimeMillis() 
            this.lastUpdatedTime = System.currentTimeMillis() 
            this.type = ProtocolConstants.TYPE_RESUME_ACTIVITY 
        } 

        _currentAppContext.value = appContext 
        AppContextManager.sendAppContext(this.applicationContext, appContext, appContextResponse) 
    } 

    // Delete resume activity from LTW 
    private fun deleteResumeActivity() { 
        currentAppContext.value?.let { 
            AppContextManager.deleteAppContext( 
                this.applicationContext, 
                it.contextId, 
                appContextResponse 
            ) 
            _currentAppContext.value = null 
        } ?: run { 
            Toast.makeText(this, "No resume activity to delete", Toast.LENGTH_SHORT).show() 
            Log.d("MainActivity", "No resume activity to delete") 
        }
    } 

    private fun updateResumeActivity() { 
        currentAppContext.value?.let { 
            it.lastUpdatedTime = System.currentTimeMillis() 
            AppContextManager.sendAppContext(this.applicationContext, it, appContextResponse) 
            _currentAppContext.postValue(it) 
        } ?: run { 
            Toast.makeText(this, "No resume activity to update", Toast.LENGTH_SHORT).show() 
            Log.d("MainActivity", "No resume activity to update") 
        } 
    } 

    private fun setButtonDisabled(button: Button) { 
        button.isEnabled = false 
        button.alpha = 0.5f 
    } 

    private fun setButtonEnabled(button: Button) { 
        button.isEnabled = true 
        button.alpha = 1.0f 
    } 

    override fun onDestroy() { 
        super.onDestroy() 
        // Deinitialize the AppContextManager 
        AppContextManager.deInitialize(this.applicationContext) 
    } 

    override fun onStart() { 
        super.onStart() 
        // AppContextManager.initialize(this.applicationContext, appContextEventHandler) 
    } 


    override fun onStop() { 
        super.onStop() 
        // AppContextManager.deInitialize(this.applicationContext) 
    } 

    private fun generateContextId(): String { 
        return "${packageName}.${UUID.randomUUID()}" 
    } 

} 

Schritte zur Integrationsüberprüfung

Führen Sie die folgenden Schritte aus, um die Integration des Continuity SDK in Ihrer Anwendung zu überprüfen:

Vorbereitung

Die folgenden Schritte sind erforderlich, um die Integrationsüberprüfung vorzubereiten:

  1. Stellen Sie sicher, dass das private LTW installiert ist.

  2. Verbinden Sie LTW mit Ihrem PC:

    Anweisungen finden Sie unter "So verwalten Sie Ihr mobiles Gerät auf Ihrem PC ".

    Hinweis

    Wenn Sie nach dem Scannen des QR-Codes nicht zu LTW umgeleitet werden, öffnen Sie bitte zuerst LTW und scannen Sie den QR-Code innerhalb der App.

  3. Stellen Sie sicher, dass die Partner-App das Continuity SDK integriert hat.

Validation

Führen Sie als Nächstes die folgenden Schritte aus, um die Integration zu überprüfen:

  1. Starten Sie die App, und initialisieren Sie das SDK. Vergewissern Sie sich, dass onContextRequestReceived aufgerufen wird.
  2. Nachdem onContextRequestReceived aufgerufen wurde, kann die App den AppContext an LTW senden. Wenn onContextResponseSuccess nach dem Senden von AppContext aufgerufen wird, ist die SDK-Integration erfolgreich.
  3. Wenn die App AppContext sendet, während der PC gesperrt oder getrennt ist, überprüfen Sie, ob onContextResponseError mit "PC ist nicht verbunden" aufgerufen wird.
  4. Stellen Sie beim Wiederherstellen der Verbindung sicher, dass onContextRequestReceived erneut aufgerufen wird, und die App kann dann den aktuellen AppContext an LTW senden.

Der folgende Screenshot zeigt den Protokolleintrag, wenn der PC mit der Fehlermeldung "PC ist nicht verbunden" getrennt wird, und der Protokolleintrag nach der erneuten Verbindung, wenn onContextRequestReceived erneut aufgerufen wird.

Ein Screenshot von Windows-Protokolleinträgen zeigt die Fehlermeldung

AppContext

XDR definiert AppContext als Metadaten, über die XDR verstehen kann, welche App fortgesetzt werden soll, zusammen mit dem Kontext, mit dem die Anwendung fortgesetzt werden muss. Apps können Aktivitäten verwenden, um Benutzern die Möglichkeit zu geben, auf mehrere Geräte zurück zu den Aktionen in ihrer App zu gelangen. Aktivitäten, die von einer mobilen App erstellt werden, erscheinen auf den Windows-Geräten der Benutzer, solange diese Geräte als Cross Device Experience Host (CDEH) bereitgestellt wurden.  

Jede Anwendung unterscheidet sich, und es liegt an Windows, die Zielanwendung für den Lebenslauf und bis zu bestimmten Anwendungen unter Windows zu verstehen, um den Kontext zu verstehen. XDR schlägt ein generisches Schema vor, das Anforderungen für alle Erstanbieter- sowie Drittanbieter-App-Wiederaufnahmeszenarien erfüllen kann.

contextId

  • Erforderlich: Ja
  • Beschreibung: Dies ist ein eindeutiger Bezeichner, der verwendet wird, um einen AppContext von einem anderen zu unterscheiden. Dadurch wird sichergestellt, dass jeder AppContext eindeutig identifizierbar ist.
  • Verwendung: Erstellen Sie für jeden AppContext eine eindeutige ContextId, um Konflikte zu vermeiden.

type

  • Erforderlich: Ja
  • Beschreibung: Dies ist ein binäres Flag, das den Typ von AppContext angibt, der an "Link to Windows" (LTW) gesendet wird. Der Wert sollte mit dem requestedContextType konsistent sein.
  • Verwendung: Legen Sie dieses Kennzeichen entsprechend dem Typ des Kontexts fest, den Sie senden. Beispiel: ProtocolConstants.TYPE_RESUME_ACTIVITY.

Erstellungszeit

  • Erforderlich: Ja
  • Beschreibung: Dieser Zeitstempel stellt die Erstellungszeit des AppContext dar.
  • Verwendung: Notieren Sie sich den genauen Zeitpunkt, zu dem der AppContext erstellt wird.

intentUri

  • Erforderlich: Nein, wenn Weblink bereitgestellt wird
  • Beschreibung: Dieser URI gibt an, welche App den appContext fortsetzen kann, der vom ursprünglichen Gerät übergeben wird.
  • Verwendung: Geben Sie dies an, wenn Sie eine bestimmte App für die Verarbeitung des Kontexts angeben möchten.
  • Erforderlich: Nein, wenn intentUri bereitgestellt wird
  • Beschreibung: Dieser URI wird verwendet, um den Webendpunkt der Anwendung zu starten, wenn sie sich entscheiden, keine Store-Apps zu verwenden. Dieser Parameter wird nur verwendet, wenn intentUri nicht bereitgestellt wird. Wenn beides angegeben wird, wird intentUri verwendet, um die Anwendung unter Windows fortzusetzen.
  • Verwendung: Nur zu verwenden, wenn die Anwendung auf Webendpunkten und nicht auf den Speicheranwendungen fortgesetzt werden möchte.

App-ID

  • Erforderlich: Ja
  • Beschreibung: Dies ist der Paketname der Anwendung, für die der Kontext gilt.
  • Verwendung: Legen Sie dies auf den Paketnamen Ihrer Anwendung fest.

title

  • Erforderlich: Ja
  • Beschreibung: Dies ist der Titel des AppContext, z. B. eines Dokumentnamens oder eines Webseitentitels.
  • Verwendung: Geben Sie einen aussagekräftigen Titel an, der den AppContext darstellt.

Vorschau

  • Erforderlich: Nein
  • Beschreibung: Dies sind Bytes des Vorschaubilds, die den AppContext darstellen können.
  • Verwendung: Bereitstellen eines Vorschaubilds, falls verfügbar, um Benutzern eine visuelle Darstellung des AppContext zu ermöglichen.

Lebensdauer

  • Erforderlich: Nein
  • Beschreibung: Dies ist die Lebensdauer der AppContext in Millisekunden. Sie wird nur für laufende Szenarien verwendet. Wenn sie nicht festgelegt ist, beträgt der Standardwert 5 Minuten.
  • Verwendung: Legen Sie dies fest, um festzulegen, wie lange die Gültigkeitsdauer AppContext beträgt. Sie können einen Wert bis zu 5 Minuten festlegen. Jeder größere Wert wird automatisch auf 5 Minuten gekürzt.

Intent-URIs

URIs ermöglichen es Ihnen, eine andere App zu starten, um eine bestimmte Aufgabe auszuführen und hilfreiche App-zu-App-Szenarien zu ermöglichen. Weitere Informationen zum Starten von Apps mit URIs finden Sie unter Starten Sie die Standard-Windows-App für eine URI und Erstellen Sie Deep Links zu App-Inhalten | Android-Entwickler.

Behandeln von API-Antworten in Windows

In diesem Abschnitt wird beschrieben, wie die API-Antworten in Windows-Anwendungen behandelt werden. Das Continuity SDK bietet eine Möglichkeit, die API-Antworten für Win32-, UWP- und Windows App SDK-Apps zu behandeln.

Beispiel für Win32-App

Für Win32-Apps zum Behandeln des Protokoll-URI-Starts sind die folgenden Schritte erforderlich:

  1. Zunächst muss ein Eintrag wie folgt im System-Registry erfolgen:

    [HKEY_CLASSES_ROOT\partnerapp] 
    @="URL:PartnerApp Protocol" 
    "URL Protocol"="" 
    
    [HKEY_CLASSES_ROOT\partnerapp\shell\open\command] 
    @="\"C:\\path\\to\\PartnerAppExecutable.exe\" \"%1\"" 
    
  2. Der Start muss in der Hauptfunktion der Win32-App behandelt werden:

    #include <windows.h> 
    #include <shellapi.h> 
    #include <string> 
    #include <iostream> 
    
    int CALLBACK wWinMain(HINSTANCE, HINSTANCE, PWSTR lpCmdLine, int) 
    { 
        // Check if there's an argument passed via lpCmdLine 
        std::wstring cmdLine(lpCmdLine); 
        std::wstring arguments; 
    
        if (!cmdLine.empty()) 
        { 
            // Check if the command-line argument starts with "partnerapp://", indicating a URI launch 
            if (cmdLine.find(L"partnerapp://") == 0) 
            { 
                // This is a URI protocol launch 
                // Process the URI as needed 
                // Example: Extract action and parameters from the URI 
                arguments = cmdLine;  // or further parse as required 
            } 
            else 
            {
                // Launched by command line or activation APIs 
            } 
        } 
        else 
        { 
            // Handle cases where no arguments were passed 
        } 
    
        return 0; 
    } 
    

UWP-Apps

Für UWP-Apps kann der Protokoll-URI im App-Manifest des project registriert werden. Die folgenden Schritte veranschaulichen die Behandlung der Protokollaktivierung in einer UWP-App.

  1. Zunächst wird der Protokoll-URI wie folgt in der Package.appxmanifest Datei registriert:

    <Applications> 
            <Application Id= ... > 
                <Extensions> 
                    <uap:Extension Category="windows.protocol"> 
                      <uap:Protocol Name="alsdk"> 
                        <uap:Logo>images\icon.png</uap:Logo> 
                        <uap:DisplayName>SDK Sample URI Scheme</uap:DisplayName> 
                      </uap:Protocol> 
                    </uap:Extension> 
              </Extensions> 
              ... 
            </Application> 
       <Applications> 
    
  2. Als nächstes überschreiben Sie in der App.xaml.cs Datei die OnActivated Methode wie folgt:

    public partial class App 
    { 
       protected override void OnActivated(IActivatedEventArgs args) 
      { 
          if (args.Kind == ActivationKind.Protocol) 
          { 
             ProtocolActivatedEventArgs eventArgs = args as ProtocolActivatedEventArgs; 
             // TODO: Handle URI activation 
             // The received URI is eventArgs.Uri.AbsoluteUri 
          } 
       } 
    } 
    

Weitere Informationen zum Behandeln des URI-Starts in UWP-Apps finden Sie in Schritt 3 bei der Behandlung der URI-Aktivierung.

WinUI 3-Beispiel

Der folgende Codeausschnitt veranschaulicht, wie die Protokollaktivierung in einer C++-WinUI-App mit Windows App SDK behandelt wird:

void App::OnActivated(winrt::Windows::ApplicationModel::Activation::IActivatedEventArgs const& args) 
{ 
     if (args.Kind() == winrt::Windows::ApplicationModel::Activation::ActivationKind::Protocol) 
     { 
         auto protocolArgs = args.as<winrt::Windows::ApplicationModel::Activation::ProtocolActivatedEventArgs>(); 
         auto uri = protocolArgs.Uri(); 
         std::wstring uriString = uri.AbsoluteUri().c_str(); 
         //Process the URI as per argument scheme 
     } 
} 

Wenn Sie einen Weblink verwenden, wird der Webendpunkt der Anwendung gestartet. App-Entwickler müssen sicherstellen, dass der von ihrer Android-Anwendung bereitgestellte Weblink gültig ist, da XDR den Standardbrowser des Systems verwendet, um auf den bereitgestellten Weblink umzuleiten.

Verarbeiten von Argumenten, die von Cross Device Resume abgerufen werden

Es liegt in der Verantwortung jeder App, das empfangene Argument zu deserialisieren und zu entschlüsseln und die Informationen entsprechend zu verarbeiten, um den laufenden Kontext von Telefon zu PC zu übertragen. Wenn beispielsweise ein Anruf übertragen werden muss, muss die App diesen Kontext über Telefon kommunizieren können, und die Desktop-App muss diesen Kontext entsprechend verstehen und den Ladevorgang fortsetzen.