Partager via


Utilisez le kit de développement logiciel (SDK) de continuité pour implémenter la Reprise entre appareils (XDR) pour les applications Android et Windows.

Cet article fournit des instructions complètes pour les développeurs internes et tiers sur la façon d’intégrer des fonctionnalités à l’aide du Kit de développement logiciel (SDK) De continuité dans vos applications. Le Kit de développement logiciel (SDK) De continuité permet d’effectuer des expériences inter-appareils transparentes, ce qui permet aux utilisateurs de reprendre des activités sur différentes plateformes, notamment Android et Windows.

En suivant ces conseils, vous pouvez créer une expérience utilisateur fluide et intégrée sur plusieurs appareils en tirant parti du XDR à l’aide du Kit de développement logiciel (SDK) de continuité.

Important

L'intégration reprend dans Windows

La reprise est une fonctionnalité d'accès limité (LAF). Pour obtenir access à cette API, vous devez obtenir l'approbation de Microsoft pour interagir avec le package « Lien vers Windows » sur les appareils mobiles Android.

Pour demander access, envoyez un e-mail wincrossdeviceapi@microsoft.com avec les informations répertoriées ci-dessous :

  • Description de votre expérience utilisateur
  • Capture d’écran de votre application où un utilisateur accède en mode natif au web ou aux documents
  • PackageId de votre application
  • URL du Google Play Store pour votre application

Si la demande est approuvée, vous recevrez des instructions sur la façon de déverrouiller la fonctionnalité. Les approbations seront basées sur votre communication, à condition que votre scénario réponde aux exigences du scénario décrites.

Prerequisites

Pour les applications Android, vérifiez que les exigences suivantes sont remplies avant d’intégrer le Kit de développement logiciel (SDK) de continuité :

  • Version minimale du Kit de développement logiciel (SDK) : 24
  • Version de Kotlin : 1.9.x
  • Lien vers Windows (LTW) : 1.241101.XX

Pour les applications Windows, vérifiez que les exigences suivantes sont remplies :

  • Version minimale de Windows : Windows 11
  • Environnement de développement : Visual Studio 2019 ou version ultérieure

Note

Les applications iOS ne sont pas prises en charge pour l’intégration avec le Kit de développement logiciel (SDK) de continuité pour l’instant.

Configurer votre environnement de développement

Les sections suivantes fournissent des instructions pas à pas pour configurer l’environnement de développement pour les applications Android et Windows.

Configuration d'Android

Pour configurer l’environnement de développement pour Android, procédez comme suit :

  1. Pour configurer l’offre groupée, téléchargez et utilisez le fichier .aar via les bibliothèques fournies dans les versions suivantes : Versions du Kit de développement logiciel (SDK) inter-appareils windows.

  2. Ajoutez les balises meta dans le fichier AndroidManifest.xml de votre application Android. L’extrait de code suivant montre comment ajouter les balises meta requises :

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

Étapes d’intégration d’API

Après les déclarations de manifeste, les développeurs d’applications peuvent facilement envoyer leur contexte d’application en suivant un exemple de code simple.

L’application doit :

  1. Initialize/DeInitialize the Continuity SDK :
    1. L’application doit déterminer le moment approprié pour appeler les fonctions Initialize et DeInitialize.
    2. Après avoir appelé la fonction Initialize, un rappel qui implémente IAppContextEventHandler doit être déclenché.
  2. Envoyer/supprimer AppContext :
    1. Après avoir initialisé le SDK, si onContextRequestReceived est appelé, il indique que la connexion est établie. L’application peut ensuite envoyer (y compris créer et mettre à jour) AppContext à LTW ou supprimer AppContext à partir de LTW.
    2. S’il n’existe aucune connexion entre le téléphone et le PC et que l’application envoie AppContext à LTW, l’application reçoit onContextResponseError avec le message « PC n’est pas connecté ».
    3. Lorsque la connexion est rétablie, onContextRequestReceived est appelé à nouveau. L’application peut ensuite envoyer l’appContext actuel à LTW.
    4. Après onSyncServiceDisconnected ou désinitialisation du Kit de développement logiciel (SDK), l’application ne doit pas envoyer un AppContext.

Voici un exemple de code. Pour tous les champs obligatoires et facultatifs dans AppContext, reportez-vous à la description AppContext.

L’extrait de code Android suivant montre comment effectuer des demandes d’API à l’aide du Kit de développement logiciel (SDK) de continuité :

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()}" 
    } 

} 

Étapes de validation d’intégration

Pour valider l’intégration du Kit de développement logiciel (SDK) de continuité dans votre application, procédez comme suit :

Préparation

Les étapes suivantes sont requises pour préparer la validation d’intégration :

  1. Assurez-vous que le LTW privé est installé.

  2. Connectez LTW à votre PC :

    Pour obtenir des instructions, consultez Comment gérer votre appareil mobile sur votre PC .

    Note

    Si après avoir analysé le code QR, vous n’êtes pas redirigé vers LTW, ouvrez D’abord LTW et scannez le code QR dans l’application.

  3. Vérifiez que l’application partenaire a intégré le Kit de développement logiciel (SDK) de continuité.

Validation

Ensuite, procédez comme suit pour valider l’intégration :

  1. Lancez l’application et initialisez le Kit de développement logiciel (SDK). Vérifiez que onContextRequestReceived est appelé.
  2. Après l'appel de onContextRequestReceived, l'application peut envoyer AppContext à LTW. Si onContextResponseSuccess est appelé après l’envoi d’AppContext, l’intégration du SDK réussit.
  3. Si l’application envoie AppContext pendant que le PC est verrouillé ou déconnecté, vérifiez que onContextResponseError est appelé avec « PC n’est pas connecté ».
  4. Lorsque la connexion est restaurée, vérifiez que onContextRequestReceived est appelé à nouveau et que l’application peut ensuite envoyer l’appContext actuel à LTW.

La capture d’écran ci-dessous montre l’entrée de journal lorsque le PC est déconnecté avec le message d’erreur « PC n’est pas connecté » et l’entrée de journal après la reconnexion lorsque onContextRequestReceived est appelé à nouveau.

Capture d’écran des entrées de journal Windows montrant un message d'erreur indiquant que le PC n'est pas connecté, suivi de l’entrée de journal onContextRequestReceived après la reconnexion.

AppContext

XDR définit AppContext comme métadonnées par le biais de laquelle XDR peut comprendre l’application à reprendre, ainsi que le contexte avec lequel l’application doit être reprise. Les applications peuvent utiliser des activités pour permettre aux utilisateurs de revenir à ce qu’ils faisaient dans leur application, sur plusieurs appareils. Les activités créées par n’importe quelle application mobile s’affichent sur les appareils Windows des utilisateurs tant que ces appareils ont été approvisionnés par l’hôte d’expérience inter-appareils (CDEH).  

Chaque application est différente, et c'est à Windows de comprendre l'application cible pour la reprise, tandis que certaines applications spécifiques sur Windows doivent comprendre le contexte. XDR propose un schéma générique qui peut répondre aux exigences pour tous les scénarios de reprise d’application tiers ainsi que pour tous les scénarios de reprise d’application tiers.

contextId

  • Obligatoire : Oui
  • Description : il s’agit d’un identificateur unique utilisé pour distinguer un AppContext d’un autre. Il garantit que chaque AppContext est identifiable de façon unique.
  • Utilisation : veillez à générer un contextId unique pour chaque AppContext afin d’éviter les conflits.

type

  • Obligatoire : Oui
  • Description : il s’agit d’un indicateur binaire qui indique le type d’AppContext envoyé à Link to Windows (LTW). La valeur doit être cohérente avec le TypeContextType demandé.
  • Utilisation : définissez cet indicateur en fonction du type de contexte que vous envoyez. Par exemple : ProtocolConstants.TYPE_RESUME_ACTIVITY.

createTime

  • Obligatoire : Oui
  • Description : cet horodatage représente l’heure de création de l’AppContext.
  • Utilisation : enregistrez l’heure exacte à laquelle AppContext est créé.

intentUri

  • Obligatoire : Non, si weblink est fourni
  • Description : cet URI indique l’application qui peut continuer le AppContext transmis par l’appareil d’origine.
  • Utilisation : indiquez cette option si vous souhaitez spécifier une application particulière pour gérer le contexte.
  • Obligatoire : Non, si intentUri est fourni
  • Description : cet URI est utilisé pour lancer le point de terminaison web de l’application s’ils choisissent de ne pas utiliser d’applications du Store. Ce paramètre est utilisé uniquement lorsque intentUri n’est pas fourni. Si les deux sont fournis, intentUri sera utilisé pour reprendre l’application sur Windows.
  • Utilisation : à utiliser uniquement si l’application souhaite reprendre sur les points de terminaison web et non les applications de magasin.

appId

  • Obligatoire : Oui
  • Description : il s’agit du nom du package de l’application pour laquelle le contexte est destiné.
  • Utilisation : définissez ce paramètre sur le nom du package de votre application.

title

  • Obligatoire : Oui
  • Description : il s’agit du titre d’AppContext, tel qu’un nom de document ou un titre de page web.
  • Utilisation : fournissez un titre explicite qui représente AppContext.

Aperçu

  • Obligatoire : Non
  • Description : Il s’agit d’octets de l’image d’aperçu qui peuvent représenter AppContext.
  • Utilisation : fournissez une image d’aperçu si elle est disponible pour donner aux utilisateurs une représentation visuelle de l’AppContext.

Durée de vie

  • Obligatoire : Non
  • Description : il s'agit de la durée de vie du AppContext en millisecondes. Il est utilisé uniquement pour les scénarios en cours. Si elle n’est pas définie, la valeur par défaut est de 5 minutes.
  • Utilisation : définissez cette valeur pour définir la durée pendant laquelle la AppContext valeur doit être valide. Vous pouvez définir une valeur jusqu’à un maximum de 5 minutes. Toute valeur supérieure sera automatiquement raccourcie à 5 minutes.

URI d’intention

Les URI vous permettent de lancer une autre application pour effectuer une tâche spécifique, ce qui permet des scénarios d’application à application utiles. Pour plus d’informations sur le lancement d’applications à l’aide d’URI, consultez Lancer l’application Windows par défaut pour un URI et Créer des liens profonds vers le contenu de l'application | Développeurs Android.

Gestion des réponses d’API dans Windows

Cette section explique comment gérer les réponses d’API dans les applications Windows. Le Kit de développement logiciel (SDK) de continuité fournit un moyen de gérer les réponses d’API pour les applications Win32, UWP et Windows App SDK.

Exemple d’application Win32

Pour que les applications Win32 gèrent le lancement de l’URI du protocole, les étapes suivantes sont requises :

  1. Tout d’abord, une entrée doit être effectuée dans le Registre comme suit :

    [HKEY_CLASSES_ROOT\partnerapp] 
    @="URL:PartnerApp Protocol" 
    "URL Protocol"="" 
    
    [HKEY_CLASSES_ROOT\partnerapp\shell\open\command] 
    @="\"C:\\path\\to\\PartnerAppExecutable.exe\" \"%1\"" 
    
  2. Le lancement doit être géré dans la fonction principale de l’application Win32 :

    #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; 
    } 
    

Applications UWP

Pour les applications UWP, l'URI de protocole peut être inscrit dans le manifeste de l'application de project. Les étapes suivantes montrent comment gérer l’activation du protocole dans une application UWP.

  1. Tout d’abord, l’URI du protocole est inscrit dans le Package.appxmanifest fichier comme suit :

    <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. Ensuite, dans le App.xaml.cs fichier, remplacez la OnActivated méthode comme suit :

    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 
          } 
       } 
    } 
    

Pour plus d’informations sur la gestion du lancement d’URI dans les applications UWP, consultez l’étape 3 dans Gérer l’activation d’URI.

Exemple WinUI 3

L’extrait de code suivant montre comment gérer l’activation du protocole dans une application WinUI C++ avec Windows App SDK :

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 
     } 
} 

L’utilisation d’un lien web lance le point de terminaison web de l’application. Les développeurs d’applications doivent s’assurer que le lien web fourni à partir de leur application Android est valide, car XDR utilise le navigateur par défaut du système pour rediriger vers le weblink fourni.

Gestion des arguments obtenus à partir de Cross Device Resume

Il incombe à chaque application de désérialiser et de déchiffrer l’argument reçu et de traiter les informations en conséquence pour transférer le contexte continu du téléphone vers le PC. Par exemple, si un appel doit être transféré, l’application doit être en mesure de communiquer ce contexte à partir du téléphone et de l’application de bureau doit comprendre ce contexte de manière appropriée et continuer le chargement.