Gunakan SDK Kontinuitas untuk menerapkan resume lintas perangkat (XDR) untuk aplikasi Android dan Windows

Artikel ini menyediakan panduan komprehensif untuk pengembang pihak pertama dan pihak ketiga tentang cara mengintegrasikan fitur menggunakan SDK Kelangsungan di aplikasi Anda. SDK Kelangsungan memungkinkan pengalaman lintas perangkat yang mulus, memungkinkan pengguna untuk melanjutkan aktivitas di berbagai platform, termasuk Android dan Windows.

Dengan mengikuti panduan ini, Anda dapat membuat pengalaman pengguna yang lancar dan terintegrasi di beberapa perangkat dengan memanfaatkan XDR menggunakan Continuity SDK.

Penting

Pengenalan di Windows akan Dilaksanakan Kembali

Resume adalah fitur Akses Terbatas (LAF). Untuk mendapatkan akses ke API ini, Anda harus mendapatkan persetujuan dari Microsoft untuk berinteroperasi dengan paket "Tautkan ke Windows" di perangkat seluler Android.

Untuk meminta access, email wincrossdeviceapi@microsoft.com dengan informasi yang tercantum di bawah ini:

  • Deskripsi pengalaman pengguna Anda
  • Cuplikan layar aplikasi Anda di mana pengguna secara asli mengakses web atau dokumen
  • PackageId aplikasi Anda
  • URL Google Play Store untuk aplikasi Anda

Jika permintaan disetujui, Anda akan menerima instruksi tentang cara membuka kunci fitur. Persetujuan akan didasarkan pada komunikasi Anda, asalkan skenario Anda memenuhi Persyaratan Skenario yang diuraikan.

Prasyarat

Untuk aplikasi Android, pastikan persyaratan berikut terpenuhi sebelum mengintegrasikan Continuity SDK:

  • Versi SDK Minimum: 24
  • Versi Kotlin: 1.9.x
  • Tautan ke Windows (LTW): 1.241101.XX

Untuk aplikasi Windows, pastikan persyaratan berikut terpenuhi:

  • Versi Windows Minimum: Windows 11
  • Lingkungan Pengembangan: Visual Studio 2019 atau yang lebih baru

Nota

Aplikasi iOS tidak didukung untuk integrasi dengan SDK Kelangsungan saat ini.

Mengonfigurasi lingkungan pengembangan Anda

Bagian berikut memberikan instruksi langkah demi langkah untuk menyiapkan lingkungan pengembangan untuk aplikasi Android dan Windows.

Penyiapan Android

Untuk menyiapkan lingkungan pengembangan untuk Android, ikuti langkah-langkah berikut:

  1. Untuk menyiapkan bundel, unduh dan gunakan file .aar melalui pustaka yang disediakan dalam rilis SDK Lintas Perangkat Windows berikut.

  2. Tambahkan tag meta di file AndroidManifest.xml aplikasi Android Anda. Cuplikan berikut menunjukkan cara menambahkan tag meta yang diperlukan:

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

Langkah-langkah integrasi API

Setelah deklarasi manifes, pengembang aplikasi dapat dengan mudah mengirim konteks aplikasi mereka dengan mengikuti contoh kode sederhana.

Aplikasi harus:

  1. Inisialisasi/Deinisialisasi SDK Kontinuitas:
    1. Aplikasi harus menentukan waktu yang tepat untuk memanggil fungsi Inisialisasi dan DeInitialisasi.
    2. Setelah memanggil fungsi Inisialisasi, panggilan balik yang mengimplementasikan IAppContextEventHandler harus dipicu.
  2. Kirim/Hapus AppContext:
    1. Setelah menginisialisasi SDK, jika onContextRequestReceived dipanggil, itu menunjukkan koneksi dibuat. Aplikasi kemudian dapat mengirim (termasuk membuat dan memperbarui) AppContext ke LTW atau menghapus AppContext dari LTW.
    2. Jika tidak ada koneksi antara telepon dan PC dan aplikasi mengirim AppContext ke LTW, aplikasi akan menerima onContextResponseError dengan pesan "PC tidak terhubung."
    3. Ketika koneksi dibuat kembali, onContextRequestReceived dipanggil lagi. Aplikasi kemudian dapat mengirim AppContext saat ini ke LTW.
    4. Setelah onSyncServiceDisconnected atau deinisialisasi SDK, aplikasi tidak boleh mengirim AppContext.

Di bawah ini adalah contoh kode. Untuk semua bidang yang diperlukan dan opsional di AppContext, silakan lihat deskripsi AppContext.

Cuplikan kode Android berikut menunjukkan cara membuat permintaan API menggunakan Continuity SDK:

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

} 

Langkah-langkah validasi integrasi

Untuk memvalidasi integrasi SDK Kelangsungan di aplikasi Anda, ikuti langkah-langkah berikut:

Persiapan

Langkah-langkah berikut diperlukan untuk mempersiapkan validasi integrasi:

  1. Pastikan LTW pribadi terpasang.

  2. Sambungkan LTW ke PC Anda:

    Lihat Cara mengelola perangkat seluler Anda di PC untuk mengetahui petunjuknya.

    Nota

    Jika setelah memindai kode QR, Anda tidak dialihkan ke LTW, buka LTW terlebih dahulu dan pindai kode QR dalam aplikasi.

  3. Verifikasi bahwa aplikasi mitra telah mengintegrasikan SDK Kelangsungan.

Validation

Selanjutnya, ikuti langkah-langkah berikut untuk memvalidasi integrasi:

  1. Luncurkan aplikasi dan inisialisasi SDK. Konfirmasikan bahwa onContextRequestReceived dipanggil.
  2. Setelah onContextRequestReceived dipanggil, aplikasi dapat mengirim AppContext ke LTW. Jika onContextResponseSuccess dipanggil setelah mengirim AppContext, integrasi SDK berhasil.
  3. Jika aplikasi mengirim AppContext saat PC terkunci atau terputus, verifikasi bahwa onContextResponseError dipanggil dengan "PC tidak tersambung."
  4. Ketika koneksi dipulihkan, pastikan onContextRequestReceived dipanggil lagi dan aplikasi kemudian dapat mengirim AppContext saat ini ke LTW.

Cuplikan layar di bawah ini menunjukkan entri log ketika PC terputus dengan pesan kesalahan "PC tidak terhubung" dan entri log setelah koneksi ulang ketika onContextRequestReceived dipanggil lagi.

Cuplikan layar entri log Windows yang menunjukkan pesan kesalahan PC tidak tersambung dan entri log onContextRequestReceived berikutnya setelah koneksi ulang.

AppContext

XDR mendefinisikan AppContext sebagai metadata di mana XDR dapat memahami aplikasi mana yang akan dilanjutkan, bersama dengan konteks di mana aplikasi harus dilanjutkan. Aplikasi dapat menggunakan aktivitas untuk memungkinkan pengguna kembali ke apa yang mereka lakukan di aplikasi mereka, di beberapa perangkat. Aktivitas yang dibuat oleh aplikasi seluler apa pun muncul di perangkat Windows pengguna selama perangkat tersebut telah disediakan Cross Device Experience Host (CDEH).  

Setiap aplikasi berbeda, dan terserah Windows untuk memahami aplikasi target untuk melanjutkan, dan terserah aplikasi tertentu di Windows untuk memahami konteksnya. XDR mengusulkan skema generik yang dapat memenuhi persyaratan untuk semua pihak pertama serta skenario resume aplikasi pihak ketiga.

contextId

  • Diperlukan: Ya
  • Deskripsi: Ini adalah pengidentifikasi unik yang digunakan untuk membedakan satu AppContext dari yang lain. Ini memastikan bahwa setiap AppContext dapat diidentifikasi secara unik.
  • Penggunaan: Pastikan untuk menghasilkan contextId unik untuk setiap AppContext untuk menghindari konflik.

jenis

  • Diperlukan: Ya
  • Deskripsi: Ini adalah flag biner yang menunjukkan jenis AppContext yang dikirim ke Link ke Windows (LTW). Nilai harus konsisten dengan tipe konteks yang diminta.
  • Penggunaan: Atur bendera ini sesuai dengan jenis konteks yang Anda kirim. Contohnya, ProtocolConstants.TYPE_RESUME_ACTIVITY.

createTime

  • Diperlukan: Ya
  • Deskripsi: Tanda waktu ini mewakili waktu pembuatan AppContext.
  • Penggunaan: Rekam waktu yang tepat saat AppContext dibuat.

intentUri

  • Diperlukan: Tidak, jika weblink disediakan
  • Deskripsi: URI ini menunjukkan aplikasi mana yang dapat melanjutkan AppContext yang diserahkan dari perangkat asal.
  • Penggunaan: Berikan ini jika Anda ingin menentukan aplikasi tertentu untuk menangani konteks.
  • Diperlukan: Tidak, jika intentUri disediakan
  • Deskripsi: URI ini digunakan untuk meluncurkan titik akhir web aplikasi jika mereka memilih untuk tidak menggunakan aplikasi penyimpanan. Parameter ini hanya digunakan ketika intentUri tidak disediakan. Jika keduanya disediakan, intentUri akan digunakan untuk melanjutkan aplikasi di Windows.
  • Penggunaan: Hanya untuk digunakan jika aplikasi ingin melanjutkan di titik akhir web dan bukan aplikasi penyimpanan.

appId

  • Diperlukan: Ya
  • Deskripsi: Ini adalah nama paket dari aplikasi yang terkait dengan konteks ini.
  • Penggunaan: Atur ini ke nama paket aplikasi Anda.

title

  • Diperlukan: Ya
  • Deskripsi: Ini adalah judul AppContext, seperti nama dokumen atau judul halaman web.
  • Penggunaan: Berikan judul yang bermakna yang mewakili AppContext.

pratinjau

  • Diperlukan: Tidak
  • Deskripsi: Ini adalah byte gambar pratinjau yang dapat mewakili AppContext.
  • Penggunaan: Berikan gambar pratinjau jika tersedia untuk memberi pengguna representasi visual AppContext.

Seumur hidup

  • Diperlukan: Tidak
  • Deskripsi: Ini adalah masa pakai AppContext dalam milidetik. Ini hanya digunakan untuk skenario yang sedang berlangsung. Jika tidak diatur, nilai defaultnya adalah 5 menit.
  • Penggunaan: Atur ini untuk menentukan berapa lama AppContext seharusnya valid. Anda dapat mengatur nilai hingga maksimum 5 menit. Nilai yang lebih besar akan secara otomatis dipersingkat menjadi 5 menit.

Intent URI

URI memungkinkan Anda meluncurkan aplikasi lain untuk melakukan tugas tertentu, memungkinkan skenario aplikasi-ke-aplikasi yang bermanfaat. Untuk informasi selengkapnya tentang meluncurkan aplikasi menggunakan URI, lihat Luncurkan aplikasi default Windows untuk URI dan Buat Deep Links ke Konten Aplikasi | Pengembang Android.

Menangani respons API di Windows

Bagian ini menjelaskan cara menangani respons API di aplikasi Windows. SDK Kelangsungan menyediakan cara untuk menangani respons API untuk aplikasi Win32 dan WinUI.

Contoh aplikasi Win32

Agar aplikasi Win32 dapat menangani peluncuran URI protokol, langkah-langkah berikut diperlukan:

  1. Pertama, entri perlu dibuat ke registri sebagai berikut:

    [HKEY_CLASSES_ROOT\partnerapp] 
    @="URL:PartnerApp Protocol" 
    "URL Protocol"="" 
    
    [HKEY_CLASSES_ROOT\partnerapp\shell\open\command] 
    @="\"C:\\path\\to\\PartnerAppExecutable.exe\" \"%1\"" 
    
  2. Peluncuran harus ditangani dalam fungsi utama aplikasi 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; 
    } 
    

Aplikasi WinUI

Untuk aplikasi WinUI yang dikemas, URI protokol dapat didaftarkan dalam manifes aplikasi proyek. Langkah-langkah berikut menunjukkan cara menangani aktivasi protokol di aplikasi WinUI.

  1. Pertama, URI protokol terdaftar dalam Package.appxmanifest file sebagai berikut:

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

Contoh WinUI 3

Cuplikan kode berikut menunjukkan cara menangani aktivasi protokol di aplikasi C++ WinUI dengan 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 
     } 
} 

Menggunakan weblink akan meluncurkan titik akhir web aplikasi. Pengembang aplikasi perlu memastikan bahwa weblink yang disediakan dari aplikasi Android mereka valid karena XDR akan menggunakan browser default sistem untuk mengalihkan ke tautan web yang disediakan.

Menangani argumen yang diperoleh dari Resume Lintas Perangkat

Adalah tanggung jawab setiap aplikasi untuk mendeserialisasi dan mendekripsi argumen yang diterima dan memproses informasi yang sesuai untuk mentransfer konteks yang sedang berlangsung dari telepon ke PC. Misalnya, jika panggilan perlu ditransfer, aplikasi harus dapat mengomunikasikan konteks tersebut dari telepon dan aplikasi desktop harus memahami konteks tersebut dengan tepat dan melanjutkan pemuatan.