Sửa lỗi ActivityNotFoundException: No Activity Found to Handle Intent trên Android

intermediate📱 Android2026-05-13| Android (API 1+), ảnh hưởng tất cả phiên bản — kiểm soát package visibility nghiêm ngặt hơn trên Android 11+ (API 30+)

Error Message

android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=... }
#intent#activity#manifest#implicit-intent

Điều Gì Đã Xảy Ra

App của bạn đã gọi startActivity(intent) với một implicit intent, và Android không tìm thấy gì — không có app nào được cài đặt khai báo intent filter khớp với những gì bạn yêu cầu. Lỗi crash hiển thị trong logcat như sau:

android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=https://example.com }
  at android.app.Instrumentation.checkStartActivityResult(Instrumentation.java:2105)
  at android.app.Instrumentation.execStartActivity(Instrumentation.java:1747)
  at android.app.Activity.startActivity(Activity.java:5258)

Đây không phải lỗi về quyền truy cập hay cấu hình manifest sai ở phía bạn. Không có app nào trên thiết bị khai báo intent filter cho những gì bạn đang yêu cầu. Tuy nhiên, vẫn có một vài lý do cụ thể khiến điều này xảy ra ngay cả trên những thiết bị mà bạn kỳ vọng nó sẽ hoạt động bình thường.

Quá Trình Debug

1. Xác nhận chính xác intent đang được kích hoạt

Log intent của bạn trước khi gọi startActivity:

Log.d("IntentDebug", intent.toString());

Chú ý kỹ ba điều: Action có đúng không? URI scheme có đúng không — https:// so với http:// so với scheme tùy chỉnh như myapp://? MIME type có được đặt khi không nên, hoặc bị thiếu khi handler yêu cầu nó?

2. Kiểm tra xem có app nào xử lý được không

Gọi resolveActivity() trước khi chạm vào startActivity():

if (intent.resolveActivity(getPackageManager()) != null) {
    startActivity(intent);
} else {
    Log.e("IntentDebug", "No handler found for: " + intent.toString());
    Toast.makeText(this, "No app found to handle this action", Toast.LENGTH_SHORT).show();
}

Kết quả null xác nhận vấn đề. Trên Android 11+, null cũng có thể có nghĩa là app đích tồn tại nhưng không hiển thị với app của bạn — chi tiết hơn ở phần bên dưới.

3. Package visibility trên Android 11+ (cái bẫy tinh vi)

API 30 đã giới thiệu tính năng lọc khả năng hiển thị package. Dù Chrome hay Gmail đã được cài đặt, app của bạn không thể truy vấn chúng nếu không khai báo intent trong block <queries> ở manifest. Bỏ qua block đó và resolveActivity() trả về null — ngay cả trên thiết bị có trình duyệt hoàn toàn tốt đang chạy ở đó. Đây là nguyên nhân phổ biến nhất trên các thiết bị chạy Android 11 trở lên.

Giải Pháp

Fix 1: Thêm vào AndroidManifest.xml (Android 11+)

Khai báo các intent pattern mà app của bạn cần truy vấn. Dưới đây là ba trường hợp bạn sẽ gặp thường xuyên nhất:

<!-- Mở URL trong trình duyệt -->
<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
        <data android:scheme="https" />
    </intent>
</queries>
<!-- Gửi email -->
<queries>
    <intent>
        <action android:name="android.intent.action.SENDTO" />
        <data android:scheme="mailto" />
    </intent>
</queries>
<!-- Gọi điện thoại -->
<queries>
    <intent>
        <action android:name="android.intent.action.DIAL" />
        <data android:scheme="tel" />
    </intent>
</queries>

Bạn có thể gộp nhiều block <intent> vào trong một element <queries> duy nhất. Một lưu ý quan trọng về vị trí đặt: <queries> phải là phần tử con trực tiếp của <manifest>, không được lồng bên trong <application>.

Fix 2: Bọc startActivity trong try/catch

Dù đã có <queries>, một try/catch phòng thủ vẫn cứu bạn trong các trường hợp đặc biệt. Các thiết bị Android giá rẻ — đặc biệt là những máy chạy ROM OEM tùy chỉnh từ các nhà sản xuất như Lava hay Tecno — đôi khi xuất xưởng mà không có trình duyệt hay email client mặc định nào:

try {
    Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"));
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    Toast.makeText(this, "No browser installed", Toast.LENGTH_SHORT).show();
}

Trong Kotlin:

val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
try {
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    Toast.makeText(this, "No app found to open this link", Toast.LENGTH_SHORT).show()
}

Fix 3: Dùng Intent.createChooser để chia sẻ

Với ACTION_SEND, chooser thường hiển thị dialog trống thay vì crash. Nhưng nếu không có app nào hiển thị do quy tắc visibility của API 30, nó vẫn ném exception. Hãy xử lý cả hai trường hợp:

Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, "Check this out!");

Intent chooser = Intent.createChooser(shareIntent, "Share via");
if (shareIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

Thêm query tương ứng vào manifest của bạn:

<queries>
    <intent>
        <action android:name="android.intent.action.SEND" />
        <data android:mimeType="text/plain" />
    </intent>
</queries>

Fix 4: Custom URI scheme không có handler

Các scheme tùy chỉnh như myapp://action sẽ thất bại hoàn toàn khi app đích chưa được cài đặt. Kiểm tra trước khi kích hoạt, và chuẩn bị sẵn một web fallback:

Uri customUri = Uri.parse("myapp://open/profile/123");
Intent deepLink = new Intent(Intent.ACTION_VIEW, customUri);

if (deepLink.resolveActivity(getPackageManager()) != null) {
    startActivity(deepLink);
} else {
    // App chưa được cài đặt — chuyển sang web
    Intent fallback = new Intent(Intent.ACTION_VIEW, Uri.parse("https://myapp.com/profile/123"));
    startActivity(fallback);
}

Kiểm Tra

Chạy app trên thiết bị thật — emulator có thể che khuất các vấn đề visibility chỉ xuất hiện trên phần cứng thực tế. Xác nhận bốn điều sau:

  • Intent được resolve mà không bị crash.
  • Logcat không hiển thị stack trace ActivityNotFoundException.
  • Luồng hoạt động đúng trên thiết bị chạy Android 11+ (API 30), nơi các quy tắc visibility thực sự có hiệu lực.
  • Đường dẫn fallback của bạn kích hoạt đúng cách trên thiết bị ít app hoặc emulator image không có GApps.

Muốn xác minh block <queries> của bạn đang hoạt động không? Dump những gì app của bạn thực sự thấy được lúc runtime:

// Kotlin — liệt kê tất cả app có thể xử lý ACTION_VIEW cho https
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
val activities = packageManager.queryIntentActivities(intent, 0)
activities.forEach { Log.d("PM", it.activityInfo.packageName) }

Danh sách trống dù đã thêm <queries>? Kiểm tra lại scheme — httpshttp được coi là các mục riêng biệt và cần được khai báo riêng.

Bài Học Rút Ra

  • Implicit intent không có null check hoặc try/catch là quả bom hẹn giờ. Sự đa dạng của thiết bị Android rất khắc nghiệt — bạn không thể giả định bất kỳ app cụ thể nào được cài đặt, kể cả Chrome.
  • Android 11+ đã chuyển package visibility sang cơ chế opt-in, không còn là opt-out. Mọi implicit intent mà app của bạn kích hoạt đều cần một entry <queries> tương ứng. Không có nó, resolveActivity() trả về null ngay cả khi app đích đang ở ngay đó trên thiết bị.
  • Thiết bị factory-reset và thiết bị giá rẻ phơi bày lỗ hổng trong logic fallback của bạn. Điện thoại giá rẻ thường không có trình duyệt mặc định, email client hay map app. Nếu fallback của bạn chỉ là một comment trong code, đó không phải là fallback thực sự.
  • Một cách hiểu giúp ích: <queries> không phải về quyền truy cập. Bạn không xin Android cấp quyền — bạn đang cho nó biết intent filter nào mà app của bạn cần nhận biết tại thời điểm truy vấn.

Related Error Notes