Avatar ·

Как запустить Activity из другого приложения в Android на Kotlin

Привет, коллеги! Я начинающий Android-разработчик на Kotlin, и у меня возникла проблема. Пишу приложение-лаунчер, которое должно открывать Activity из других установленных приложений. Например, я хочу по нажатию кнопки открыть камеру или карты. Но я не понимаю, как правильно составить Intent и какие данные туда передавать. Вот что я пробовал: ```kotlin val intent = Intent() intent.action = Intent.ACTION_VIEW intent.data = Uri.parse("com.example.targetapp") startActivity(intent) ``` Но это вызывает краш с ошибкой `android.content.ActivityNotFoundException`. Я также пробовал получить PackageManager и вызывать `getLaunchIntentForPackage()`: ```kotlin val pm = packageManager val intent = pm.getLaunchIntentForPackage("com.whatsapp") startActivity(intent) ``` Это работает для WhatsApp, но не для всех приложений. Например, для приложения "Заметки" (com.example.notes) возвращается null. Почему так? И как правильно открыть конкретную Activity внутри приложения, а не просто главный экран? Я читал про `Intent.FLAG_ACTIVITY_NEW_TASK` и `Intent.FLAG_ACTIVITY_CLEAR_TOP`, но не уверен, когда их использовать. Также слышал про `explicit intents` и `implicit intents`. Помогите разобраться! Нужен пример с открытием Activity по имени компонента (ComponentName) и по действию (action). Заранее спасибо! <<>>

Понимание проблемы: явные и неявные Intent'ы

Ваша ошибка типична для новичков. Дело в том, что Intent.ACTION_VIEW с простым URI не может "угадать", какое приложение вы хотите открыть. Для запуска Activity из другого приложения нужно использовать либо явный Intent (с указанием конкретного компонента), либо неявный Intent (с действием и данными, которые система сама сопоставит с зарегистрированными Activity).

Вот правильный подход для Kotlin:

1. Запуск Activity по имени пакета (главный экран приложения)

// Получаем PackageManager
val packageManager = packageManager

// Пытаемся получить Intent для запуска главной Activity приложения
val intent = packageManager.getLaunchIntentForPackage("com.example.targetapp")

if (intent != null) {
    // Важно: для Activity из другого приложения нужен флаг NEW_TASK
    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    startActivity(intent)
} else {
    // Приложение не установлено или не имеет главной Activity
    Toast.makeText(this, "Приложение не найдено", Toast.LENGTH_SHORT).show()
}

2. Запуск конкретной Activity по ComponentName (явный Intent)

Если вы знаете точное имя пакета и имя класса Activity (например, из документации или декомпиляции), используйте:

val intent = Intent()
intent.component = ComponentName(
    "com.example.targetapp",      // packageName
    "com.example.targetapp.MainActivity" // полное имя класса Activity
)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP

try {
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    Toast.makeText(this, "Activity не найдена", Toast.LENGTH_SHORT).show()
}

3. Запуск Activity через неявный Intent (по действию)

Например, открыть камеру для фото:

val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
if (takePictureIntent.resolveActivity(packageManager) != null) {
    // Создаем временный файл для фото
    val photoFile = File.createTempFile("photo_", ".jpg", cacheDir)
    val photoUri = FileProvider.getUriForFile(this, 
        "${packageName}.fileprovider", photoFile)
    
    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri)
    takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    takePictureIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    startActivity(takePictureIntent)
}

Почему getLaunchIntentForPackage() возвращает null?

  • Приложение не имеет главной Activity (например, сервис или библиотека)
  • У приложения нет Activity с intent-filter категории LAUNCHER
  • Приложение не установлено на устройстве

Важные флаги (flags)

  • FLAG_ACTIVITY_NEW_TASK — обязателен при запуске Activity из другого приложения (создает новую задачу)
  • FLAG_ACTIVITY_CLEAR_TOP — если Activity уже запущена, она будет перезапущена (а не создана новая)
  • FLAG_ACTIVITY_SINGLE_TOP — если Activity уже на вершине стека, она не будет создана заново

Проверка наличия Activity

Всегда проверяйте, может ли Intent быть обработан:

if (intent.resolveActivity(packageManager) != null) {
    startActivity(intent)
} else {
    // Обработка ошибки
}

Это защитит от краша ActivityNotFoundException.

<<>>

Альтернативный подход: использование PackageManager для поиска Activity

Если вы не знаете точное имя Activity, но знаете, что она должна обрабатывать определённое действие (например, ACTION_SEND), можно перебрать все Activity в системе:

val intent = Intent(Intent.ACTION_SEND)
intent.type = "text/plain"

val packageManager = packageManager
val activities = packageManager.queryIntentActivities(intent, 0)

if (activities.isNotEmpty()) {
    // Показываем диалог выбора приложения
    val chooser = Intent.createChooser(intent, "Поделиться через")
    chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
    startActivity(chooser)
} else {
    Toast.makeText(this, "Нет приложений для этого действия", Toast.LENGTH_SHORT).show()
}

Дополнительный совет: открытие Activity с данными

Для передачи данных в Activity другого приложения используйте putExtra():

val intent = Intent()
intent.component = ComponentName("com.example.notes", 
    "com.example.notes.EditNoteActivity")
intent.putExtra("note_id", 123)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)

startActivity(intent)

Обратите внимание: чтобы это работало, целевая Activity должна быть экспортируемой (иметь android:exported="true" в манифесте). Начиная с Android 12, все Activity должны явно указывать этот атрибут.

Проверка установки приложения

Перед запуском проверьте, установлено ли приложение:

fun isAppInstalled(packageName: String): Boolean {
    return try {
        packageManager.getPackageInfo(packageName, 0)
        true
    } catch (e: PackageManager.NameNotFoundException) {
        false
    }
}

Используйте этот метод для красивого UX — показывайте кнопку "Установить" вместо краша.