قصدها و فیلترهای قصد

Intent یک شیء پیام‌رسانی است که می‌توانید از آن برای درخواست کنش از جزء برنامه دیگری استفاده کنید. اگرچه قصدها ارتباط بین مؤلفه‌ها را به روش‌های مختلفی تسهیل می‌کنند، سه مورد استفاده اساسی وجود دارد:

  • شروع فعالیت

    Activity نشان‌دهنده یک صفحه در برنامه است. می‌توانید با ارسال Intent به startActivity()، نمونه جدیدی از Activity را شروع کنید. Intent فعالیت را برای شروع توصیف می‌کند و داده‌های لازم را به‌همراه دارد.

    اگر می‌خواهید پس‌از اتمام فعالیت نتیجه‌ای از آن دریافت کنید، startActivityForResult() را فراخوانی کنید. فعالیت شما نتیجه را به‌صورت شیء Intent جداگانه در فراخوانی برگشتی onActivityResult() فعالیتتان دریافت می‌کند. برای اطلاعات بیشتر، راهنمای فعالیت‌ها را ببینید.

  • شروع کردن سرویس

    Service مؤلفه‌ای است که عملیات را در پس‌زمینه بدون میانای کاربر انجام می‌دهد. با Android نسخه ۵.۰ (سطح میانای برنامه‌سازی کاربردی ۲۱) و بالاتر، می‌توانید سرویسی را با JobScheduler شروع کنید. برای اطلاعات بیشتر درباره JobScheduler، API-reference documentation آن را ببینید.

    برای نسخه‌های قدیمی‌تر از Android 5.0 (سطح میانای برنامه‌سازی کاربردی ۲۱)، می‌توانید بااستفاده از روش‌های کلاس Service سرویسی را شروع کنید. می‌توانید سرویسی را برای انجام عملیات یک‌باره (مثل بارگیری فایل) با ارسال Intent به startService() شروع کنید. Intent سرویس را برای شروع توصیف می‌کند و داده‌های لازم را به‌همراه دارد.

    اگر سرویس با میانای سرویس‌گیرنده-سرور طراحی شده باشد، می‌توانید با ارسال Intent به bindService()، از مؤلفه دیگری به سرویس متصل شوید. برای اطلاعات بیشتر، راهنمای خدمات را ببینید.

  • درحال ارسال همه‌فرستی

    پخش همگانی پیامی است که هر برنامه‌ای می‌تواند دریافت کند. سیستم برای رویدادهای سیستم، مانند زمانی که سیستم راه‌اندازی می‌شود یا دستگاه شروع به شارژ می‌کند، پخش‌های مختلفی را ارائه می‌دهد. می‌توانید با ارسال Intent به sendBroadcast() یا sendOrderedBroadcast()، همه‌فرستی را به برنامه‌های دیگر تحویل دهید.

بقیه این صفحه توضیح می‌دهد که هدف‌ها چگونه کار می‌کنند و چگونه باید از آن‌ها استفاده کرد. برای اطلاعات مرتبط، تعامل با برنامه‌های دیگر و هم‌رسانی محتوا را ببینید.

انواع هدف

‫دو نوع قصد وجود دارد:

  • هدف‌های صریح با مشخص کردن ComponentName کامل، مشخص می‌کنند که کدام مؤلفه از کدام برنامه هدف را برآورده می‌کند. معمولاً از هدف صریح برای شروع مؤلفه‌ای در برنامه خودتان استفاده می‌کنید، زیرا نام کلاس فعالیت یا سرویسی را که می‌خواهید شروع کنید می‌دانید. برای مثال، ممکن است در پاسخ به کنش کاربر، فعالیت جدیدی را در برنامه‌تان شروع کنید یا سرویسی را برای بارگیری فایل در پس‌زمینه شروع کنید.
  • هدف‌های ضمنی نام مؤلفه خاصی را ذکر نمی‌کنند، بلکه کنش عمومی‌ای را برای اجرا اعلام می‌کنند که به مؤلفه‌ای از برنامه دیگر اجازه می‌دهد آن را مدیریت کند. برای مثال، اگر می‌خواهید مکانی را روی نقشه به کاربر نشان دهید، می‌توانید از قصد ضمنی استفاده کنید تا از برنامه توانمند دیگری درخواست کنید مکان مشخص‌شده‌ای را روی نقشه نشان دهد.

شکل ۱ نشان می‌دهد که چگونه از یک intent هنگام شروع یک فعالیت استفاده می‌شود. وقتی شیء Intent صریحاً نام مؤلفه فعالیت خاصی را ذکر می‌کند، سیستم فوراً آن مؤلفه را راه‌اندازی می‌کند.

شکل ۱. نحوه ارسال قصد ضمنی ازطریق سیستم برای شروع فعالیتی دیگر: [۱] فعالیت الف یک Intent با شرح کنش ایجاد می‌کند و آن را به startActivity() ارسال می‌کند. [۲] «سیستم Android» همه برنامه‌ها را برای پیدا کردن فیلتر قصدی که با قصد مطابقت داشته باشد جستجو می‌کند. وقتی تطابقی پیدا شد، [۳] سیستم فعالیت تطابق (فعالیت B) را با فراخوانی روش onCreate() و ارسال Intent به آن شروع می‌کند.

وقتی از قصد ضمنی استفاده می‌کنید، سیستم Android با مقایسه محتوای قصد با فیلترهای قصد اعلام‌شده در فایل مانیفست سایر برنامه‌های موجود در دستگاه، مؤلفه مناسب را برای شروع پیدا می‌کند. اگر قصد با فیلتر قصد مطابقت داشته باشد، سیستم آن مؤلفه را راه‌اندازی می‌کند و شیء Intent را به آن تحویل می‌دهد. اگر چند فیلتر قصد سازگار باشند، سیستم کادر گفتگویی نمایش می‌دهد تا کاربر بتواند برنامه موردنظر را برای استفاده انتخاب کند.

فیلتر قصد عبارتی در فایل مانیفست برنامه است که نوع قصدهایی را که مؤلفه مایل به دریافت آن‌ها است مشخص می‌کند. برای مثال، با اعلام فیلتر قصد برای یک فعالیت، این امکان را برای برنامه‌های دیگر فراهم می‌کنید که مستقیماً فعالیت شما را با نوع خاصی از قصد شروع کنند. به‌همین ترتیب، اگر هیچ فیلتر قصدی را برای فعالیتی اعلام نکنید، آن فعالیت فقط با قصد صریح می‌تواند شروع شود.

احتیاط: برای اطمینان از ایمن بودن برنامه‌تان، همیشه هنگام راه‌اندازی Service از قصد صریح استفاده کنید و فیلترهای قصد را برای سرویس‌هایتان اعلام نکنید. استفاده از هدف غیرصریح برای شروع سرویس، خطر امنیتی دارد زیرا نمی‌توانید مطمئن باشید کدام سرویس به هدف پاسخ می‌دهد، و کاربر نمی‌تواند ببیند کدام سرویس شروع می‌شود. از Android نسخه ۵.۰ (سطح میانای برنامه‌سازی کاربردی ۲۱)، اگر bindService() را با هدف ضمنی فراخوانی کنید، سیستم استثنایی را ایجاد می‌کند.

درحال ساختن قصد

شیء Intent اطلاعاتی را حمل می‌کند که سیستم Android از آن برای تعیین اینکه کدام مؤلفه را شروع کند (مانند نام دقیق مؤلفه یا دسته مؤلفه‌ای که باید قصد را دریافت کند) استفاده می‌کند، به‌علاوه اطلاعاتی که مؤلفه گیرنده برای انجام صحیح کنش استفاده می‌کند (مانند کنشی که باید انجام شود و داده‌هایی که باید روی آن‌ها کنش انجام شود).

اطلاعات اصلی موجود در Intent عبارت است از:

نام مؤلفه
نام مؤلفه برای شروع.

این مورد اختیاری است، اما بخش مهمی از اطلاعات است که قصد را صریح می‌کند، به این معنی که قصد باید فقط به مؤلفه برنامه تعریف‌شده توسط نام مؤلفه تحویل داده شود. بدون نام مؤلفه، هدف ضمنی است و سیستم تصمیم می‌گیرد که کدام مؤلفه باید هدف را براساس اطلاعات هدف دیگر (مانند کنش، داده، و دسته—که در زیر شرح داده شده است) دریافت کند. اگر لازم است مؤلفه خاصی را در برنامه‌تان شروع کنید، باید نام مؤلفه را مشخص کنید.

توجه: هنگام شروع Service، همیشه نام مؤلفه را مشخص کنید. درغیراین‌صورت، نمی‌توانید مطمئن باشید که کدام سرویس به قصد پاسخ خواهد داد و کاربر نمی‌تواند ببیند کدام سرویس شروع می‌شود.

این فیلد از Intent یک شیء ComponentName است که می‌توانید آن را بااستفاده از نام کلاس کاملاً واجدشرایط مؤلفه هدف، ازجمله نام بسته برنامه، مشخص کنید، برای مثال، com.example.ExampleActivity. می‌توانید نام مؤلفه را با setComponent()،‏ setClass()،‏ setClassName()، یا با سازنده Intent تنظیم کنید.

کنش
رشته‌ای که کنش عمومی را برای اجرا مشخص می‌کند (مانند مشاهده یا انتخاب).

درمورد هدف همه‌فرستی، این کنشی است که انجام شده و گزارش می‌شود. کنش تا حد زیادی تعیین می‌کند که بقیه هدف چگونه ساختاردهی شود—به‌ویژه اطلاعاتی که در داده‌ها و موارد اضافی وجود دارد.

می‌توانید کنش‌های خودتان را برای استفاده توسط هدف‌ها در برنامه‌تان (یا برای استفاده توسط برنامه‌های دیگر برای فراخوانی مؤلفه‌ها در برنامه‌تان) مشخص کنید، اما معمولاً ثابت‌های کنش تعریف‌شده توسط کلاس Intent یا کلاس‌های چارچوب دیگر را مشخص می‌کنید. در اینجا چند کنش رایج برای شروع فعالیت آورده شده است:

ACTION_VIEW
از این کنش در قصد با startActivity() زمانی استفاده کنید که اطلاعاتی دارید که فعالیت می‌تواند به کاربر نشان دهد، مثل عکسی برای مشاهده در برنامه گالری، یا نشانی‌ای برای مشاهده در برنامه نقشه.
ACTION_SEND
این هدف که با نام هدف هم‌رسانی نیز شناخته می‌شود، باید در هدفی با startActivity() استفاده شود، زمانی که داده‌هایی دارید که کاربر می‌تواند ازطریق برنامه دیگری، مانند برنامه ایمیل یا برنامه هم‌رسانی اجتماعی، هم‌رسانی کند.

برای ثابت‌های بیشتری که کنش‌های عمومی را تعریف می‌کنند، به مرجع کلاس Intent مراجعه کنید. کنش‌های دیگر در جای دیگری از چارچوب Android تعریف شده‌اند، مثلاً در Settings برای کنش‌هایی که صفحه‌های خاصی را در برنامه «تنظیمات» سیستم باز می‌کنند.

می‌توانید کنش مربوط به قصد را با setAction() یا با سازنده Intent مشخص کنید.

اگر کنش‌های خودتان را تعریف می‌کنید، حتماً نام بسته برنامه‌تان را به‌عنوان پیشوند اضافه کنید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

const val ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL"

جاوا

static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";
داده
شناسه منبع یکسان (شیء Uri) که به داده‌ای که باید روی آن اقدام شود و/یا نوع MIME آن داده ارجاع می‌دهد. نوع داده‌های ارائه‌شده معمولاً توسط کنش قصد تعیین می‌شود. برای مثال، اگر کنش ACTION_EDIT باشد، داده‌ها باید حاوی URI سند برای ویرایش باشد.

هنگام ایجاد قصد، اغلب مهم است که علاوه‌بر URI، نوع داده (نوع MIME آن) را نیز مشخص کنید. برای مثال، فعالیتی که می‌تواند تصاویر را نمایش دهد احتمالاً نمی‌تواند فایل صوتی پخش کند، هرچند قالب‌های URI ممکن است مشابه باشند. مشخص کردن نوع MIME داده‌هایتان به سیستم Android کمک می‌کند بهترین مؤلفه را برای دریافت هدف شما پیدا کند. بااین‌حال، گاهی‌اوقات نوع MIME را می‌توان از URI استنباط کرد—به‌ویژه زمانی که داده‌ها از نوع content: URI باشند. ‫content: URI نشان می‌دهد که داده‌ها در دستگاه قرار دارند و توسط یک ContentProvider کنترل می‌شوند، که باعث می‌شود نوع MIME داده‌ها برای سیستم قابل‌مشاهده باشد.

برای تنظیم کردن فقط شناسه منبع یکسان داده، setData() را فراخوانی کنید. برای تنظیم فقط نوع MIME، با setType() تماس بگیرید. درصورت لزوم، می‌توانید هر دو را به‌طور صریح با setDataAndType() تنظیم کنید.

احتیاط: اگر می‌خواهید هم URI و هم نوع MIME را تنظیم کنید، setData() و setType() را فراخوانی نکنید چون هرکدام مقدار دیگری را باطل می‌کند. همیشه از setDataAndType() برای تنظیم هر دو نوع MIME و URI استفاده کنید.

دسته
رشته‌ای حاوی اطلاعات بیشتر درباره نوع مؤلفه‌ای که باید هدف را مدیریت کند. هر تعداد شرح دسته را می‌توان در یک هدف قرار داد، اما اکثر اهداف به دسته نیاز ندارند. در اینجا چند دسته رایج آورده شده است:
CATEGORY_BROWSABLE
فعالیت هدف به مرورگر وب اجازه می‌دهد خودش را برای نمایش داده‌های ارجاع‌شده توسط پیوند، مثل تصویر یا پیام ایمیل، راه‌اندازی کند.
CATEGORY_LAUNCHER
فعالیت، فعالیت اولیه یک تکلیف است و در راه‌انداز برنامه سیستم فهرست شده است.

برای مشاهده فهرست کامل دسته‌ها، شرح کلاس Intent را ببینید.

با addCategory() می‌توانید دسته را مشخص کنید.

این ویژگی‌های فهرست‌شده در بالا (نام مؤلفه، کنش، داده، و دسته) نشان‌دهنده مشخصه‌های تعریف‌کننده یک قصد هستند. با خواندن این ویژگی‌ها، سیستم Android می‌تواند تشخیص دهد که کدام مؤلفه برنامه را باید راه‌اندازی کند. بااین‌حال، قصد می‌تواند حاوی اطلاعات اضافی باشد که بر نحوه حل آن برای مؤلفه برنامه تأثیری ندارد. یک intent همچنین می‌تواند اطلاعات زیر را ارائه دهد:

موارد اضافی
جفت‌های کلید-مقدار که اطلاعات اضافی موردنیاز برای انجام کنش درخواست‌شده را حمل می‌کنند. همان‌طور که برخی‌از کنش‌ها از انواع خاصی از شناسه منبع یکسان داده استفاده می‌کنند، برخی‌از کنش‌ها نیز از موارد اضافی خاصی استفاده می‌کنند.

می‌توانید بااستفاده از روش‌های مختلف putExtra() داده‌های اضافی اضافه کنید، هرکدام دو پارامتر را می‌پذیرند: نام کلید و مقدار. همچنین می‌توانید شیء Bundle را با همه داده‌های اضافی ایجاد کنید، سپس Bundle را با putExtras() در Intent درج کنید.

برای مثال، هنگام ایجاد قصدی برای ارسال ایمیل با ACTION_SEND، می‌توانید گیرنده به را با کلید EXTRA_EMAIL و موضوع را با کلید EXTRA_SUBJECT مشخص کنید.

کلاس Intent EXTRA_* ثابت‌های زیادی را برای انواع داده‌های استانداردشده مشخص می‌کند. اگر لازم است کلیدهای اضافی خودتان را اعلام کنید (برای هدف‌هایی که برنامه‌تان دریافت می‌کند)، حتماً نام بسته برنامه‌تان را به‌عنوان پیشوند اضافه کنید، همان‌طور که در مثال زیر نشان داده شده است:

کاتلین

const val EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS"

جاوا

static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";

احتیاط: هنگام ارسال قصدی که انتظار دارید برنامه دیگری دریافت کند، از داده‌های Parcelable یا Serializable استفاده نکنید. اگر برنامه‌ای تلاش کند به داده‌های موجود در شیء Bundle دسترسی پیدا کند اما به کلاس بسته‌بندی‌شده یا سریالی‌شده دسترسی نداشته باشد، سیستم RuntimeException را مطرح می‌کند.

پرچم‌ها
پرچم‌ها در کلاس Intent تعریف می‌شوند که به‌عنوان فراداده برای هدف عمل می‌کنند. پرچم‌ها ممکن است به سیستم Android دستور دهند که چگونه فعالیتی را راه‌اندازی کند (برای مثال، فعالیت باید به کدام تکلیف تعلق داشته باشد) و پس‌از راه‌اندازی چگونه با آن رفتار کند (برای مثال، آیا به فهرست فعالیت‌های اخیر تعلق دارد یا نه).

برای اطلاعات بیشتر، روش setFlags() را ببینید.

نمونه قصد آشکار

قصد صریح قصدی است که برای راه‌اندازی مؤلفه خاصی از برنامه استفاده می‌کنید، مثلاً فعالیت یا سرویس خاصی در برنامه‌تان. برای ایجاد قصد صریح، نام مؤلفه را برای شیء Intent تعریف کنید—همه ویژگی‌های دیگر قصد اختیاری هستند.

برای مثال، اگر سرویسی در برنامه‌تان ساخته‌اید که نام آن DownloadService است و برای بارگیری فایل از وب طراحی شده است، می‌توانید آن را با کد زیر شروع کنید:

کاتلین

// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
val downloadIntent = Intent(this, DownloadService::class.java).apply {
    data = Uri.parse(fileUrl)
}
startService(downloadIntent)

جاوا

// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);

سازنده Intent(Context, Class) برنامه Context و مؤلفه شیء Class را ارائه می‌دهد. به همین دلیل، این هدف به‌طور صریح کلاس DownloadService را در برنامه شروع می‌کند.

برای اطلاعات بیشتر درباره ساختن و شروع کردن سرویس، راهنمای سرویس‌ها را ببینید.

مثال قصد ضمنی

هدف ضمنی کنشی را مشخص می‌کند که می‌تواند هر برنامه‌ای را در دستگاه که قادر به انجام آن کنش است فراخوانی کند. استفاده از هدف ضمنی زمانی مفید است که برنامه شما نمی‌تواند کنش را انجام دهد، اما برنامه‌های دیگر احتمالاً می‌توانند و شما می‌خواهید کاربر انتخاب کند از کدام برنامه استفاده شود.

برای مثال، اگر محتوایی دارید که می‌خواهید کاربر با افراد دیگر هم‌رسانی کند، قصدی با کنش ACTION_SEND ایجاد کنید و موارد اضافی را که محتوای هم‌رسانی را مشخص می‌کند اضافه کنید. وقتی startActivity() را با آن هدف فراخوانی می‌کنید، کاربر می‌تواند برنامه‌ای را برای هم‌رسانی محتوا انتخاب کند.

کاتلین

// Create the text message with a string.
val sendIntent = Intent().apply {
    action = Intent.ACTION_SEND
    putExtra(Intent.EXTRA_TEXT, textMessage)
    type = "text/plain"
}

// Try to invoke the intent.
try {
    startActivity(sendIntent)
} catch (e: ActivityNotFoundException) {
    // Define what your app should do if no activity can handle the intent.
}

جاوا

// Create the text message with a string.
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");

// Try to invoke the intent.
try {
    startActivity(sendIntent);
} catch (ActivityNotFoundException e) {
    // Define what your app should do if no activity can handle the intent.
}

وقتی startActivity() فراخوانده می‌شود، سیستم همه برنامه‌های نصب‌شده را بررسی می‌کند تا مشخص کند کدام‌یک می‌توانند این نوع هدف را مدیریت کنند (هدفی با کنش ACTION_SEND که داده‌های «text/plain» را حمل می‌کند). اگر فقط یک برنامه وجود داشته باشد که بتواند آن را مدیریت کند، آن برنامه بلافاصله باز می‌شود و هدف به آن داده می‌شود. اگر هیچ برنامه دیگری نتواند آن را مدیریت کند، برنامه شما می‌تواند ActivityNotFoundException را که رخ می‌دهد دریافت کند. اگر چند فعالیت قصد را بپذیرند، سیستم کادر گفتگویی مانند آنچه در شکل ۲ نشان داده شده است نمایش می‌دهد تا کاربر بتواند انتخاب کند از کدام برنامه استفاده کند.

اطلاعات بیشتر درباره راه‌اندازی برنامه‌های دیگر نیز در راهنمای ارسال کاربر به برنامه دیگر ارائه شده است.

شکل ۲. کادر گفتگوی انتخاب‌گر.

اجبار انتخابگر برنامه

وقتی بیش‌از یک برنامه به هدف ضمنی شما پاسخ می‌دهد، کاربر می‌تواند انتخاب کند که از کدام برنامه استفاده کند و آن برنامه را به‌عنوان انتخاب پیش‌فرض برای آن کنش تنظیم کند. امکان انتخاب پیش‌فرض زمانی مفید است که کنشی انجام می‌شود که کاربر احتمالاً می‌خواهد هربار از همان برنامه استفاده کند، مثلاً هنگام باز کردن صفحه وب (کاربران اغلب فقط یک مرورگر وب را ترجیح می‌دهند).

بااین‌حال، اگر چند برنامه بتوانند به هدف پاسخ دهند و کاربر بخواهد هربار از برنامه متفاوتی استفاده کند، باید صریحاً کادر گفتگوی انتخابگر را نشان دهید. کادر گفتگوی انتخابگر از کاربر می‌خواهد انتخاب کند از کدام برنامه برای این کنش استفاده شود (کاربر نمی‌تواند برنامه پیش‌فرضی برای این کنش انتخاب کند). برای مثال، وقتی برنامه‌تان «هم‌رسانی» را با کنش ACTION_SEND انجام می‌دهد، ممکن است کاربران بخواهند بسته به وضعیت فعلی‌شان بااستفاده از برنامه دیگری هم‌رسانی کنند، بنابراین همیشه باید از کادر گفتگوی انتخاب‌کننده استفاده کنید، همان‌طور که در شکل ۲ نشان داده شده است.

برای نمایش انتخاب‌گر، بااستفاده از createChooser() یک Intent ایجاد کنید و آن را به startActivity() ارسال کنید، همان‌طور که در مثال زیر نشان داده شده است. این مثال کادر گفتگویی را با فهرستی از برنامه‌هایی که به هدف ارسال‌شده به روش createChooser() پاسخ می‌دهند نمایش می‌دهد و از نوشتار ارائه‌شده به‌عنوان عنوان کادر گفتگو استفاده می‌کند.

کاتلین

val sendIntent = Intent(Intent.ACTION_SEND)
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
val title: String = resources.getString(R.string.chooser_title)
// Create intent to show the chooser dialog
val chooser: Intent = Intent.createChooser(sendIntent, title)

// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(packageManager) != null) {
    startActivity(chooser)
}

جاوا

Intent sendIntent = new Intent(Intent.ACTION_SEND);
...

// Always use string resources for UI text.
// This says something like "Share this photo with"
String title = getResources().getString(R.string.chooser_title);
// Create intent to show the chooser dialog
Intent chooser = Intent.createChooser(sendIntent, title);

// Verify the original intent will resolve to at least one activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
    startActivity(chooser);
}

شناسایی کردن راه‌اندازی‌های قصد ناامن

برنامه شما ممکن است برای پیمایش بین مؤلفه‌های داخل برنامه شما، یا برای انجام کنشی ازطرف برنامه دیگری، هدف‌هایی را راه‌اندازی کند. برای بهبود امنیت پلاتفرم، Android 12 (سطح میانای برنامه‌سازی کاربردی ۳۱) و بالاتر ویژگی اشکال‌زدایی‌ای ارائه می‌دهد که اگر برنامه شما راه‌اندازی ناامنی از یک هدف انجام دهد، به شما هشدار می‌دهد. برای مثال، ممکن است برنامه‌تان راه‌اندازی ناامنی از هدف تودرتو انجام دهد که هدفی است که به‌عنوان یک مورد اضافی در هدف دیگری ارسال می‌شود.

اگر برنامه شما هر دو کنش زیر را انجام دهد، سیستم راه‌اندازی قصد ناامن را شناسایی می‌کند و نقض StrictMode رخ می‌دهد:

  1. برنامه شما یک هدف تودرتو را از موارد اضافی یک هدف ارائه‌شده باز می‌کند.
  2. برنامه شما بلافاصله بااستفاده از آن هدف تودرتو، جزء برنامه را شروع می‌کند، مثلاً با ارسال هدف به startActivity()، startService()، یا bindService().

برای جزئیات بیشتر درباره نحوه شناسایی این وضعیت و ایجاد تغییرات در برنامه‌تان، پست وبلاگ درباره قصدهای تودرتو در Android را در Medium بخوانید.

بررسی راه‌اندازی‌های هدف ناامن

برای بررسی راه‌اندازی‌های هدف ناامن در برنامه‌تان، detectUnsafeIntentLaunch() را هنگام پیکربندی VmPolicy فراخوانی کنید، همان‌طور که در تکه‌کد زیر نشان داده شده است. اگر برنامه‌تان نقض StrictMode را شناسایی کند، شاید بخواهید اجرای برنامه را متوقف کنید تا از اطلاعات حساس احتمالی محافظت کنید.

کاتلین

fun onCreate() {
    StrictMode.setVmPolicy(VmPolicy.Builder()
        // Other StrictMode checks that you've previously added.
        // ...
        .detectUnsafeIntentLaunch()
        .penaltyLog()
        // Consider also adding penaltyDeath()
        .build())
}

جاوا

protected void onCreate() {
    StrictMode.setVmPolicy(new VmPolicy.Builder()
        // Other StrictMode checks that you've previously added.
        // ...
        .detectUnsafeIntentLaunch()
        .penaltyLog()
        // Consider also adding penaltyDeath()
        .build());
}

استفاده مسئولانه‌تر از قصدها

برای به‌حداقل رساندن احتمال راه‌اندازی قصد ناامن و نقض StrictMode، این روال‌های مطلوب را دنبال کنید.

فقط موارد اضافی ضروری را در هدف‌ها کپی کنید و هرگونه پاک‌سازی و اعتبارسنجی لازم را انجام دهید. ممکن است برنامه‌تان موارد اضافی را از یک هدف به هدف دیگری که برای راه‌اندازی مؤلفه جدید استفاده می‌شود کپی کند. این اتفاق زمانی می‌افتد که برنامه شما putExtras(Intent) یا putExtras(Bundle) را فراخوانی می‌کند. اگر برنامه‌تان یکی از این عملیات‌ها را انجام می‌دهد، فقط موارد اضافه‌ای را کپی کنید که مؤلفه دریافت‌کننده انتظار دارد. اگر هدف دیگر (که کپی را دریافت می‌کند) مؤلفه‌ای را راه‌اندازی می‌کند که صادر نشده است، موارد اضافی را قبل‌از کپی کردن آن‌ها در هدفی که مؤلفه را راه‌اندازی می‌کند، پاک‌سازی و اعتبارسنجی کنید.

مؤلفه‌های برنامه‌تان را بی‌دلیل صادر نکنید. برای مثال، اگر قصد دارید مؤلفه برنامه‌ای را بااستفاده از هدف تودرتوی داخلی راه‌اندازی کنید، مشخصه android:exported آن مؤلفه را روی false تنظیم کنید.

به‌جای قصد تودرتو، از PendingIntent استفاده کنید. به این ترتیب، وقتی برنامه دیگری PendingIntent را از Intent حاوی آن باز می‌کند، برنامه دیگر می‌تواند PendingIntent را بااستفاده از هویت برنامه شما راه‌اندازی کند. این پیکربندی به برنامه دیگر اجازه می‌دهد تا هر مؤلفه‌ای را، ازجمله مؤلفه صادرنشده، به‌طور ایمن در برنامه شما راه‌اندازی کند.

نمودار شکل ۲ نشان می‌دهد که سیستم چگونه کنترل را از برنامه شما (کارخواه) به برنامه دیگری (سرویس) منتقل می‌کند و دوباره به برنامه شما برمی‌گرداند:

  1. برنامه شما قصدی ایجاد می‌کند که فعالیتی را در برنامه دیگری فراخوانی می‌کند. در آن قصد، شیء PendingIntent را به‌عنوان یک مورد اضافی اضافه می‌کنید. این قصد معوق مؤلفه‌ای را در برنامه‌تان فراخوانی می‌کند؛ این مؤلفه صادر نشده است.
  2. پس‌از دریافت قصد برنامه شما، برنامه دیگر شیء تودرتوی PendingIntent را استخراج می‌کند.
  3. برنامه دیگر روش send() را در شیء PendingIntent فراخوانی می‌کند.
  4. پس‌از بازگرداندن کنترل به برنامه شما، سیستم قصد معوق را بااستفاده از بافت برنامه شما فراخوانی می‌کند.

شکل ۲. نمودار ارتباط بین‌برنامه‌ای هنگام استفاده از قصد معوق تودرتو.

دریافت قصد ضمنی

برای تبلیغ اینکه برنامه‌تان کدام هدف‌های ضمنی را می‌تواند دریافت کند، یک یا چند فیلتر هدف برای هرکدام از مؤلفه‌های برنامه‌تان با عنصر <intent-filter> در فایل مانیفست خود اعلام کنید. هر فیلتر قصد نوع قصدهایی را که می‌پذیرد براساس کنش، داده، و دسته قصد مشخص می‌کند. سیستم فقط درصورتی قصد ضمنی را به مؤلفه برنامه شما ارسال می‌کند که قصد بتواند از یکی از فیلترهای قصد شما عبور کند.

توجه: قصد صریح همیشه به هدف خود تحویل داده می‌شود، صرف‌نظر از هر فیلتر قصدی که مؤلفه اعلام می‌کند.

یک مؤلفه برنامه باید فیلترهای جداگانه‌ای برای هر کار منحصربه‌فردی که می‌تواند انجام دهد اعلام کند. برای مثال، یک فعالیت در برنامه گالری تصویر ممکن است دو فیلتر داشته باشد: یک فیلتر برای مشاهده تصویر، و فیلتر دیگر برای ویرایش تصویر. وقتی فعالیت شروع می‌شود، Intent را بررسی می‌کند و براساس اطلاعات موجود در Intent تصمیم می‌گیرد که چگونه رفتار کند (مثلاً اینکه کنترل‌های ویرایشگر را نشان دهد یا نه).

هر فیلتر قصد با یک عنصر <intent-filter> در فایل مانیفست برنامه تعریف می‌شود که در مؤلفه برنامه مربوطه (مانند عنصر <activity>) تودرتو شده است.

در هر مؤلفه برنامه که شامل عنصر <intent-filter> است، به‌طور صریح مقداری برای android:exported تنظیم کنید. این مشخصه نشان می‌دهد که آیا مؤلفه برنامه برای برنامه‌های دیگر قابل‌دسترسی است یا نه. در برخی شرایط، مثلاً فعالیت‌هایی که فیلترهای قصد آن‌ها شامل دسته LAUNCHER است، مفید است که این مشخصه روی true تنظیم شود. درغیراین‌صورت، ایمن‌تر است که این مشخصه را روی false تنظیم کنید.

هشدار: اگر فعالیت، سرویس، یا گیرنده رویداد ثبت‌شده‌ای در برنامه‌تان از فیلترهای قصد استفاده می‌کند و به‌طور صریح مقدار android:exported را تنظیم نمی‌کند، برنامه‌تان نمی‌تواند در دستگاهی که Android نسخه ۱۲ یا بالاتر را اجرا می‌کند نصب شود.

درون <intent-filter>، می‌توانید نوع هدف‌های قابل‌پذیرش را بااستفاده از یک یا چند مورد از این سه عنصر مشخص کنید:

<action>
کنش قصد پذیرفته‌شده را در مشخصه name اعلام می‌کند. مقدار باید مقدار رشته تحت‌اللفظی یک کنش باشد، نه ثابت کلاس.
<data>
نوع داده پذیرفته‌شده را بااستفاده از یک یا چند مشخصه که جنبه‌های مختلف شناسه منبع یکسان داده (scheme، host، port، path) و نوع MIME را مشخص می‌کند اعلام می‌کند.
<category>
دسته هدف پذیرفته‌شده را در مشخصه name اعلام می‌کند. مقدار باید مقدار رشته تحت‌اللفظی یک کنش باشد، نه ثابت کلاس.

توجه: برای دریافت هدف‌های ضمنی، باید دسته CATEGORY_DEFAULT را در فیلتر هدف بگنجانید. روش‌های startActivity() و startActivityForResult() با همه قصدها به‌گونه‌ای رفتار می‌کنند که گویی دسته CATEGORY_DEFAULT را اعلام کرده‌اند. اگر این دسته را در فیلتر قصد خود اعلام نکنید، هیچ قصد ضمنی‌ای به فعالیت شما منتهی نخواهد شد.

برای مثال، در اینجا یک اعلان فعالیت با فیلتر قصد برای دریافت ACTION_SEND قصد وقتی نوع داده نوشتار است وجود دارد:

<activity android:name="ShareActivity" android:exported="false">
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
</activity>

می‌توانید فیلتری ایجاد کنید که شامل بیش‌از یک نمونه از <action>، <data>، یا <category> باشد. اگر این کار را انجام دهید، باید مطمئن شوید که مؤلفه می‌تواند هرگونه ترکیب از آن عناصر فیلتر را مدیریت کند.

وقتی می‌خواهید انواع مختلفی از قصدها را مدیریت کنید، اما فقط در ترکیب‌های خاصی از کنش، داده، و نوع دسته، باید چندین فیلتر قصد ایجاد کنید.

قصد ضمنی با مقایسه قصد با هریک از سه عنصر، دربرابر فیلتر آزمایش می‌شود. برای اینکه قصد به مؤلفه ارسال شود، باید هر سه آزمایش را با موفقیت پشت سر بگذارد. اگر حتی با یکی‌از آن‌ها مطابقت نداشته باشد، سیستم Android قصد را به مؤلفه ارائه نمی‌دهد. بااین‌حال، چون یک مؤلفه ممکن است چندین فیلتر قصد داشته باشد، قصدی که از یکی از فیلترهای مؤلفه عبور نمی‌کند ممکن است از فیلتر دیگری عبور کند. اطلاعات بیشتر درباره نحوه حل‌وفصل کردن قصدها توسط سیستم در بخش زیر درباره حل‌وفصل کردن قصد ارائه شده است.

احتیاط: استفاده از فیلتر قصد روشی امن برای جلوگیری از راه‌اندازی مؤلفه‌های شما توسط برنامه‌های دیگر نیست. اگرچه فیلترهای قصد مؤلفه را محدود می‌کنند تا فقط به انواع خاصی از قصد ضمنی پاسخ دهد، اگر توسعه‌دهنده نام مؤلفه‌های شما را تعیین کند، برنامه دیگری می‌تواند به‌طور بالقوه مؤلفه برنامه شما را بااستفاده از قصد صریح راه‌اندازی کند. اگر مهم است که فقط برنامه خودتان بتواند یکی از مؤلفه‌هایتان را شروع کند، فیلترهای قصد را در مانیفست خود اعلام نکنید. درعوض، مشخصه exported را برای آن مؤلفه روی "false" تنظیم کنید.

به‌همین ترتیب، برای جلوگیری از اجرای ناخواسته Service برنامه دیگر، همیشه از هدف صریح برای شروع سرویس خودتان استفاده کنید.

توجه: برای همه فعالیت‌ها، باید فیلترهای قصد را در فایل مانیفست اعلام کنید. بااین‌حال، فیلترهای گیرنده‌های رویداد ثبت‌شده را می‌توان با فراخوانی registerReceiver() به‌صورت پویا ثبت کرد. سپس می‌توانید با unregisterReceiver() ثبت گیرنده را لغو کنید. با این کار، برنامه‌تان می‌تواند فقط درطول دوره زمانی مشخصی که برنامه‌تان درحال اجرا است، به همه‌فرستی‌های خاصی گوش دهد.

فیلترهای نمونه

برای نشان دادن برخی‌از رفتارهای فیلتر قصد، در اینجا مثالی از فایل مانیفست برنامه هم‌رسانی اجتماعی آورده شده است:

<activity android:name="MainActivity" android:exported="true">
    <!-- This activity is the main entry, should appear in app launcher -->
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name="ShareActivity" android:exported="false">
    <!-- This activity handles "SEND" actions with text data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="text/plain"/>
    </intent-filter>
    <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND"/>
        <action android:name="android.intent.action.SEND_MULTIPLE"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/vnd.google.panorama360+jpg"/>
        <data android:mimeType="image/*"/>
        <data android:mimeType="video/*"/>
    </intent-filter>
</activity>

اولین فعالیت، MainActivity، نقطه ورود اصلی برنامه است—فعالیتی که وقتی کاربر برای اولین‌بار برنامه را با نماد راه‌انداز راه‌اندازی می‌کند باز می‌شود:

  • کنش ACTION_MAIN نشان می‌دهد که این نقطه ورودی اصلی است و انتظار هیچ داده قصدی را ندارد.
  • دسته CATEGORY_LAUNCHER نشان می‌دهد که نماد این فعالیت باید در راه‌انداز برنامه سیستم قرار گیرد. اگر عنصر <activity> نمادی با icon مشخص نکند، سیستم از نماد عنصر <application> استفاده می‌کند.

این دو باید باهم جفت شوند تا فعالیت در راه‌انداز برنامه نشان داده شود.

فعالیت دوم، ShareActivity، برای تسهیل هم‌رسانی نوشتار و محتوای رسانه‌ای درنظر گرفته شده است. اگرچه کاربران ممکن است با پیمایش به این فعالیت از MainActivity وارد آن شوند، می‌توانند مستقیماً از برنامه دیگری که قصد ضمنی منطبق با یکی از دو فیلتر قصد را صادر می‌کند، وارد ShareActivity شوند.

توجه: نوع MIME، application/vnd.google.panorama360+jpg، نوع داده خاصی است که عکس‌های تمام‌نما را مشخص می‌کند، که می‌توانید با میاناهای برنامه‌سازی کاربردی تمام‌نمای Google آن‌ها را مدیریت کنید.

مطابقت دادن قصدها با فیلترهای قصد برنامه‌های دیگر

اگر برنامه دیگری Android 13 (سطح میانای برنامه‌سازی کاربردی ۳۳) یا بالاتر را هدف قرار دهد، تنها درصورتی می‌تواند هدف برنامه شما را مدیریت کند که هدف شما با کنش‌ها و دسته‌های عنصر <intent-filter> در آن برنامه دیگر مطابقت داشته باشد. اگر سیستم مطابقت پیدا نکند، ActivityNotFoundException را برمی‌گرداند. برنامه ارسال‌کننده باید این استثنا را مدیریت کند.

به‌طور مشابه، اگر برنامه‌تان را به‌روز کنید تا Android 13 یا بالاتر را هدف قرار دهد، همه هدف‌های ناشی از برنامه‌های خارجی فقط درصورتی به مؤلفه صادرشده برنامه‌تان تحویل داده می‌شوند که آن هدف با کنش‌ها و دسته‌های عنصر <intent-filter> که برنامه‌تان اعلام می‌کند مطابقت داشته باشد. این رفتار صرف‌نظر از نسخه کیت توسعه نرم‌افزار هدف برنامه ارسال‌کننده رخ می‌دهد.

در موارد زیر، مطابقت هدف اعمال نمی‌شود:

  • قصدها به مؤلفه‌هایی ارسال می‌شوند که هیچ فیلتر قصدی را اعلام نمی‌کنند.
  • هدف‌هایی که از داخل همان برنامه منشأ می‌گیرند.
  • هدف‌هایی که از سیستم منشأ می‌گیرند؛ یعنی هدف‌هایی که از «شناسه کاربری سیستم» (uid=1000) ارسال می‌شوند. برنامه‌های سیستم شامل system_server و برنامه‌هایی است که android:sharedUserId را روی android.uid.system تنظیم می‌کنند.
  • قصدها از ریشه منشأ می‌گیرند.

درباره مطابقت قصد بیشتر بدانید.

استفاده از قصد معوق

شیء PendingIntent پوششی برای شیء Intent است. هدف اصلی PendingIntent اعطای اجازه به برنامه خارجی برای استفاده از Intent موجود است، به‌گونه‌ای که انگار از فرایند برنامه خودتان اجرا شده است.

موارد استفاده اصلی برای قصد معوق شامل موارد زیر است:

  • اعلام قصدی که باید اجرا شود وقتی کاربر کنشی را با اعلان شما انجام می‌دهد (NotificationManager سیستم Android Intent را اجرا می‌کند).
  • اعلام قصدی که باید اجرا شود وقتی کاربر کنشی را با ابزاره برنامه شما انجام می‌دهد (برنامه صفحه اصلی Intent را اجرا می‌کند).
  • اعلام قصدی که باید در زمان مشخصی در آینده اجرا شود (AlarmManager سیستم Android،‏ Intent را اجرا می‌کند).

همان‌طور که هر هدف Intent برای مدیریت شدن توسط نوع خاصی از مؤلفه برنامه (Activity، Service، یا BroadcastReceiver) طراحی شده است، PendingIntent نیز باید با همین ملاحظات ایجاد شود. هنگام استفاده از قصد معوق، برنامه‌تان قصد را با تماسی مثل startActivity() اجرا نمی‌کند. درعوض، باید نوع مؤلفه موردنظر را هنگام ایجاد PendingIntent با فراخوانی روش ایجادکننده مربوطه اعلام کنید:

مگر اینکه برنامه‌تان قصد معوق را از برنامه‌های دیگر دریافت کند، روش‌های بالا برای ایجاد PendingIntent احتمالاً تنها PendingIntent روش‌هایی هستند که به آن‌ها نیاز خواهید داشت.

هر روش Context برنامه فعلی، Intent که می‌خواهید بسته‌بندی کنید، و یک یا چند پرچم را که مشخص می‌کنند چگونه باید از هدف استفاده شود (مثلاً اینکه آیا می‌توان از هدف بیش‌از یک بار استفاده کرد یا نه) دریافت می‌کند.

برای اطلاعات بیشتر درباره استفاده از قصد معوق، به اسناد مربوط به هریک از موارد استفاده مربوطه، مانند راهنماهای API اعلان‌ها و ابزاره‌های برنامه مراجعه کنید.

تغییرپذیری را مشخص کنید

اگر برنامه شما Android نسخه ۱۲ یا بالاتر را هدف قرار می‌دهد، باید تغییرپذیری هر شیء PendingIntent را که برنامه‌تان ایجاد می‌کند مشخص کنید. برای اعلام اینکه یک شیء PendingIntent داده‌شده تغییرپذیر یا تغییرناپذیر است، به‌ترتیب از پرچم PendingIntent.FLAG_MUTABLE یا PendingIntent.FLAG_IMMUTABLE استفاده کنید.

اگر برنامه‌تان تلاش کند شیء PendingIntent را بدون تنظیم کردن پرچم تغییرپذیری ایجاد کند، سیستم IllegalArgumentException را صادر می‌کند و پیام زیر در Logcat نشان داده می‌شود:

PACKAGE_NAME: Targeting S+ (version 31 and above) requires that one of \
FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.

Strongly consider using FLAG_IMMUTABLE, only use FLAG_MUTABLE if \
some functionality depends on the PendingIntent being mutable, e.g. if \
it needs to be used with inline replies or bubbles.

هرزمان ممکن بود، قصد معوق تغییرناپذیر ایجاد کنید

در اکثر موارد، برنامه‌تان باید اشیای PendingIntent تغییرناپذیر ایجاد کند، همان‌طور که در تکه‌کد زیر نشان داده شده است. اگر شیء PendingIntent تغییرناپذیر باشد، برنامه‌های دیگر نمی‌توانند هدف را برای اصلاح نتیجه فراخوانی هدف تغییر دهند.

کاتلین

val pendingIntent = PendingIntent.getActivity(applicationContext,
        REQUEST_CODE, intent,
        /* flags */ PendingIntent.FLAG_IMMUTABLE)

جاوا

PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),
        REQUEST_CODE, intent,
        /* flags */ PendingIntent.FLAG_IMMUTABLE);

بااین‌حال، برخی‌از موارد استفاده به‌جای آن به اشیای PendingIntent تغییرپذیر نیاز دارند:

  • پشتیبانی از کنش‌های پاسخ مستقیم در اعلان‌ها. پاسخ مستقیم مستلزم تغییر در داده‌های کلیپ در شیء PendingIntent است که با پاسخ مرتبط است. معمولاً این تغییر را با ارسال FILL_IN_CLIP_DATA به‌عنوان پرچم به روش fillIn() درخواست می‌کنید.
  • مرتبط کردن اعلان‌ها با چارچوب Android Auto، بااستفاده از نمونه‌های CarAppExtender.
  • قرار دادن مکالمه‌ها در حبابک‌ها بااستفاده از نمونه‌های PendingIntent. شیء PendingIntent تغییرپذیر به سیستم اجازه می‌دهد پرچم‌های صحیح را اعمال کند، مثلاً FLAG_ACTIVITY_MULTIPLE_TASK و FLAG_ACTIVITY_NEW_DOCUMENT.
  • درخواست اطلاعات مکان دستگاه با فراخوانی requestLocationUpdates() یا میاناهای برنامه‌سازی کاربردی مشابه. شیء تغییرپذیر PendingIntent به سیستم اجازه می‌دهد موارد اضافی هدف را که نشان‌دهنده رویدادهای چرخه حیات مکان است اضافه کند. این رویدادها شامل تغییر مکان و دردسترس قرار گرفتن ارائه‌دهنده است.
  • درحال زمان‌بندی زنگ ساعت بااستفاده از AlarmManager. شیء تغییرپذیر PendingIntent به سیستم اجازه می‌دهد EXTRA_ALARM_COUNT intent extra را اضافه کند. این مقدار اضافی نشان‌دهنده تعداد دفعاتی است که زنگ تکرارشونده به‌صدا درآمده است. با گنجاندن این مورد اضافی، هدف می‌تواند به‌طور دقیق به برنامه اطلاع دهد که آیا هشدار تکرارشونده چندین بار فعال شده است یا نه، مثلاً زمانی که دستگاه در حالت خواب بوده است.

اگر برنامه‌تان شیء PendingIntent تغییرپذیر ایجاد می‌کند، اکیداً توصیه می‌شود که از هدف صریح استفاده کنید و ComponentName را تکمیل کنید. به این ترتیب، هرگاه برنامه دیگری PendingIntent را فراخوانی کند و کنترل را به برنامه شما برگرداند، همان مؤلفه در برنامه شما همیشه شروع می‌شود.

استفاده از قصد آشکار در قصد معوق

برای تعریف بهتر نحوه استفاده برنامه‌های دیگر از قصد معوق برنامه‌تان، همیشه قصد معوق را در قصد صریح قرار دهید. برای کمک به رعایت این روال مطلوب، کارهای زیر را انجام دهید:

  1. بررسی کنید که فیلدهای کنش، بسته، و مؤلفه قصد پایه تنظیم شده باشد.
  2. از FLAG_IMMUTABLE، که در Android 6.0 (سطح میانای برنامه‌سازی کاربردی ۲۳) اضافه شده است، برای ایجاد قصد معوق استفاده کنید. این پرچم مانع از این می‌شود که برنامه‌هایی که PendingIntent دریافت می‌کنند ویژگی‌های خالی را پر کنند. اگر minSdkVersion برنامه‌تان 22 یا پایین‌تر است، می‌توانید بااستفاده از کد زیر، ایمنی و سازگاری را باهم ارائه دهید:

    if (Build.VERSION.SDK_INT >= 23) {
      // Create a PendingIntent using FLAG_IMMUTABLE.
    } else {
      // Existing code that creates a PendingIntent.
    }

حل‌وفصل قصد

وقتی سیستم قصد ضمنی برای شروع فعالیتی را دریافت می‌کند، با مقایسه آن با فیلترهای قصد براساس سه جنبه، بهترین فعالیت را برای آن قصد جستجو می‌کند:

  • کنش.
  • داده (هم URI و هم نوع داده).
  • دسته.

بخش‌های زیر نحوه مطابقت دادن اهداف با مؤلفه‌های مناسب را براساس اعلام فیلتر هدف در فایل مانیفست برنامه شرح می‌دهد.

آزمایش کنش

برای مشخص کردن کنش‌های هدف پذیرفته‌شده، فیلتر هدف می‌تواند صفر یا چند عنصر <action> را اعلام کند، همان‌طور که در مثال زیر نشان داده شده است:

<intent-filter>
    <action android:name="android.intent.action.EDIT" />
    <action android:name="android.intent.action.VIEW" />
    ...
</intent-filter>

برای عبور از این فیلتر، کنش مشخص‌شده در Intent باید با یکی از کنش‌های فهرست‌شده در فیلتر مطابقت داشته باشد.

اگر فیلتر هیچ کنشی را فهرست نکند، چیزی برای مطابقت دادن قصد وجود ندارد، بنابراین همه قصدها در آزمایش ناموفق می‌شوند. اما اگر Intent کنشی را مشخص نکند، تا زمانی که فیلتر حداقل یک کنش داشته باشد، در آزمایش قبول می‌شود.

آزمایش دسته

برای مشخص کردن دسته‌های هدف پذیرفته‌شده، فیلتر هدف می‌تواند صفر یا چند عنصر <category> را اعلام کند، همان‌طور که در مثال زیر نشان داده شده است:

<intent-filter>
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    ...
</intent-filter>

برای اینکه هدف از آزمون دسته عبور کند، هر دسته در Intent باید با دسته‌ای در فیلتر مطابقت داشته باشد. برعکس آن لازم نیست—فیلتر قصد ممکن است دسته‌های بیشتری را نسبت‌به آنچه در Intent مشخص شده است اعلام کند و Intent همچنان عبور کند. بنابراین، قصدی که هیچ دسته‌ای ندارد همیشه این آزمایش را با موفقیت پشت سر می‌گذارد، صرف‌نظر از اینکه چه دسته‌هایی در فیلتر اعلام شده باشد.

توجه: Android به‌طور خودکار دسته CATEGORY_DEFAULT را به همه هدف‌های ضمنی ارسال‌شده به startActivity() و startActivityForResult() اعمال می‌کند. اگر می‌خواهید فعالیتتان قصد ضمنی دریافت کند، باید دسته‌ای برای "android.intent.category.DEFAULT" در فیلترهای قصد خود داشته باشد، همان‌طور که در مثال <intent-filter> قبلی نشان داده شده است.

آزمایش داده‌ها

برای مشخص کردن داده‌های قصد پذیرفته‌شده، فیلتر قصد می‌تواند صفر یا چند عنصر <data> را اعلام کند، همان‌طور که در مثال زیر نشان داده شده است:

<intent-filter>
    <data android:mimeType="video/mpeg" android:scheme="http" ... />
    <data android:mimeType="audio/mpeg" android:scheme="http" ... />
    ...
</intent-filter>

هر عنصر <data> می‌تواند ساختار URI و نوع داده (نوع رسانه MIME) را مشخص کند. هر بخش از URI یک مشخصه جداگانه است: scheme، host، port، و path:

<scheme>://<host>:<port>/<path>

مثال زیر مقادیر احتمالی این مشخصه‌ها را نشان می‌دهد:

content://com.example.project:200/folder/subfolder/etc

در این URI، طرح‌واره content، میزبان com.example.project، درگاه 200، و مسیر folder/subfolder/etc است.

هرکدام از این مشخصه‌ها در عنصر <data> اختیاری است، اما وابستگی‌های خطی وجود دارد:

  • اگر طرحی مشخص نشده باشد، میزبان نادیده گرفته می‌شود.
  • اگر میزبانی مشخص نشده باشد، درگاه نادیده گرفته می‌شود.
  • اگر طرح و میزبان هر دو مشخص نشده باشند، مسیر نادیده گرفته می‌شود.

وقتی URI در یک قصد با مشخصات URI در یک فیلتر مقایسه می‌شود، فقط با بخش‌هایی از URI که در فیلتر گنجانده شده‌اند مقایسه می‌شود. برای مثال:

  • اگر فیلتری فقط طرحی را مشخص کند، همه URIهای دارای آن طرح با فیلتر مطابقت دارند.
  • اگر فیلتری طرح و مرجع را مشخص کند اما مسیر را مشخص نکند، همه URIهای دارای طرح و مرجع یکسان، صرف‌نظر از مسیرشان، از فیلتر عبور می‌کنند.
  • اگر فیلتری طرح‌واره، مرجع، و مسیر را مشخص کند، فقط URIهایی که طرح‌واره، مرجع، و مسیر یکسانی داشته باشند از فیلتر عبور می‌کنند.

توجه: مشخصات مسیر می‌تواند حاوی نویسه عام ستاره (*) باشد تا فقط تطابق جزئی نام مسیر لازم باشد.

آزمایش داده هم URI و هم نوع MIME را در قصد با URI و نوع MIME مشخص‌شده در فیلتر مقایسه می‌کند. قوانین به‌صورت زیر است:

  1. قصدی که نه URI و نه نوع MIME دارد، فقط درصورتی از آزمون عبور می‌کند که فیلتر هیچ URI یا نوع MIME را مشخص نکند.
  2. قصدی که حاوی URI است اما نوع MIME ندارد (نه صریح و نه قابل‌استنباط از URI) فقط درصورتی آزمون را می‌گذراند که URI آن با قالب URI فیلتر مطابقت داشته باشد و فیلتر نیز نوع MIME را مشخص نکند.
  3. قصدی که حاوی نوع MIME است اما URI ندارد فقط درصورتی در آزمایش قبول می‌شود که فیلتر همان نوع MIME را فهرست کند و قالب URI را مشخص نکند.
  4. قصدی که هم URI و هم نوع MIME (چه صریح و چه قابل‌استنباط از URI) را دربرمی‌گیرد، تنها درصورتی بخش نوع MIME آزمایش را می‌گذراند که آن نوع با نوع فهرست‌شده در فیلتر مطابقت داشته باشد. اگر URI آن با URI موجود در فیلتر مطابقت داشته باشد یا اگر URI content: یا file: داشته باشد و فیلتر URI را مشخص نکند، بخش URI آزمایش را می‌گذراند. به‌عبارت دیگر، فرض می‌شود که مؤلفه از داده‌های content: و file: پشتیبانی می‌کند اگر فیلتر آن فقط نوع MIME را فهرست کند.

توجه: اگر قصدی نوع MIME یا URI را مشخص کند، درصورت نبودن عناصر <data> در <intent-filter>، آزمایش داده ناموفق خواهد بود.

این قانون آخر، قانون (د)، نشان‌دهنده این انتظار است که مؤلفه‌ها بتوانند داده‌های محلی را از ارائه‌دهنده فایل یا محتوا دریافت کنند. بنابراین، فیلترهای آن‌ها می‌تواند فقط نوع داده را فهرست کند و نیازی نیست که صریحاً طرح‌های content: و file: را نام ببرد. مثال زیر موردی معمول را نشان می‌دهد که در آن عنصر <data> به Android می‌گوید که مؤلفه می‌تواند داده‌های تصویر را از ارائه‌دهنده محتوا دریافت کند و آن را نمایش دهد:

<intent-filter>
    <data android:mimeType="image/*" />
    ...
</intent-filter>

فیلترهایی که نوع داده را مشخص می‌کنند اما URI را مشخص نمی‌کنند شاید رایج‌ترین باشند زیرا بیشتر داده‌های دردسترس توسط ارائه‌دهندگان محتوا توزیع می‌شوند.

پیکربندی رایج دیگر فیلتری با طرح‌واره و نوع داده است. برای مثال، عنصر <data> زیر به Android می‌گوید که بخش می‌تواند داده‌های ویدیو را از شبکه بازیابی کند تا کنش را انجام دهد:

<intent-filter>
    <data android:scheme="http" android:mimeType="video/*" />
    ...
</intent-filter>

تطبیق قصد

قصدها با فیلترهای قصد مطابقت داده می‌شوند تا نه‌تنها مؤلفه هدف برای فعال‌سازی پیدا شود، بلکه اطلاعاتی درباره مجموعه مؤلفه‌های موجود در دستگاه نیز به‌دست آید. برای مثال، برنامه Home با پیدا کردن همه فعالیت‌هایی که فیلترهای قصد دارند و کنش ACTION_MAIN و دسته CATEGORY_LAUNCHER را مشخص می‌کنند، راه‌انداز برنامه را پر می‌کند. مطابقت فقط درصورتی موفقیت‌آمیز است که کنش‌ها و دسته‌ها در Intent با فیلتر مطابقت داشته باشند، همان‌طور که در اسناد کلاس IntentFilter شرح داده شده است.

برنامه شما می‌تواند از تطبیق هدف به شیوه‌ای مشابه با آنچه برنامه Home انجام می‌دهد استفاده کند. PackageManager مجموعه‌ای از روش‌های query...() دارد که همه مؤلفه‌هایی را که می‌توانند قصد خاصی را بپذیرند برمی‌گرداند و مجموعه‌ای مشابه از روش‌های resolve...() که بهترین مؤلفه را برای پاسخ به یک قصد تعیین می‌کنند. برای مثال، queryIntentActivities() فهرستی از همه فعالیت‌هایی را که می‌توانند هدف ارسال‌شده به‌عنوان آرگومان را اجرا کنند برمی‌گرداند، و queryIntentServices() فهرست مشابهی از سرویس‌ها را برمی‌گرداند. هیچ‌کدام از این روش‌ها مؤلفه‌ها را فعال نمی‌کند؛ آن‌ها فقط مؤلفه‌هایی را که می‌توانند پاسخ دهند فهرست می‌کنند. روش مشابهی، queryBroadcastReceivers()، برای گیرنده‌های رویداد ثبت‌شده وجود دارد.