ساخت اپلیکیشن لانچر اندروید — به زبان ساده

۲۳۵ بازدید
آخرین به‌روزرسانی: ۲۹ شهریور ۱۴۰۲
زمان مطالعه: ۵ دقیقه
ساخت اپلیکیشن لانچر اندروید — به زبان ساده

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

شاید بهتر باشد ابتدا مفهوم حالت کیوسک را توضیح دهیم. زمانی که یک اپلیکیشن در حالت کیوسک اجرا می‌شود، کاربر نمی‌تواند از اپلیکیشن خارج شود. در این حالت صفحه همواره روشن است. اپلیکیشن همواره در پیش‌زمینه است. نوار نوتیفیکیشن معمولاً پنهان یا غیرفعال شده و تقریباً همیشه دکمه Home غیرفعال می‌شود. این وضعیت عملاً موجب می‌شود که کاربر اپلیکیشن همواره درون همان اپلیکیشن منفرد باقی بماند. بنابراین با این توضیح احتمالاً اکنون می‌توانید متوجه شوید که چرا لازم است اپلیکیشن‌های ثانویه از داخل اپلیکیشن اصلی اجرا شوند.

لانچ کردن اپلیکیشن‌های دیگر از داخل یک اپلیکیشن روی اندروید، خود چالش بزرگی محسوب نمی‌شود. قطعه کدهای زیادی در بستر آنلاین وجود دارند که شیوه اجرای این کار را مدیریت می‌کنند. با این حال، یافتن مثال پایداری از شیوه پیاده‌سازی قابلیتی مانند این و همزمان پیروی از اصولی که گوگل برای توسعه اندروید توصیه می‌کند، مانند نوشتن کد در کاتلین با استفاده از الگوی معماری MVVM، استفاده از «اتصال داده» (data binding) و بهره‌گیری از «کامپوننت‌های معماری اندروید» (AAC) مانند ViewModel و Navigation در صورت امکان، کار ساده‌ای محسوب نمی‌شود.

بنابراین بدون هیچ توضیح اضافی در ادامه مثال ساده‌ای از طراحی سطح بالای یک قابلیت لانچر اپلیکیشن را می‌بینید که در ادامه هر یک از کامپوننت‌های شماره‌گذاری شده در نمودار نیز توضیح داده شده‌اند. چند کلاس کمکی مانند ابزار Logger به جهت کاستن از پیچیدگی حذف شده‌اند، اما شما می‌توانید کد کامل پروژه را در این ریپوی گیت‌هاب (+) مشاهده کنید.

لانچر اندروید
برای مشاهده تصویر در اندازه بزرگتر روی آن کلیک کنید.

هدف از هر کامپوننت شماره‌گذاری شده در نمودار فوق در ادامه توضیح داده شده است.

1. LauncherDialog

کامپوننت AppLauncherDialog، کادر محاوره‌ای که برای کاربر نمایش پیدا می‌کند را شامل می‌شود و مجموعه اپلیکیشن‌هایی که می‌توان اجرا کرد را ارائه می‌کند. این کلاس از اتصال‌های داده استفاده می‌کند و AppLauncherViewModel را «مشاهده» (Observe) می‌کند، به طوری که تغییرات داده‌ها بی‌درنگ در UI بازتاب می‌یابند. کلاس این کادر محاوره‌ای در زمینه همه اکشن‌ها (مانند لانچ کردن اپلیکیشن) مختصر است و از سوی «مدل نما» (View Model) مدیریت می‌شود. در نهایت باید اشاره کرد که در پروژه نمونه این کادر محاوره از داشبورد قالب‌بندی باز می‌شود و به منظور نمایش ناوبری از فرگمان به یک کادر محاوره‌ای با کامپوننت معماری Navigation طراحی شده است.

2. AppLauncherViewModel

کلاس AppLauncherViewModel شامل داده‌هایی است که درون نما (AppLauncherDialog) عرضه می‌شوند. داده‌های ارائه شده از طریق واداشتن AppLauncherViewModel برای تماشای مدل داده (AppLauncherModel) به دست آمده‌اند. تا پیش از ارائه، داده‌ها انتقال می‌یابند و هم قابلیت تعامل با اکشن می‌یابند و هم قابلیت عرضه پیدا می‌کنند. در مثال ما این وضعیت شامل استخراج اطلاعات لانچ از هر یک از پکیج‌های اپلیکیشن در مدل داده است و بدین ترتیب از ایجاد نسخه تکراری پکیج‌ها خودداری می‌شود و هر پکیجی که روی دستگاه نباشد حذف می‌شود. در نهایت اپلیکیشن‌ها بر مبنای نام مرتب‌سازی می‌شوند. قطعه کد زیر شیوه استخراج اطلاعات اپلیکیشن از طریق نام پکیج را نمایش می‌دهد.

1            appPackages?.apply {
2                distinctBy { it }.forEach { appPackage -> // de-duplicate
3                    try {
4                        val packageManager = AppContext.packageManager
5
6                        // extract the app meta data
7                        val appInfo = packageManager.getApplicationInfo(appPackage, PackageManager.GET_META_DATA)
8
9                        // extract the app name
10                        val appLabel = packageManager.getApplicationLabel(appInfo)
11
12                        // extract the app icon
13                        val appIcon = packageManager.getApplicationIcon(appInfo)
14
15                        // extract the app launch intent
16                        val appLaunchIntent = packageManager.getLaunchIntentForPackage(appPackage)
17                        appLaunchIntent?.addCategory(CATEGORY_LAUNCHER)
18
19                        // add the application info to our collection
20                        apps.add(App.AppInfo(appPackage, appLabel.toString(), appIcon, appLaunchIntent))
21                    } catch (nameNotFoundException: PackageManager.NameNotFoundException) {
22                        Logger.i("Package '$appPackage' not found on device, skipping")
23                    }
24                }
25
26                // now let's sort the apps by app label
27                apps.sortBy { it.label.toLowerCase() }
28            }

3. AppLauncherModel

AppLauncherModel شامل داده‌های از پیش تبدیل یافته یا خامی است که از سوی AppLauncherViewModel مورد مشاهده (Obderve) قرار می‌گیرند و در نهایت در AppLauncherDialog ارائه می‌شوند. این داده‌ها صرفاً مجموعه‌ای از نام‌های پکیج محسوب می‌شوند که از سوی لانچر مورد پشتیبانی قرار می‌گیرند. همچنین فلگی وجود دارد که تعیین می‌کند آیا اپلیکیشن باید به صوت نرمال لانچ شود یا باید آن را در حالت «آزاد» (free form) یا «شناور» (floating) لانچ کنیم. این حالت‌ها صرفاً روی دستگاه‌هایی پشتیبانی می‌شوند که اندروید Nougat و بالاتر را اجرا می‌کنند. البته متأسفانه همه دستگاه‌هایی که اندروید Nougat یا بالاتر را اجرا می‌کنند، امکان اجرای این حالت را ندارند.

4. AppLauncherFileMonitor

قابلیت لانچر اپلیکیشن باید بداند کدام اپلیکیشن‌ها برای کاربر نمایش پیدا می‌کنند. این اطلاعات می‌تواند از چندین محل تأمین شود. همه این موارد به شیوه‌ای که می‌خواهید این قابلیت را کنترل کنید وابسته هستند. آیا این قابلیت باید از سوی شما کنترل شود و یا می‌توان کنترل آن را به کاربر سپرد؟ اگر این قابلیت از شما کنترل شود، در این صورت آیا داده‌ها برای نمونه در یک پایگاه داده ابری مانند Firebase Cloud Store موجود هستند یا نه. اگر قرار است از سوی کاربر کنترل شوند، در این صورت آیا اطلاعات در یک پایگاه داده محلی مانند SQLite (با بهره‌گیری از Room) با حتی در بخش Shared Preferences موجود هستند و کاربر می‌تواند اپلیکیشن‌های جدید را در زمان اجرا به این قابلیت اضافه کند یا نه. ما در مثال نسبتاً ساده خودمان ذخیره پیکربندی در قالب یک فایل JSON در دایرکتوری داده اپلیکیشن را انتخاب کردیم.

1{
2    "Apps": [
3        "com.ebay.mobile",
4        "com.google.android.apps.maps",
5        "com.socialnmobile.hd.flashlight",
6        "com.samsung.android.contacts",
7        "com.babybillssoftwarefactory.bernie2020",
8        "com.babybillssoftwarefactory.irn",
9        "com.babybillssoftwarefactory.lolmemory",
10        "com.babybillssoftwarefactory.lql",
11        "com.decluttr",
12        "com.groupon",
13        "com.google.android.videos",
14        "com.sec.android.app.popupcalculator"
15    ],
16    "FloatApps": false
17}

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

1            fileObserver.observer = object : FileObserver(AppContext.getExternalFilesDir(null)?.absolutePath, ALL_EVENTS) {
2                override fun onEvent(event: Int, path: String?) {
3                    try {
4                        if (path?.equals(APP_LAUNCHER_CONFIGURATION_FILE, ignoreCase = true) == true) {
5                            when (event) {
6                                CREATE -> {
7                                    // notify that the file configuration has been created / modified
8                                    Logger.i("AppLauncher file based configuration monitoring event received (CREATE)")
9                                    EventBus.getDefault().post(AppLauncherEvents.AppLauncherFileConfigurationCreatedEvent)
10                                }
11
12                                MODIFY -> {
13                                    // notify that the file configuration has been created / modified
14                                    Logger.i("AppLauncher file based configuration monitoring event received (MODIFY)")
15                                    EventBus.getDefault().post(AppLauncherEvents.AppLauncherFileConfigurationModifiedEvent)
16                                }
17
18                                DELETE -> {
19                                    // notify that the file configuration has been removed
20                                    Logger.i("AppLauncher file based configuration monitoring event received (DELETE)")
21                                    EventBus.getDefault().post(AppLauncherEvents.AppLauncherFileConfigurationDeletedEvent)
22                                }
23                            }
24                        }
25                    } catch (ex: Exception) {
26                        Logger.w(ex)
27                    }
28                }
29            }
30            fileObserver.observer.startWatching()

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

لانچر اندروید

اساساً همه کار همین است و به این ترتیب این مقاله به پایان می‌رسد.

اگر این مطلب برای شما مفید بوده است، آموزش‌های زیر نیز به شما پیشنهاد می‌شوند:

==

بر اساس رای ۲ نفر
آیا این مطلب برای شما مفید بود؟
اگر بازخوردی درباره این مطلب دارید یا پرسشی دارید که بدون پاسخ مانده است، آن را از طریق بخش نظرات مطرح کنید.
منابع:
swlh
نظر شما چیست؟

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *