همه چیز درباره PendingIntent
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 9 دقیقه

همه چیز درباره PendingIntent

PendingIntent بخش مهمی از فریم‌ورک اندروید است، اما بیشتر منابع موجود برای توسعه دهنده‌ها به جای استفاده از آن‌ها رو جزئیات پیاده سازی آن‌ها تمرکز دارند.

اندروید 12 شامل تغییرات مهمی در مورد PendingIntentها است، از جمله این تغییر که pending intentها برای تغیییر پذیر بودن یا نبودن نیاز به تصمیم گیری دارند، من فکر می‌کنم خیلی مفید است که درباره کارهایی که PendingIntentها انجام می‌دهند، نحوه استفاده سیستم از آن‌ها و اینکه چرا گاهی اوقات یک PendingIntent قابل تغییر می‌خواهید صحبت کنیم.

PendingIntent چیست؟

یک شئ PendingIntent به برنامه شما این امکان را می‌دهد کاری را که برنامه دیگری باید انجام دهد از طرف برنامه شما انجام شود. برای مثال بسته شدن زنگ هشدار، یا هنگامی که کاربر روی اعلانی ضربه می‌‍زند، Intent آماده شده فراخوانی می‌شود.

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

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

بیایید نگاهی به روش‌هایی که برنامه ما می‌تواند با PendingIntent کار کند بیندازیم و اینکه چرا ممکن است بخواهیم از آن‌ها به این روش‌ها استفاده کنیم.

موارد مشترک

رایج‌ترین و ابتدایی‌ترین راه برای استفاده از PendingIntent یک کار مرتبط با اعلان است:

val intent = Intent(applicationContext, MainActivity::class.java).apply {
    action = NOTIFICATION_ACTION
    data = deepLink
}
val pendingIntent = PendingIntent.getActivity(
    applicationContext,
    NOTIFICATION_REQUEST_CODE,
    intent,
    PendingIntent.FLAG_IMMUTABLE
)
val notification = NotificationCompat.Builder(
        applicationContext,
        NOTIFICATION_CHANNEL
    ).apply {
        // ...
        setContentIntent(pendingIntent)
        // ...
    }.build()
notificationManager.notify(
    NOTIFICATION_TAG,
    NOTIFICATION_ID,
    notification
)

همانطور که می‌بینید در حال ساخت یک intent استاندارد برای باز کردن برنامه خود هستیم و سپس قبل از افزودن آن به اعلان خود، آن را به سادگی در یک PendingIntent قرار می‌دهیم.

در این حالت یک عملکرد داریم که می‌دانیم می‌خوایم انجام دهیم، بنابراین یک PendingIntent می‌سازیم که با پاس دادن پارامتر FLAG_IMMUTABLE برنامه نمی‌تواند آن را تغییر دهد.

بعد از اینکه ما NotificationManagerCompat.notify() را فراخوانی کردیم کارمان تمام است. سیستم اعلان را نمایش می‌دهد، و وقتی کاربر روی آن کلیک کرد، فراخوانی PendingIntent.send() روی PendingIntent ما، برنامه ما را شروع می‌کند.

بروزرسانی PendingIntent تغییر ناپذیر

ممکن است فکر کنید اگر برنامه‌ای نیاز به بروزرسانی PendingIntent دارد، باید تغییر پذیر باشد، اما این همیشه صدق نمی‌کند! برنامه‌ای که یک PendingIntent ایجاد می‌کند همیشه می‌تواند با استفاده از پرچم FLAG_UPDATE_CURRENT آن را بروز کند:

val updatedIntent = Intent(applicationContext, MainActivity::class.java).apply {
   action = NOTIFICATION_ACTION
   data = differentDeepLink
}
// Because we're passing `FLAG_UPDATE_CURRENT`, this updates
// the existing PendingIntent with the changes we made above.
val updatedPendingIntent = PendingIntent.getActivity(
   applicationContext,
   NOTIFICATION_REQUEST_CODE,
   updatedIntent,
   PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
// The PendingIntent has been updated.

ما همچنین در مورد اینکه چرا ممکن است شخصی PendingIntent را قابل تغییر کند صحبت خواهیم کرد.

APIهای بین برنامه‌ای

موارد مشترک فقط برای تعامل با سیستم مفید نیست. در حالی که معمولاً از startActivityForResult() و onActivityResult() برای دریافت پاسخ پس از انجام یک عمل انجام می‌شود، این‌ها تنها راه ممکن نیستند.

یک برنامه سفارش آنلاین را تصور کنید که با ارائه یک API به برنامه‌های دیگر اجازه می‌دهد با آن ادغام شوند. این ممکن است یک PendingIntent را به عنوان extra به intent برنامه خود که برای شروع سفارش غذا استفاده می‌شود، بپذیرد. برنامه سفارش غذا فقط پس از تحویل سفارش PendingIntent را شروع می‌کند.

در این حالت برنامه سفارش غذا به جای ارسال activity result از PendingIntent استفاده می‌کند، زیرا ممکن است زمان قابل توجهی برای تحویل سفارش طول بکشد و منطقی نیست که کاربر را مجبور به منتظر ماندن تا وقوع این کار کند.

ما یک PendingIntent تغییر ناپذیر ایجاد می‌کنیم زیرا نمی‌خواهیم برنامه سفارش آنلاین چیزی از Intent ما را تغییر دهد. ما فقط می‌خواهیم هنگامی که سفارش رسید، دقیقاً همانطوری که هست ارسال شود.

PendingIntentهای قابل تغییر

اما فرض کنید ما توسعه دهنده برنامه سفارش غذا بودیم و می‌خواستیم قابلیتی را اضافه کنیم که به کاربر امکان دهد پیامی را تایپ کند و در برنامه‌ای که آن را فراخوانی کرده است تایپ شود. مانند اینکه به برنامه اجازه دهید چیزی مانند "It’s PIZZA TIME" را نمایش دهد. در این صورت باید چه کاری بکنیم؟

پاسخ این سوال استفاده از PendingIntent قابل تغییر است.

از آن جا که PendingIntent اساساً یک بسته در اطراف intent است، ممکن است تصور شود که متدی به نام PendingIntent.getIntent() وجود دارد که می‌توان برای دریافت و بروزرسانی intent بسته بندی شده فراخوانی کرد، اما چنین چیزی درست نیست. بنابراین چگونه کار می‌کند؟

علاوه بر  متد send() در PendingIntent که هیچ پارامتری ندارد، چند نسخه دیگر نیز وجود دارد، از جمله این نسخه که یک Intent را می‌پذیرد:

fun PendingIntent.send(
    context: Context!, 
    code: Int, 
    intent: Intent?
)

این پارامتر intent که در ورودی قرار دارد جایگزین intent موجود در PendingIntent نمی‌شود، بلکه برای پر کردن پارامترهایی از intent بسته بندی شده استفاده می‌شود که هنگام ایجاد PendingIntent ارائه نمی‌شود.

بیایید به یک مثال نگاه کنیم:

val orderDeliveredIntent = Intent(applicationContext, OrderDeliveredActivity::class.java).apply {
   action = ACTION_ORDER_DELIVERED
}
val mutablePendingIntent = PendingIntent.getActivity(
   applicationContext,
   NOTIFICATION_REQUEST_CODE,
   orderDeliveredIntent,
   PendingIntent.FLAG_MUTABLE
)

این PendingIntent می‌تواند به برنامه سفارش آنلاین غذا تحویل داده شود. پس از تحویل، برنامه می‌تواند customerMessage را به عنوان intent extra همانند زیر ارسال کند:

val intentWithExtrasToFill = Intent().apply {
   putExtra(EXTRA_CUSTOMER_MESSAGE, customerMessage)
}
mutablePendingIntent.send(
   applicationContext,
   PENDING_INTENT_CODE,
   intentWithExtrasToFill
)

سپس برنامه مبدا EXTRA_CUSTOMER_MESSAGE را در intent خود مشاهده می‌کند و می‌تواند پیام را نمایش دهد.

نکات مهم هنگام ایجاد PendingIntent تغییر پذیر

  • هنگام ایجاد یک PendingIntent تغییر پذیر همیشه مولفه‌ای که در intent شروع می‌شود به صراحت تنظیم کنید. این کار را می‌توان به همان روشی که در بالا انجام داده‌ایم، با تعیین دقیق کلاس دریافتی انجام داد. اما با فراخوانی Intent.setComponent() نیز می‌توان این کار را انجام داد.

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

  • اگر سعی کنید مقادیر داخل PendingIntent غیر قابل تغییر را override کنید، موفق نخواهید بود. تحویل Intent بسته بندی شده قابل تغییر نیست.

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

جزئیات پرچم‌ها

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

FLAG_IMMUTABLE نشان می‌دهد که intent موجود در PendingIntent توسط برنامه‌های دیگری که یک intent را به PendingIntent.send() منتقل می‌کنند قابل تغییر نیست. یک برنامه همیشه می‌تواند از FLAG_UPDATE_CURRENT برای تغییر PendingIntent خود استفاده کند.

قبل از اندروید 12 یک PendingIntent ایجاد شده بدون این پرچم به طور پیش فرض قابل تغییر بود.

در نسخه‌های قبل از اندروید 6 (API 23PendingIntent‌ها همیشه قابل تغییر هستند.

FLAG_MUTABLE نشان می‌دهد که Intent در داخل PendingIntent باید اجازه دهد محتوای آن توسط برنامه با ادغام مقادیر Intent از پارامتر PendingIntent.send() بروز شود.

همیشه ComponentName را از Intent بسته بندی شده در هر PendingIntent قابل تغییر، پر کنید. عدم انجام این کار منجر به آسیب پذیری‌های امنیتی می‌شود.

این پرچم در اندروید 12 اضافه شد. قبل از اندروید 12، هر PendingIntent ایجاد شده بدون پرچم FLAG_IMMUTABLE به طور ضمنی قابل تغییر بودند.

FLAG_UPDATE_CURRENT درخواست می‌کند سیستم به جای ذخیره سازی PendingIntent جدید PendingIntent موجود را با داده‌های اضافی جدید بروز می‌کند. اگر هم ثبت نشده باشد، این یکی را ثبت می‌کند.

FLAG_ONE_SHOT فقط اجازه می‌دهد تا PendingIntent یک بار ارسال شود(از طریق PendingIntent.send()). اگر Intent موجود در آن فقط یک بار ارسال شود، می‌تواند هنگام انتقال PendingIntent به برنامه دیگر مهم باشد. با این کار می‌توان از اجرای چندین بار کارها توسط برنامه جلوگیری کرد.

استفاده از پرچم FLAG_ONE_SHOT از بروزرسانی مسائلی مانند "replay attack" جلوگیری می‌کند.

FLAG_CANCLE_CURRENT قبل از ثبت مورد جدید، PendingIntent موجود را لغو می‌کند. این مهم است که اگر PendingIntent خاصی به یک برنامه ارسال شده باشد و بخواهید آن را به برنامه دیگری ارسال کنید، باید داده‌ها را بروز کنید. با استفاده از FLAG_CANCEL_CURRENT برنامه اول دیگر قادر به فراخوانی send نیست، اما برنامه دوم این امکان را دارد.

دریافت PendingIntentها

گاهی اوقات سیستم یا سایر فریم ورک‌ها یک PendingIntent را به عنوان خروجی از فراخوانی API برمی‌گردانند. یک مثال برای آن متد MediaStore.createWriteRequest() است که در اندروید 11 اضافه شده است.

static fun MediaStore.createWriteRequest(
    resolver: ContentResolver, 
    uris: MutableCollection<Uri>
): PendingIntent

درست همانطور که یک PendingIntent ایجاد شده توسط برنامه ما با هویت برنامه ما اجرا می‌شود، یک PendingIntent ایجاد شده توسط سیستم نیز با هویت سیستم اجرا می‌شود. در مورد این API، به برنامه ما امکان می‌دهد Activity را شروع کند که می‌تواند به مجموعه Uriها اجازه نوشتن به برنامه ما را بدهد.

خلاصه

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

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

ما همچنین در مورد اقدامات احتیاطی که باید انجام دهیم صحبت کردیم، اطمینان از پر کردن ComponentName در Intent بسته بندی شده، اگر لازم باشد که PendingIntent حتماً تغییر پذیر باشد.

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

بروزرسانی‌های PendingIntent تنها یکی از تغییرات اندروید 12 بود که برای بهبود امنیت طراحی شده بود. می‌توانید این تغییرات را در اینجا بخوانید.

منبع

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
در انتظار ثبت رای

/@pouryasharifi78
پوریا شریفی
توسعه‌دهنده‌ی اندروید

ابتدا که با برنامه‌نویسی آشنا شدم به سمت php و طراحی وب رفتم، بعد از اون به توسعه‌ی اندروید علاقه‌مند شدم و تقریبا ۲ سال است که مشغول به برنامه‌نویسی اندروید هستم، همچنین عاشق یادگیری چیزهای جدید هستم.

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید