Введение: зачем вызывать Activity из другого приложения?
Представьте, что вы создаете приложение для заметок, и хотите дать пользователю возможность сделать фото прямо из вашего интерфейса. Или, допустим, ваш мессенджер должен открывать контакты из адресной книги. В обоих случаях вам нужно запустить Activity из другого приложения в Android.
Эта задача — один из самых популярных вопросов на Stack Overflow (тысячи просмотров и сотни голосов). И не зря: если сделать это неправильно, приложение может упасть с ActivityNotFoundException или, что ещё хуже, создать угрозу безопасности. В этом туториале мы разберём все нюансы на языке Kotlin, чтобы вы могли смело интегрировать внешние экраны в свои проекты.
Кстати, вы на сайте PythonLib, где мы часто говорим о Python, но сегодня — особый случай. Android-разработка на Kotlin — это мощный навык, который отлично сочетается с бэкендом на Python. Поехали!
Что такое Intent и почему это ключ к решению?
В Android Intent — это сообщение, которое говорит системе: «Эй, мне нужно сделать что-то с таким-то действием». Для запуска Activity из другого приложения используются два подхода:
- Явный Intent — вы точно знаете, какой компонент хотите запустить (например,
com.example.target.MainActivity). Работает только если у вас есть доступ к коду этого приложения. - Неявный Intent — вы описываете, что хотите сделать (например, «открыть карту по координатам»), а система сама находит подходящее приложение. Это более гибкий и безопасный способ для вызова внешнего Activity.
Мы сосредоточимся на неявных Intent, так как они чаще всего используются для межпроцессного взаимодействия Android.
Базовый пример: открываем карту по координатам
Начнём с простого примера, который демонстрирует, как запустить Activity из другого приложения для отображения карты.
// Kotlin
val gmmIntentUri = Uri.parse("geo:55.7558,37.6173?z=15")
val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri)
mapIntent.setPackage("com.google.android.apps.maps")
// Проверяем, есть ли приложение, которое может обработать Intent
if (mapIntent.resolveActivity(packageManager) != null) {
startActivity(mapIntent)
} else {
// Показываем пользователю сообщение об ошибке
Toast.makeText(this, "Google Карты не установлены", Toast.LENGTH_SHORT).show()
}
Что здесь происходит? Мы создаём Intent с действием ACTION_VIEW и URI, содержащим координаты. Затем с помощью setPackage мы явно указываем, что хотим использовать Google Карты (хотя это необязательно — система может предложить выбор). Самое важное — проверка через resolveActivity. Это спасает от краша, если нужное приложение отсутствует на устройстве.
Сценарий: вызываем камеру и получаем результат
Теперь перейдём к более сложному кейсу — запуск Activity из другого приложения с ожиданием результата. Классический пример: сделать фото и получить его в своём приложении.
// Kotlin
private const val REQUEST_IMAGE_CAPTURE = 1
private fun dispatchTakePictureIntent() {
val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
// Проверяем, что камера доступна
if (takePictureIntent.resolveActivity(packageManager) != null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
} else {
// Обработка ошибки: камера не найдена
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == Activity.RESULT_OK) {
val imageBitmap = data?.extras?.get("data") as Bitmap
// Теперь у вас есть фото! Используйте его.
imageView.setImageBitmap(imageBitmap)
}
}
Обратите внимание: startActivityForResult (и его современная альтернатива — ActivityResultLauncher, о которой ниже) позволяет получить данные обратно. Без этого вы бы просто открыли камеру, но не смогли бы забрать снимок. Это ключевой момент для вызова внешнего Activity с обменом данными.
Современный подход: Activity Result API (Kotlin)
Начиная с AndroidX Activity 1.2.0, Google рекомендует использовать Activity Result API вместо устаревшего startActivityForResult. Он безопаснее, чище и отлично работает с Kotlin.
// Kotlin
private val takePictureLauncher = registerForActivityResult(
ActivityResultContracts.TakePicturePreview()
) { bitmap: Bitmap? ->
// Обработка результата
bitmap?.let {
imageView.setImageBitmap(it)
}
}
// Запуск камеры
private fun capturePhoto() {
takePictureLauncher.launch(null)
}
Этот код делает то же самое, что и предыдущий пример, но без лишнего шаблонного кода. Activity Result API автоматически обрабатывает lifecycle и проверки, что делает запуск Activity из другого приложения более надёжным.
Безопасность: защита от ActivityNotFoundException
Самая распространённая ошибка новичков — пропуск проверки наличия Activity. Без неё ваше приложение упадёт с ActivityNotFoundException, если целевое приложение не установлено. Всегда используйте resolveActivity или queryIntentActivities:
// Kotlin
val intent = Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, "Привет, мир!")
}
val activities = packageManager.queryIntentActivities(intent, 0)
if (activities.isNotEmpty()) {
startActivity(Intent.createChooser(intent, "Поделиться через"))
} else {
// Ни одно приложение не может обработать этот Intent
}
Обратите внимание на Intent.createChooser — это обязательный элемент для неявных Intent, когда пользователь должен выбрать из нескольких приложений. Без него система может выбрать приложение по умолчанию без диалога, что сбивает с толку.
Практический пример: интеграция с контактами
Давайте соберём всё вместе и создадим функцию, которая открывает контакты и возвращает номер телефона. Это реальный сценарий для запуска Activity из другого приложения в Android.
// Kotlin
private val pickContactLauncher = registerForActivityResult(
ActivityResultContracts.PickContact()
) { uri: Uri? ->
uri?.let {
// Получаем данные из контент-провайдера
val cursor = contentResolver.query(it, null, null, null, null)
cursor?.use { c ->
if (c.moveToFirst()) {
val nameIndex = c.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME)
val name = c.getString(nameIndex)
contactNameTextView.text = name
}
}
}
}
private fun openContacts() {
pickContactLauncher.launch(null)
}
Этот код использует специальный контракт Pick