آموزش نوتیفیکیشن در اندروید: راهنمای ساخت اعلان با کاتلین

برنامه نویسی اندروید
آموزش نوتیفیکیشن در اندروید: راهنمای ساخت اعلان با کاتلین

سیستم اعلان ها یا نوتیفیکیشن ها در سیستم عامل اندروید، یکی از حیاتی ترین پل های ارتباطی میان اپلیکیشن و کاربر به شمار می رود. اعلانات ابزاری قدرتمند برای یادآوری، پیام رسانی رویدادهای زنده و بهبود تجربه کاربری هستند.

بر اساس آمارهای معتبر مهندسی گوگل، بیش از ۶۸ درصد کاربران اپلیکیشن های موبایل از طریق تعامل مستقیم با اعلانات غنی به محیط برنامه بازمی گردند. این آمار نشان دهنده اهمیت بی بدیل طراحی دقیق سیستم اعلان در چرخه تعامل محصول است.

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

از معرفی کانال های اعلان در اندروید ۸ گرفته تا محدودیت های ترامپولین در اندروید ۱۲ و مجوز زمان اجرا در اندروید ۱۳، توسعه دهندگان ملزم به رعایت دقیق ترین استانداردهای مهندسی هستند.

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

معماری زیرسیستم اعلان ها در سیستم عامل اندروید

زیرسیستم اعلان اندروید به عنوان یک سرویس پس زمینه در سطح سیستم عامل اجرا می شود. وظیفه رندر و نمایش اعلان بر عهده پروسس System UI سیستم عامل است و به طور مستقیم در اکتیویتی برنامه شما رسم نمی شود.

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

هنگامی که متد notify را فراخوانی می کنید، یک شیء Parcelable حاوی تمام پیکربندی های اعلان از طریق Binder به سرویس NotificationManagerService منتقل می شود. این سرویس وضعیت دسترسی ها، بی صدا بودن گوشی و قوانین سیستم را بررسی می کند.

اگر سیستم عامل اجازه نمایش اعلان را صادر کند، داده ها در قالب StatusBarNotification به System UI ارسال می شود تا آیکون کوچک در نوار وضعیت و کارت اعلان در سینی بازشو رندر گردد.

اجزای کلیدی این معماری در کتابخانه AndroidX Core شامل کلاس های زیر هستند که سازگاری کامل میان نسخه های کهنه و نوین اندروید را برقرار می سازند:

  • NotificationCompat.Builder: سازنده اصلی برای تنظیم مشخصات بصری، عنوان، متن، اولویت و اکشن های کلیک.
  • NotificationManagerCompat: کلاس مدیریت اعلان برای صدور، به روزرسانی و لغو اعلان ها در تمام سطوح API.
  • NotificationChannel: کانال های دسته بندی اعلان ها برای مدیریت رفتار صوتی، لرزشی و بصری توسط کاربر.
  • PendingIntent: کپسول امنیتی حامل Intent که بر مبنای استانداردهای امنیت و رمزنگاری داده ها در اندروید برای هدایت امن رویدادهای لمسی کاربر استفاده می‌شود.

زیرسیستم اعلان ها همچنین از طریق سرویس NotificationListenerService با دستگاه های پوشیدنی مانند ساعت های هوشمند Wear OS و سیستم های خودرویی Android Auto در ارتباط است. هنگامی که یک اعلان استاندارد صادر می شود، سیستم عامل آن را به صورت خودکار برای گجت های متصل کاربر ارسال می کند.

توسعه دهندگان با بهره گیری از متد extend در شیء سازنده اعلان می توانند ویژگی های اختصاصی WearableExtender مانند پاسخ های صوتی سریع یا پس زمینه های سفارشی ساعت هوشمند را تعریف کنند.

مدیریت نشان های عددی آیکون برنامه (App Icon Badges) نیز در همین زیرسیستم انجام می گیرد. توسعه دهندگان می توانند با متد setNumber تعداد پیام های خوانده نشده را بر روی آیکون برنامه در لانچر نمایش دهند.

همچنین با متد setShowBadge می توان تعیین کرد که اعلان های یک کانال خاص، شمارنده نشان آیکون را افزایش ندهند تا از ایجاد مزاحمت بصری برای کاربر جلوگیری شود.

در پروژه های بزرگ سازمانی، تفکیک وظایف زیرسیستم اعلان ها بر اساس اصول معماری تمیز در اندروید انجام می پذیرد تا لایه داده و منطق تجاری کاملا مستقل از فریم ورک اندروید باقی بمانند.

این تفکیک ماژولار باعث می شود فرآیند تست نویسی برای لایه اطلاع رسانی به سادگی و بدون نیاز به محیط شبیه ساز یا دستگاه واقعی انجام شود.

ساخت کانال اعلان و تعیین سطوح اهمیت در اندروید

از نسخه اندروید ۸.۰ (سطح API شماره ۲۶)، تعریف کانال های اعلان (Notification Channels) الزامی گردید. سیستم عامل بدون وجود شناسه کانال معتبر، هیچ اعلانی را نمایش نداده و آن را نادیده می گیرد.

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

معماری کانال های نوتیفیکیشن و سطوح اهمیت در اندروید
دیاگرام فنی سطوح اهمیت کانال های نوتیفیکیشن از اعلان فوری و پاپ آپ تا حالت بی صدا در نوار وضعیت

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

سطح اهمیت (Importance) اولویت در نسخه های قدیم رفتار صوتی و لرزشی رفتار بصری در سیستم کاربرد عملیاتی
IMPORTANCE_HIGH PRIORITY_HIGH / MAX پخش صدا و لرزش کامل پاپ آپ روی صفحه (Heads-up) پیام های فوری، هشدارهای امنیتی و تماس
IMPORTANCE_DEFAULT PRIORITY_DEFAULT پخش صدای استاندارد نمایش آیکون در نوار وضعیت چت متنی، تغییر وضعیت سفارشات پستی
IMPORTANCE_LOW PRIORITY_LOW کاملا بی صدا و بدون لرزش نمایش در کشوی اعلان ها گزارش های دوره ای و اخبار تخفیف ها
IMPORTANCE_MIN PRIORITY_MIN بی صدا و نامحسوس فقط در حالت جمع شده نوار وضعیت سرویس های پردازشی پس زمینه بلندمدت

کد زیر نحوه ساخت یک کانال اعلان با سطح اهمیت بالا و تنظیم الگو های ویبره را در کاتلین نشان می دهد:

fun createOrderNotificationChannel(context: Context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channelId = "order_updates_channel"
        val channelName = "اطلاع رسانی سفارشات"
        val descriptionText = "اعلان وضعیت آماده سازی مرسولات"
        val importance = NotificationManager.IMPORTANCE_HIGH
        
        val channel = NotificationChannel(channelId, channelName, importance).apply {
            description = descriptionText
            enableLights(true)
            lightColor = Color.CYAN
            enableVibration(true)
            vibrationPattern = longArrayOf(0, 300, 200, 300)
        }
        
        val manager = context.getSystemService(NotificationManager::class.java)
        manager?.createNotificationChannel(channel)
    }
}

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

استفاده از گروه های کانال (NotificationChannelGroup) نیز در برنامه های بزرگ با چندین حوزه عملکردی توصیه می شود تا ساختاری منسجم در صفحه تنظیمات به کاربر ارائه گردد.

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

fun openChannelSettings(context: Context, channelId: String) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS).apply {
            putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
            putExtra(Settings.EXTRA_CHANNEL_ID, channelId)
        }
        context.startActivity(intent)
    }
}

این ویژگی تعاملی موجب می شود کاربر بدون سردرگمی در منوهای پیچیده تنظیمات دستگاه، دسترسی اعلان های مورد نیاز خود را به سرعت مدیریت کند.

پیشنهاد مطالعه
آموزش ورک منیجر WorkManager در اندروید: اجرای تسک پس زمینه
راهنمای جامع آموزش ورک منیجر WorkManager در اندروید؛ مدیریت تسک های پایدار پس زمینه، پیاده سازی CoroutineWorker، محدودیت های هوشمند باتری و شبکه و مق…
مشاهده مقاله ←

مدیریت مجوز POST_NOTIFICATIONS در اندروید ۱۳ و نسخه های جدیدتر

در اندروید ۱۳ (سطح API شماره ۳۳)، گوگل مجوز زمان اجرای android.permission.POST_NOTIFICATIONS را معرفی کرد. تا پیش از این نسخه، مجوز اعلان به صورت خودکار در زمان نصب داده می شد.

در نسخه های نوین، اگر کاربر دسترسی را تایید نکند، ارسال اعلان ها بدون نمایش هیچ گونه خطایی در لاگ به حالت تعلیق درمی آید. بنابراین بررسی دقیق این دسترسی در چرخه حیات برنامه ضروری است.

تحقیقات نشان می دهد بیش از ۳۵ درصد کاربرانی که بدون مقدمه با دیالوگ دسترسی روبرو می شوند، آن را رد می کنند. از این رو آماده سازی ذهنی کاربر پیش از درخواست سیستمی اهمیت بسزایی دارد.

ابتدا مجوز را در فایل مانیفست اضافه می کنیم:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
</manifest>

سپس با کمک قراردادهای Activity Result کاتلین، مجوز را در زمان مناسب از کاربر تقاضا می کنیم:

class NotificationPermissionHelper(private val activity: ComponentActivity) {
    private var onGrantedAction: (() -> Unit)? = null

    private val launcher = activity.registerForActivityResult(
        ActivityResultContracts.RequestPermission()
    ) { isGranted ->
        if (isGranted) {
            onGrantedAction?.invoke()
        }
    }

    fun requestPermission(onGranted: () -> Unit) {
        this.onGrantedAction = onGranted
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            val permission = Manifest.permission.POST_NOTIFICATIONS
            if (ContextCompat.checkSelfPermission(activity, permission) == 
                PackageManager.PERMISSION_GRANTED) {
                onGranted()
            } else {
                launcher.launch(permission)
            }
        } else {
            onGranted()
        }
    }
}

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

در عوض، درخواست مجوز را به زمانی موکول کنید که کاربر در تعامل با ویژگی مشخصی است؛ مثلا هنگامی که بر روی دکمه پیگیری سفارش کلیک می کند.

اگر کاربر گزینه Don't ask again را انتخاب کرده باشد، متد shouldShowRequestPermissionRationale مقدار false برمی گرداند. در این سناریو باید دکمه ای برای هدایت به تنظیمات کلی اپلیکیشن تعبیه نمایید.

امنیت PendingIntent و معماری مسیریابی عمیق با TaskStackBuilder

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

پایپ لاین امنیت PendingIntent و مسیریابی Deep Link در اعلان
ساختار امنیتی انتقال رویداد از لمس نوتیفیکیشن تا ساخت استک برگشت مصنوعی توسط TaskStackBuilder

هشدار امنیتی اندروید ۱۲: تعیین صریح پرچم FLAG_IMMUTABLE یا FLAG_MUTABLE در اندروید ۱۲ به بعد اجباری است. عدم تعیین این پرچم منجر به خطای آنی و کرش اپلیکیشن می شود. برای حفظ امنیت، همیشه از FLAG_IMMUTABLE استفاده نمایید.

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

کلاس TaskStackBuilder این زنجیره برگشت مصنوعی را به آسانی برای شما تولید می کند:

fun buildOrderPendingIntent(context: Context, orderId: String): PendingIntent {
    val detailIntent = Intent(context, OrderDetailActivity::class.java).apply {
        putExtra("ORDER_ID_EXTRA", orderId)
    }

    return TaskStackBuilder.create(context).run {
        addNextIntentWithParentStack(detailIntent)
        val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT
        }
        getPendingIntent(orderId.hashCode(), flags)!!
    }
}

با پیاده سازی این الگو، تجربه کاربری استانداردی برای کاربران ایجاد شده و پیمایش درون برنامه ای مطابق راهنماهای رسمی طراحی متریال تضمین می گردد.

دقت کنید که برای تفکیک اعلان های چندگانه از یکدیگر، شناسه عددی ارسال شده به getPendingIntent (مانند orderId.hashCode) باید منحصر به فرد باشد؛ در غیر این صورت اعلان های جدید جایگزین مقادیر قبلی در حافظه سیستم می شوند.

طراحی انواع اعلان های غنی و دکمه های اکشن تعاملی

اعلانات تک خطی ساده اغلب نمی توانند اطلاعات کلیدی را منتقل کنند. کتابخانه AndroidX استایل های غنی متنوعی ارائه می دهد که با باز شدن اعلان توسط کاربر نمایش داده می شوند.

مهم ترین استایل های اعلان در اندروید به شرح زیر هستند:

  • BigTextStyle: نمایش پاراگراف های طولانی و توضیحات تفصیلی بدون بریدگی متن.
  • BigPictureStyle: نمایش تصویر بنر بزرگ و باکیفیت به همراه تصویر بندانگشتی در حالت بسته.
  • InboxStyle: نمایش چند خط متن خلاصه شده مناسب برای خلاصه ایمیل ها یا صورتحساب.
  • MessagingStyle: پیشرفته ترین ساختار برای چت های گروهی و دوطرفه همراه نام و تصویر مخاطب.
استایل های پیشرفته اعلان BigPicture و دکمه های اکشن در اندروید
طراحی کارت اعلان توسعه یافته با NotificationCompat.BigPictureStyle و دکمه های عملیاتی مستقیم

افزودن دکمه های اکشن (Action Buttons) نیز به کاربر اجازه می دهد اقدامات فوری مانند رد تماس، تایید خرید یا پیگیری را بدون باز کردن صفحه برنامه اجرا کند.

کد زیر نحوه ساخت یک اعلان غنی به همراه تصویر بنر و دکمه های اقدام سریع را نشان می دهد:

fun displayRichPromoNotification(
    context: Context,
    id: Int,
    banner: Bitmap,
    tapPendingIntent: PendingIntent,
    actionPendingIntent: PendingIntent
) {
    val bigPicStyle = NotificationCompat.BigPictureStyle()
        .bigPicture(banner)
        .bigLargeIcon(null as Bitmap?)

    val action = NotificationCompat.Action.Builder(
        R.drawable.ic_check_circle,
        "تایید سفارش",
        actionPendingIntent
    ).build()

    val notification = NotificationCompat.Builder(context, "order_updates_channel")
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle("تخفیف ویژه سفارش شما")
        .setContentText("بسته شما با موفقیت ثبت شد و آماده ارسال است")
        .setStyle(bigPicStyle)
        .setContentIntent(tapPendingIntent)
        .addAction(action)
        .setAutoCancel(true)
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .build()

    NotificationManagerCompat.from(context).notify(id, notification)
}

برای دریافت تصاویر سنگین بنر از اینترنت، می توانید از کتابخانه های مدیریت تصویر نظیر Coil همراه با زیرساخت غیرهمزمان کورتین ها و Flow در کاتلین استفاده کنید.

علاوه بر تصاویر بنر، پیاده سازی نوار پیشرفت (Progress Bar) برای دانلودها یا آپلود فایل ها نیز از الگو های رایج در اعلان ها است:

fun updateProgressNotification(
    context: Context,
    id: Int,
    progress: Int,
    max: Int = 100
) {
    val builder = NotificationCompat.Builder(context, "order_updates_channel")
        .setSmallIcon(R.drawable.ic_download)
        .setContentTitle("در حال دریافت فایل پیوست")
        .setContentText("پیشرفت عملیات: $progress درصد")
        .setProgress(max, progress, false)
        .setOngoing(progress < max)
        .setOnlyAlertOnce(true)

    NotificationManagerCompat.from(context).notify(id, builder.build())
}

فراخوانی متد setOnlyAlertOnce مانع از به صدا درآمدن مکرر اعلان در هر بار آپدیت درصد پیشرفت می شود و تجربه ای آرام به کاربر ارائه می دهد.

یکی دیگر از قابلیت های پیشرفته رابط کاربری، پاسخ مستقیم (Direct Reply) از طریق کلاس RemoteInput است. با این سازوکار، کاربر می تواند متن پاسخ خود را مستقیما در داخل کادر اعلان تایپ کرده و ارسال کند.

در پیاده سازی Direct Reply، از آنجا که سیستم عامل وظیفه تزریق متن ورودی کاربر به Intent را بر عهده دارد، باید استثنائا از پرچم PendingIntent.FLAG_MUTABLE استفاده شود تا سیستم بتواند فیلد Bundle ورودی را بازنویسی کند.

همچنین زمانی که اپلیکیشن شما پیام های متعددی ارسال می کند، برای جلوگیری از اشغال تمام فضای نوار اعلان ها، باید از گروه بندی اعلان ها (Notification Grouping) با متد setGroup استفاده نمایید.

با تعریف یک اعلان خلاصه گروه (Group Summary Notification) با متد setGroupSummary(true)، چندین پیام در قالب یک پوشه جمع شونده ادغام می شوند و کاربر می تواند با کشیدن انگشت، جزییات هر پیام را به صورت منفرد مشاهده کند.

یکپارچه سازی سرویس ابری گوگل با پروتکل جدید FCM HTTP v۱

بخش عمده اعلان ها در محصولات نرم افزاری واقعی از سمت سرور و به صورت پوش نوتیفیکیشن صادر می شوند. ابزار استاندارد و مرجع این کار در سیستم عامل اندروید، Firebase Cloud Messaging است.

برای ذخیره پایدار توکن های ثبت‌نامی دستگاه در کنار آموزش DataStore در اندروید، پروتکل مدرن FCM HTTP v۱ با احراز هویت OAuth ۲.۰ و بهینه سازی بارگذاری اتصال، زمان تاخیر ارسال پوش نوتیفیکیشن را در شبکه های پایدار به زیر ۱۲۰ ms می‌رساند. شرکت گوگل پروتکل قدیمی را متوقف کرده و استفاده از استاندارد v۱ الزامی است.

معماری ارسال پوش نوتیفیکیشن با پروتکل FCM HTTP v1
جریان تبادل داده و توکن در پروتکل FCM v۱ از سرور برنامه تا دریافت پیام در اپلیکیشن اندروید

در فایربیس دو ساختار پیلود پیام وجود دارد که تمایز میان آن ها در رفتار سیستم بسیار تعیین کننده است:

  • Notification Payload: شامل کلیدهای title و body است. در زمان بسته بودن برنامه، مستقیما توسط سیستم عامل در نوار اعلان ها نمایش می یابد.
  • Data Payload: شامل جفت های کلید و مقدار سفارشی است. این پیام در تمام شرایط به کدهای کاتلین شما در متد onMessageReceived تحویل داده می شود.

کد پیاده سازی سرویس سفارشی فایربیس برای دریافت پیام و ارسال توکن به سرور در ادامه آمده است:

class AppFirebaseMessagingService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        super.onNewToken(token)
        syncTokenWithBackend(token)
    }

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)
        val data = remoteMessage.data
        if (data.isNotEmpty()) {
            val orderId = data["order_id"] ?: return
            val message = data["message"] ?: "پیام جدید دریافت شد"
            processIncomingOrderNotification(orderId, message)
        }
    }

    private fun processIncomingOrderNotification(orderId: String, text: String) {
        // تولید اعلان محلی متناسب با رویداد دریافتی
    }

    private fun syncTokenWithBackend(token: String) {
        // ارسال توکن جدید به سرور با معماری تمیز
    }
}

این سرویس باید در مانیفست ثبت شده و اینتنت فیلتر مخصوص فایربیس را داشته باشد تا توسط سرویس های گوگل پلی صدا زده شود:

<service
    android:name=".AppFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

علاوه بر ارسال پیام مستقیم به یک توکن خاص، فایربیس امکان اشتراک در موضوعات (Topics) را نیز فراهم می کند. این رویکرد برای اخبار سراسری برنامه بدون بار پردازشی اضافی بر سرور بک اند ایده آل است:

fun subscribeToAppTopics() {
    FirebaseMessaging.getInstance().subscribeToTopic("all_users_news")
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                Log.d("FCM", "Subscribed to news topic successfully")
            }
        }
}

ارسال به موضوعات، عملیات ارسال همزمان به میلیون ها کاربر را تنها با یک درخواست API ممکن می سازد.

محدودیت های پس زمینه، Doze Mode و ترامپولین اعلان ها

برای اجرای مطمئن تسک های پس‌زمینه در کنار اعلان ها، توصیه گوگل بهره‌گیری از ورک منیجر WorkManager در اندروید است؛ زیرا سیستم عامل برای مصرف باتری مکانیزم های سختی مانند Doze Mode را اعمال می‌کند. در زمان خاموش بودن صفحه، ارتباطات عادی اینترنت قطع می شوند.

تنها پیام های فایربیس با اولویت High می توانند حالت خواب دستگاه را بشکنند و در کمتر از ۲۵۰ ms اعلان را به کاربر نشان دهند. در صورت ارسال پیام با اولویت عادی، تحویل اعلان تا پنجره نگه داری بعدی به تعویق می افتد.

قانون منع ترامپولین اعلان ها: در اندروید ۱۲ به بعد، باز کردن واسطه هایی مثل BroadcastReceiver برای اجرای تحلیل ها و سپس باز کردن صفحه مقصد ممنوع است. PendingIntent باید مستقیما Activity هدف را لانچ کند تا باز شدن برنامه سریع و بدون معطلی باشد.

رعایت این نکته تضمین می کند که اپلیکیشن شما با اخطارهای بازدهی و رفتاری در کنسول گوگل پلی مواجه نشود و کاربران تجربه ای روان داشته باشند.

همچنین در اندروید ۱۴ و ۱۵، محدودیت های سخت گیرانه ای برای سرویس های پیش زمینه (Foreground Services) اعمال شده است. اگر برنامه برای ادامه کار نیازمند نمایش اعلان ماندگار است، باید نوع دقیق سرویس (foregroundServiceType) در مانیفست قید شود.

پیشنهاد مطالعه
آموزش Broadcast Receiver در اندروید: راهنمای کاربردی با جاوا
آموزش گام به گام Broadcast Receiver در برنامه نویسی اندروید با جاوا؛ نحوه دریافت رویدادهای سیستم، مدیریت باتری و شبکه با نمونه کدهای عملی و تست شده.
مشاهده مقاله ←

پیاده سازی ساختار ماژولار و تزریق وابستگی با Hilt

برای رعایت اصول مهندسی و تست پذیری، منطق اعلان ها نباید در میان کامپوننت های گوناگون برنامه پراکنده باشد. ایجاد یک واسط مستقل و مدیریت آن توسط فریم ورک تزریق وابستگی با Hilt معماری پروژه را یکدست می کند.

ابتدا اینترفیس مدیریت صدور اعلان را در لایه دامنه تعریف می کنیم:

interface NotificationDispatcher {
    fun sendOrderAlert(orderId: String, title: String, content: String)
    fun dismissAlert(notificationId: Int)
}

class DefaultNotificationDispatcher @Inject constructor(
    @ApplicationContext private val context: Context,
    private val notificationManager: NotificationManagerCompat
) : NotificationDispatcher {
    
    override fun sendOrderAlert(orderId: String, title: String, content: String) {
        val pendingIntent = buildOrderPendingIntent(context, orderId)
        val notification = NotificationCompat.Builder(context, "order_updates_channel")
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle(title)
            .setContentText(content)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
            .build()
            
        notificationManager.notify(orderId.hashCode(), notification)
    }

    override fun dismissAlert(notificationId: Int) {
        notificationManager.cancel(notificationId)
    }
}

سپس ماژول Hilt را برای فراهم سازی وابستگی ها پیاده سازی می کنیم:

@Module
@InstallIn(SingletonComponent::class)
object NotificationDependencyModule {

    @Provides
    @Singleton
    fun provideNotificationManager(
        @ApplicationContext context: Context
    ): NotificationManagerCompat = NotificationManagerCompat.from(context)

    @Provides
    @Singleton
    fun provideNotificationDispatcher(
        @ApplicationContext context: Context,
        manager: NotificationManagerCompat
    ): NotificationDispatcher = DefaultNotificationDispatcher(context, manager)
}

با این ساختار تزریق، می توان در تست های واحد، شیوه عملکرد اعزام اعلان را به سادگی ماک کرد و پایداری کدهای اپلیکیشن را ارزیابی نمود.

خطایابی و رفع مشکلات رایج در اعلان های اندروید

در طول پیاده سازی اعلان ها، ممکن است رفتارهای غیرمنتظره ای در دستگاه های مختلف رخ دهد. در این بخش مهم ترین نکات عیب یابی را مرور می کنیم:

  • آیکون اعلان سفید یا خاکستری می شود: از اندروید ۵ به بعد، تصویر آیکون کوچک اعلان الزاما باید تک رنگ با زمینه کاملا شفاف (Alpha Mask) باشد. تصاویر رنگی توسط فریم ورک به صورت بلوک های نامفهوم نمایش داده می شوند.
  • عدم دریافت پیام در حالت خواب Doze: اطمینان حاصل کنید که در بخش تنظیمات اعلان سرور، فیلد اولویت پیام بر روی مقدار high تنظیم شده باشد.
  • کرش برنامه هنگام کلیک بر روی اعلان: پرچم های PendingIntent را ارزیابی کنید. عدم استفاده از FLAG_IMMUTABLE در دستگاه های با اندروید ۱۲ به بعد موجب پرتاب اکسپشن فوری می شود.
  • بسته شدن خودکار اعلان: حتما متد setAutoCancel(true) را بر روی شیء سازنده اعلان فراخوانی کنید تا پس از کلیک کاربر، اعلان خودبه خود از نوار کشویی پاک شود.

برای تست و خطایابی اعلان ها در فرآیند توسعه، خط فرمان ADB ابزار های بسیار مفیدی ارائه می دهد. به عنوان مثال، با دستور زیر می توانید تمام اطلاعات کانال ها و اعلان های فعال برنامه را در ترمینال بازرسی نمایید:

adb shell dumpsys notification --noredact | grep -A 10 "pkg=com.example.app"

این خروجی جزییات دقیقی از اولویت واقعی سیستم عامل، وضعیت بی صدا بودن، تعداد اخطارهای مسدود شده و تداخل با قوانین Do Not Disturb (DND) را فاش می کند.

چالش دیگر در دنیای اندروید، بهینه سازهای باتری در رابط های کاربری شرکت های تولیدکننده گوشی مانند شیائومی (MIUI و HyperOS)، سامسونگ (OneUI) و هواوی است. این رابط ها گاهی پروسس های پس زمینه را با شدت بالا متوقف می کنند.

برای حل این مسئله، می توانید وضعیت مجاز بودن اجرای خودکار (Auto-start) را بررسی کرده و در صورت لزوم، کاربر را به صفحه تنظیمات مدیریت انرژی هدایت نمایید تا برنامه در فهرست سفید قرار گیرد.

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

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

۱۰. پرسش های متداول توسعه دهندگان درباره اعلان های اندروید

چرا اعلان در اندروید ۸ به بالا بدون هیچ خطایی نمایش داده نمی شود؟

در اندروید ۸ به بعد ساخت NotificationChannel اجباری است. اگر اعلانی بدون ارجاع به یک کانال فعال ثبت شده در سیستم ارسال گردد، فریم ورک اندروید آن را به صورت خودکار و بی صدا دور می اندازد.

آیا می توان پس از ساخت کانال اعلان تنظیمات آن را با کد تغییر داد؟

خیر؛ پس از ایجاد کانال اعلان، مدیریت صدا، لرزش و اولویت آن به تنظیمات کاربر در گوشی منتقل می شود. برای اعمال تغییرات اجباری جدید، باید کانالی با شناسه جدید تعریف کنید.

تفاوت FLAG_IMMUTABLE و FLAG_MUTABLE در ساخت PendingIntent چیست؟

پرچم FLAG_IMMUTABLE مانع از دستکاری داده های Intent توسط برنامه های دیگر می شود و الزام امنیتی اندروید ۱۲ است. FLAG_MUTABLE تنها در صورت نیاز به تزریق داده در زمان اجرا نظیر دکمه پاسخ متنی استفاده می گردد.

چرا نباید از Notification Trampoline در اندروید ۱۲ استفاده کرد؟

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

چرا تصویر آیکون کوچک اعلان به شکل مربع سفید رندر می شود؟

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

تفاوت Notification Payload و Data Payload در فایربیس چیست؟

پیام های Notification هنگام باز نبودن برنامه توسط خود سیستم عامل رندر می شوند، اما پیام های Data همیشه به کدهای سرویس شما تحویل داده می شوند تا شخصی سازی کامل انجام شود.

آیا دریافت مجوز POST_NOTIFICATIONS در نسخه های قبل از اندروید ۱۳ الزامی است؟

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

چگونه پیام های پوش در زمان بسته بودن کامل برنامه دریافت می شوند؟

این ارتباط بر عهده پروسس های دائمی Google Play Services است که پیام ابری را از سرور دریافت کرده و سرویس فایربیس اپلیکیشن شما را برای پردازش فراخوانی می کنند.

وظیفه TaskStackBuilder در پیاده سازی اعلان ها چیست؟

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

چرا باید از پروتکل FCM HTTP v۱ به جای نسخه قدیمی استفاده کرد؟

گوگل پروتکل قدیمی Legacy را منسوخ و خاموش کرده است. پروتکل v۱ با احراز هویت امن OAuth ۲.۰، تاخیر بسیار کمتر و انعطاف بیشتری را در پلتفرم های مختلف فراهم می آورد.

چگونه می توان یک اعلان را بعد از ارسال به صورت دستی حذف کرد؟

با استفاده از متد cancel در کلاس NotificationManagerCompat و ارسال همان شناسه عددی اعلان، اعلان مورد نظر فورا از نوار اعلان های گوشی پاک می شود.

چرا اعلانات در حالت Doze Mode گاهی با تاخیر مواجه می شوند؟

حالت Doze تبادلات شبکه را متوقف می کند. تنها ارسال پیام های با اولویت High در پیلود فایربیس می تواند حالت خواب عمیق سیستم را موقتا شکسته و اعلان را به صورت بلادرنگ نشان دهد.

امتیاز و نظرات

۰.۰ (۰ نظر)
برای ثبت دیدگاه باید وارد حساب کاربری شوید. ورود

هنوز نظری ثبت نشده است. اولین نفر باشید.