Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Мобильные устройства Android, устанавливающие пакет "Ссылка на Windows", могут программно предоставлять доступ к недавним задачам из приложения Android, которые будут продолжаться на компьютере с Windows (например, URL-адреса веб-сайта, ссылки на документы, музыкальные треки и т. д.).
Непрерывность задач на нескольких устройствах развивается, чтобы использовать пакет SDK непрерывности для обеспечения более глубокой интеграции с панелью задач Windows, более эффективно обслуживая клиентов естественным и интуитивно понятным способом. Хотя исходная реализация приложения непрерывности задач Phone Link по-прежнему поддерживается, для новых реализаций рекомендуется использовать перекрестное возобновление устройств (XDR) в пакете SDK непрерывности для интеграции с панелью задач Windows. Дополнительные сведения: перекрестное возобновление устройств (XDR) с помощью пакета SDK непрерывности (приложения Android и Windows).
Пакет SDK непрерывности обеспечивает более простое взаимодействие с несколькими устройствами с помощью значков продолжения задач для перекрестного устройства (XDR), которые помогут вам возобновить последние задачи устройства Android непосредственно на панели задач Windows (без необходимости полагаться на интерфейс приложения Phone Link).
Интеграция приложения Android с непрерывностью задач Phone Link
Узнайте, как программно предоставлять доступ к недавним задачам из приложения Android (например, URL-адреса веб-сайта, ссылки на документы, музыкальные треки и т. д.) на компьютер с Windows, настроив телефонную ссылку. Эта функция доступна только на поддерживаемых устройствах для взаимодействия с телефонным каналом.
Требования к сценарию
Для доступа к непрерывности задач "Связь с Windows" приложения Android необходимо выполнить следующие условия:
- Синхронизация допустимых ВЕБ-URL-адресов, доступных компьютером Windows
- Ссылки на облачные документы do sync, доступные на компьютере с Windows
- Do sync local document links to the Windows PC, который должен быть доступен на мобильном устройстве через приложение
- Не синхронизировать более 60 раз в минуту
- НЕ синхронизируйте содержимое, если пользователь не взаимодействует с вашим приложением
Область "Возобновление работы телефонов" и "Перекрестное устройство"
Ссылка на телефон будет отображать содержимое синхронизации в узле "Приложения" в разделе "Недавно использованные" и "Последние веб-сайты" и во всплывающем элементе уведомлений.
Перекрестное возобновление устройств (XDR) с помощью пакета SDK для непрерывности (Приложения Android и Windows) будет отображать содержимое синхронизации на панели задач Windows.
Утверждение функции ограниченного доступа (LAF)
Непрерывность задач телефонной связи — это функция ограниченного доступа (LAF). Чтобы получить доступ, необходимо получить утверждение от Корпорации Майкрософт, чтобы взаимодействовать с предварительно загруженным пакетом Link to Windows на мобильных устройствах Android.
Чтобы запросить доступ, отправьте сообщение электронной почты wincrossdeviceapi@microsoft.com с указанными ниже сведениями.
- Описание пользовательского интерфейса
- Снимок экрана: приложение, в котором пользователь обращается к веб-сайту или документам
- PackageId приложения
- Ссылка на магазин Google Play для приложения
Если запрос утвержден, вы получите инструкции по разблокировке функции. Утверждения будут основаны на ваших сообщениях, если ваш сценарий соответствует требованиям сценария , описанным выше.
Обработка данных
Используя непрерывность задач телефонной связи, корпорация Майкрософт будет обрабатывать и передавать данные в соответствии с соглашением о службах Майкрософт и заявлением о конфиденциальности Майкрософт. Данные, передаваемые на связанные устройства пользователя, могут обрабатываться через облачные службы Майкрософт, чтобы обеспечить надежную передачу данных между устройствами. Данные, обрабатываемые этим API, не сохраняются облачными службами Майкрософт, подлежащими контролю пользователей.
Пакет SDK непрерывности, который будет интегрироваться в пакет приложения, гарантирует, что данные, предоставленные API, обрабатываются только доверенными пакетами Майкрософт.
Примеры кода интеграции с телефонным каналом
Ниже приведены общие рекомендации и примеры кода для интеграции. Подробные инструкции по интеграции см. в документе Kotlin пакета SDK.
Объявления манифеста приложения Android
Манифест приложения — это XML-файл, который служит схемой для приложения Android. Файл объявления предоставляет сведения операционной системе о структуре, компонентах, разрешениях и т. д. Следующие объявления необходимы для обеспечения непрерывности задач с помощью ссылки на Windows.
Метаданные компонентов
Партнерские приложения должны сначала зарегистрировать метаданные в манифесте приложения.
Чтобы участвовать в контракте контекста приложения, метаданные должны быть объявлены для поддерживаемого типа контекста приложения. Например, чтобы добавить метаданные поставщика контекста приложения для функции handoff приложения :
<application...>
<meta-data
android:name="com.microsoft.crossdevice.applicationContextProvider"
android:value="true" />
</application>
Если приложение поддерживает несколько типов контекста приложения, необходимо добавить каждый тип метаданных. Типы метаданных, поддерживаемые в настоящее время, включают:
<meta-data
android:name="com.microsoft.crossdevice.browserContextProvider"
android:value="true" />
<meta-data
android:name="com.microsoft.crossdevice.applicationContextProvider"
android:value="true" />
<meta-data
android:name="com.microsoft.crossdevice.resumeActivityProvider
android:value="true" />
Чтобы добавить новый тип, формат имени метаданных должен иметь формат com.microsoft.crossdevice.xxxProvider.
Приложения также должны объявлять мета-данные типа триггера в манифесте. Эти объявления помогают системе определить, как и когда приложение должно уведомлять Load-Time Weaving (LTW) о некоторых функциях, активных.
Для триггера самостоятельного уведомления, где само приложение отвечает за уведомление системы и включается на всех устройствах, независимо от изготовителя оборудования (OEM), тип триггера должен быть объявлен следующим образом:
<application ...
<meta-data
android:name="com.microsoft.crossdevice.trigger.PartnerApp"
android:value="the sum value of all features' binary codes" />
</application>
Для триггера системного API, в котором приложение использует системные API для активации функции "Ссылка на Windows", включенной только на определенных устройствах OEM, тип триггера должен быть объявлен как:
<application ...
<meta-data
android:name="com.microsoft.crossdevice.trigger.SystemApi"
android:value="the sum value of all features' binary codes" />
</application>
Двоичные коды функций теперь:
APPLICATION_CONTEXT: 1
BROWSER_HISTORY: 2
RESUME_ACTIVITY: 4
Регистрация манифеста приложения может выглядеть следующим образом:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<application …
<!--
This is the meta-data represents this app supports XDR, LTW will check
the package before we request app context.
-->
<meta-data
android:name="com.microsoft.crossdevice.resumeActivityProvider"
android:value="true" />
<!--
This is the meta-data represents this app supports trigger from app, the
Value is the code of XDR feature, LTW will check if the app support partner
app trigger when receiving trigger broadcast.
-->
<meta-data
android:name="com.microsoft.crossdevice.trigger.PartnerApp"
android:value="4" />
</application>
</manifest>
Пример кода для отправки контекста приложения
После добавления объявлений манифеста приложения потребуется:
Определите подходящее время для вызова функций Initialize и DeInitialize для пакета SDK непрерывности. После вызова функции инициализации следует активировать обратный вызов, который реализует
IAppContextEventHandler.После инициализации пакета SDK непрерывности, если
onContextRequestReceived()он вызывается, он указывает, что подключение установлено. Затем приложение может отправитьAppContext(включая создание и обновление) в LTW или удалитьAppContextиз LTW.
Не забудьте избежать отправки конфиденциальных данных, AppContextнапример маркеров доступа. Кроме того, если время существования задано слишком коротко, AppContext срок действия может истекать до отправки на компьютер. Рекомендуется установить минимальное время существования не менее 5 минут.
class MainActivity : AppCompatActivity() {
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()
}
}
}
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
val buttonSend: Button = findViewById(R.id.buttonSend)
val buttonDelete: Button = findViewById(R.id.buttonDelete)
val buttonUpdate: Button = findViewById(R.id.buttonUpdate)
setButtonDisabled(buttonSend)
setButtonDisabled(buttonDelete)
setButtonDisabled(buttonUpdate)
buttonSend.setOnClickListener {
if (ready) {
sendAppContext()
}
}
buttonDelete.setOnClickListener {
if (ready) {
deleteAppContext()
}
}
buttonUpdate.setOnClickListener {
if (ready) {
updateAppContext()
}
}
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 app context to LTW
private fun sendAppContext() {
val appContext = AppContext().apply {
this.contextId = generateContextId()
this.appId = applicationContext.packageName
this.createTime = System.currentTimeMillis()
this.lastUpdatedTime = System.currentTimeMillis()
// Set the type of app context, for example, resume activity.
this.type = ProtocolConstants.TYPE_RESUME_ACTIVITY
// Set the rest fields in appContext
//……
}
_currentAppContext.value = appContext
AppContextManager.sendAppContext(this.applicationContext, appContext, appContextResponse)
}
// Delete app context from LTW
private fun deleteAppContext() {
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")
}
}
// Update app context from LTW
private fun updateAppContext() {
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)
}
private fun generateContextId(): String {
return "${packageName}.${UUID.randomUUID()}"
}
}
Для всех обязательных и необязательных полей см. описание AppContext.
Описание AppContext
Следующие значения должны предоставляться партнерскими приложениями при отправке контекста приложения:
| Ключ | Значение | Дополнительные сведения |
|---|---|---|
| contextId [обязательно] | Используется для отличия от других контекстов приложения. | Уникальный для каждого контекста приложения. Формат: "${packageName}.${UUID.randomUUID()}" |
| тип [обязательный] | Двоичный флаг, указывающий, какой тип контекста приложения отправляется в LTW. | Значение должно быть согласовано с запрошеннымContextType выше |
| createTime[обязательный] [FR1] | Метка времени создания контекста приложения. | |
| LastUpdatedTime[обязательный] | Метка времени последнего обновления контекста приложения. | В любое время, когда обновляются все поля контекста приложения, необходимо записать обновленное время. |
| teamId [необязательно] | Используется для идентификации организации или группы, к которой принадлежит приложение. | |
| intentUri [необязательно] | Используется для указания того, какое приложение может продолжить контекст приложения, переданный с исходного устройства. | Максимальная длина — 2083 символа. |
| appId [необязательно] | Пакет приложения, для который используется контекст. | |
| title[необязательно] | Заголовок этого контекста приложения, например имя документа или название веб-страницы. | |
| weblink[необязательно] | URL-адрес веб-страницы для загрузки в браузере для продолжения контекста приложения. | Максимальная длина — 2083 символа. |
| preview[необязательно] | Байт изображения предварительного просмотра, который может представлять контекст приложения | |
| extras[необязательный] | Объект пары "ключ-значение", содержащий сведения о состоянии конкретного приложения, необходимые для продолжения контекста приложения на продолжающемся устройстве. | Необходимо указать, когда контекст приложения имеет уникальные данные. |
| LifeTime[необязательно] | Время существования контекста приложения в миллисекундах. | Используется только для текущего сценария, если не задано, значение по умолчанию — 30 дней). |
Пример кода непрерывности браузера
В этом примере выделен тип непрерывности браузера , который отличается от других AppContext типов.
class MainActivity : AppCompatActivity() {
private val appContextResponse = object : IAppContextResponse {
override fun onContextResponseSuccess(response: AppContext) {
Log.d("MainActivity", "onContextResponseSuccess")
}
override fun onContextResponseError(response: AppContext, throwable: Throwable) {
Log.d("MainActivity", "onContextResponseError: ${throwable.message}")
}
}
private lateinit var appContextEventHandler: IAppContextEventHandler
private val browserHistoryContext: BrowserHistoryContext = BrowserHistoryContext()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//……
LogUtils.setDebugMode(true)
var ready = false
val buttonSend: Button = findViewById(R.id.buttonSend)
val buttonDelete: Button = findViewById(R.id.buttonDelete)
setButtonDisabled(buttonSend)
setButtonDisabled(buttonDelete)
buttonSend.setOnClickListener {
if (ready) {
sendBrowserHistory ()
}
}
buttonDelete.setOnClickListener {
if (ready) {
clearBrowserHistory ()
}
}
appContextEventHandler = object : IAppContextEventHandler {
override fun onContextRequestReceived(contextRequestInfo: ContextRequestInfo) {
LogUtils.d("MainActivity", "onContextRequestReceived")
ready = true
setButtonEnabled(buttonSend)
setButtonEnabled(buttonDelete)
}
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)
}
// Send browser history to LTW
private fun sendBrowserHistory () {
browserHistoryContext.setAppId(this.packageName)
browserHistoryContext.addBrowserContext(System.currentTimeMillis(),
Uri.parse("https://www.bing.com/"), "Bing Search", null
)
AppContextManager.sendAppContext(this.applicationContext, browserHistoryContext, appContextResponse)
}
// Clear browser history from LTW
private fun clearBrowserHistory() {
browserHistoryContext.setAppId(this.packageName)
browserHistoryContext.setBrowserContextEmptyFlag(true)
AppContextManager.sendAppContext(this.applicationContext, browserHistoryContext, appContextResponse)
}
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)
}
//……
}
Все обязательные и необязательные поля см. в описании BrowserContext.
Описание BrowserContext
Партнерские addBrowserContext приложения могут вызывать метод для добавления журнала браузера. При добавлении журнала браузера необходимо указать следующие значения:
| Ключ | Значение |
|---|---|
| browserWebUri [обязательно] | Веб-URI, который откроется в браузере на компьютере (http: или https:). |
| title [обязательный] | Заголовок веб-страницы. |
| метка времени [обязательный] | Метка времени, которую веб-страница была открыта или обновлена. |
| favIcon [необязательно] | Favicon веб-страницы в байтах должен быть небольшим в целом. |
Шаги проверки интеграции
Подготовьте , убедитесь, что установлен частный LTW. Убедитесь, что LTW подключен к компьютеру: как управлять мобильным устройством на компьютере. Убедитесь, что LTW подключен к телефонной ссылке: требования к телефонной связи и настройка. Если после сканирования QR-кода вы не можете перейти в LTW, сначала откройте LTW и проверьте QR-код в приложении. Наконец, убедитесь, что партнерское приложение интегрировано с пакетом SDK непрерывности.
Проверьте , запустите приложение и инициализирует пакет SDK непрерывности. Убедитесь, что
onContextRequestReceived()вызывается. ПослеonContextRequestReceived()вызова приложение может отправить контекст приложения в LTW. ЕслиonContextResponseSuccess()после отправки контекста приложения вызывается, интеграция пакета SDK выполняется успешно.
Репозиторий Windows cross-Device на GitHub
Сведения об интеграции пакета SDK windows cross-Device в проект в репозитории Windows-Cross-Device на сайте GitHub.
часто задаваемые вопросы Связь с телефоном
Список часто задаваемых вопросов см. в Связь с телефоном часто задаваемых вопросов.
Windows developer