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


در این مقاله روی موضوعی تمرکز میکنیم که در بسیاری از اپلیکیشنها اهمیت دارد و آن به دست آوردن موقعیت مکانی کاربر است. راهحلی که معرفی میشود، به طور کامل در پسزمینه عمل میکند و جالبتر از همه این است که برای نسخههای اندروید 4.4 یعنی SDK 19 به بالا تا جدیدترین نسخههای اندروید یعنی SDK 27 عمل میکند. با ما همراه باشید تا با روش گرفتن مکان کاربر در پس زمینه در اپلیکیشن ری اکت نیتیو آشنا شوید.
هدف این راهحل ردگیری مداوم کاربر نیست، بلکه میخواهیم موقعیت کاربر را در بازههای کوتاه (مثلاً هر پنج یا ده دقیقه) ردگیری کنیم، اما امکان دریافت موقعیت کاربر در بازههای کمتر از این مدت یعنی برای مثال هر دقیقه یک بار نیز وجود دارد.
اندروید 8 و خصوصیات آن
گوگل در نسخه 8 اندروید برخی تغییرات خاص از جمله در مورد سرویسهای پسزمینه و موقعیت پسزمینه معرفی کرد. انگیزه اصلی گوگل برای اعمال این تغییرها مسئله توان و مصرف منابع دستگاهها از سوی اپلیکیشنهای شخص ثالث بوده است. به بیان دیگر آنها میخواستند کنترل بهتری روی محیط سیستم عامل داشته باشند و آزادی کنترلشدهتری برای توسعه اپلیکیشنها در اختیار توسعهدهندگان قرار دهند. این بدان معنی است که دیگر نمیتوان وظایف پسزمینه را آزادانه چنان که در نسخههای قبلی اندروید امکانپذیر بود اجرا کرد.
این تغییر جهت عرضه تجربه کاربری بهتر به بازار هدف صورت گرفته است. در هر حال، ما نمیخواهیم اپلیکیشنی داشته باشیم که منابع گوشی هوشمند را به هدر بدهد، زیرا وظایف پسزمینه زیادی اجرا میکند یا نتوانسته است کد خود را به خوبی مدیریت کند و نه تنها منابع محاسباتی بلکه حافظه را نیز اشغال میکند.
گوگل با اعمال این محدودیتهای پسزمینه، در صفحه مربوط به دلایل اعمال این محدودیتها چنین توضیح میدهد:
«در اغلب موارد اپلیکیشنها میتوانند با استفاده از Job-هایی به نام JobScheduler این محدودیتها را دور بزنند. این رویکرد به اپلیکیشن امکان میدهد که کار خود را در زمانی که اپلیکیشن در حال اجرا نیست، مدیریت کنند، اما همچنان به سیستم اجازه میدهد این کارها را طوری زمانبندی کند که تأثیر سوئی روی تجربه کاربر نداشته باشد.»
این پیشنهاد کاملاً معقولی به نظر میرسد، اما چند مشکل وجود دارد.
- مشکل شماره 1: JobScheduler در SDK 21 یعنی در اندروید 5 اضافه شده است؛ از این رو در صورتی که نسخه 4.4 را هدفگیری کرده باشید، گزینه مناسبی محسوب نمیشود.
- مشکل شماره 2: حتی اگر اندروید 5 و بالاتر را هدفگیری کنیم، همچنان مشکل کوچکی وجود خواهد داشت. کارهایی که برای اجرا از سوی JobScheduler زمانبندی میشوند، تنها در بازههای 15 دقیقهای قابل اجرا هستند، اما میخواهیم در اپلیکیشن ریاکت نیتیو خود، راهحلی داشته باشیم که مکان کاربر را به صورت لحظهای داشته باشیم. زمانی که در مورد جابجایی کاربر صحبت میکنیم، 15 دقیقه بازه بزرگی محسوب میشود، بنابراین باید روش متفاوتی برای گرفتن نتیجه موردنظر خودمان طراحی کنیم.
هدف راهحل
آن چه میخواهیم در این راهنما بسازیم تنها بخش کوچکی از یک پروژه بزرگ است که برای ردگیری رانندگان کامیون در زمان انجام وظیفه تحویل کالاها طراحی شده است. هدف این پروژه برای شرکت آن است که راننده کامیون استخدام کند و مشتریها نیز باید بدانند که رانندگان و در نتیجه کالایشان کجا هستند. این قابلیت چند الزام دارد:
- ردگیری میتواند بازهای بین 5 تا 10 دقیقه داشته باشد.
- ردگیری باید زمانی که اپلیکیشن در پسزمینه است، فعال باشد.
- مصرف باتری باید در کمترین حد ممکن بماند.
- نشت حافظه نباید وجود داشته باشد و مصرف حافظه باید در کمترین حد ممکن بماند.
با توجه به خطوط راهنمای فوق، قطعاً باید به بررسی API-های سرویسهای پسزمینه در اندروید بپردازیم. نخستین گزینه منطقی بررسی WorkManager API است.
WorkManager API
این API در نگاه نخست برای کاربرد مورد نظر ما مناسب به نظر میرسد، زیرا حتی از دستگاههای با اندروید 4.0 نیز پشتیبانی میکند و میتوان وظایف پسزمینه را حتی در زمانی که دستگاه ریاستارت میشود ایجاد کرد. این API کاملاً منعطف است چون کارهای زیادی انجام میدهد، اما یک چالش بزرگ وجود دارد و آن این که این API یک وابستگی به AndroidX است.
AndroidX یک کتابخانه پشتیبانی جدید در اکوسیستم اندروید است که API-های موجود را منسجم میسازد و توان جدیدی از طریق پکیجهای نو در اختیار توسعهدهندگان برای استفاده در اپلیکیشنها قرار میدهد. ترفند کار به این صورت است که شما یا از وابستگی تحت AndroidX و یا پیش از AndroidX استفاده میکنید. امکان ترکیب این دو مورد وجود ندارد چون در زمان ایمپورت کردن وابستگیهایی که در هر دو کتابخانه پشتیبانی وجود دارند، تصادم نام رخ میدهد. API-های زیادی با این مشکل مواجه هستند.
نکته دیگر که باید اشاره کنیم این است که پروژه اصلی که این راهحل مطرح شده در مقاله حاضر از آن منشأ یافته است یک اپلیکیشن سازمانی است که 2 سال کار توسعه در ریاکت نیتیو روی آن صورت گرفته است و وابستگیهای زیادی از قبل در آن نصب شدهاند.
بنابراین انتقال همه وابستگیها به AndroidX امکانپذیر نیست، چون در این صورت باید همه وابستگیها در اپلیکیشن و همه وابستگیهای پروژه ریاکت نیتیو در پروژه نیز تغییر یابند.
به طور خلاصه این API باید مستثنا شود، چون در این مورد خاص نمیتواند برای کاری که طراحی شده است مورد استفاده قرار گیرد.
AlarmManager API
یک API دیگر به نام AlarmManager API نیز وجود دارد که امکان زمانبندی سرویسهای پسزمینه را برای اجرا در بازههای خاصی که از طرف ما تعریف میشوند با کمینه بازه زمانی 1 دقیقه فراهم میکند.
مشکل اصلی این API آن است که اگر اپلیکیشن خودش در پسزمینه باشد، امکان اجرای سرویسهای پسزمینه را نخواهد داشت و این حالت دست کم در اندروید 8 و بالاتر برقرار است. بنابراین اگر بخواهیم مطمئن شویم که اپلیکیشن ما همچنان در حال ردگیری کاربر است یا نه، در صورتی که یک اپلیکیشن دیگر مانند یک پیامرسان در پیشزمینه باز باشد، میسر نخواهد بود.
با این که این API مشکل ما را به طور کامل حل نمیکند، اما به هر حال یک API قدرتمند محسوب میشود و یک نخ جدید برای اجرای سرویسهای ما ایجاد میکند و میتوانیم هشدارهایی که زمانبندی کردهایم را لغو کنیم و به این ترتیب نخهای تخصیصیافته را ببندیم.
ورود به سرویسهای پیشزمینه
ما با استفاده از سرویسهای پیشزمینه میتوانیم حتی در صورتی که اپلیکیشن ما در پیشزمینه نباشد، یک سرویس را به صورت مداوم روی پسزمینه در حال اجرا نگه داریم. دلیل این که میتوانیم این کار را انجام دهیم آن است که اپلیکیشن ما یک نوتیفیکیشن «غیر قابل بستن» (undismissable) خواهد داشت که به کاربر اطلاع میدهد اپلیکیشن همچنان در حال اجرا است، هر چند وی نتواند فعلاً آن را ببیند. گوگل به این جهت که اپلیکیشن ما به کاربر اطلاع میدهد که اپلیکیشن همچنان در حال اجرا است، به آن امکان میدهد که عملاً هر وظیفه پسزمینه که میخواهد را اجرا کند که در مورد مثال مورد نظر ما بسیار عالی است.
بنابراین راهحل ما این است که از یک سرویس پیشزمینه به صورت وهلهای از AlarmManager API، یک IntentService و یک BroadcastReceiver برای دریافت بهروزرسانیهای مکان کاربر و ذخیره لوکال آنها استفاده کنیم.
راهحل
دموی نهایی ما که قابلیت بازتولید دارد را در این صفحه (+) میتوانید ملاحظه کنید و این مثال بسیار کوچکی از چیزی است که میخواهیم بسازیم.
قبل از هر چیز باید اشاره کنیم که راهحل ما بر مبنای مقدار زیادی کد نیتیو جاوا است، بنابراین از این جا به بعد این راهحل را تنها در صورتی میتوانید پیادهسازی کنید که یک اپلیکیشن ریاکت نیتیو به صورت ejected داشته باشید که چیزی شبیه مواقعی است که میخواهید یک پروژه ریاکت نیتیو با دستور زیر بسازید:
react-native init my-project
برای استفاده از امکان موقعیتیابی یک گوشی هوشمند باید درخواست مجوز این کار را ارائه کنید. این بخش در سمت جاوا اسکریپت مدیریت میشود، اما چیز خاصی هم نیست و صرفاً با نگاه کردن گذرا به فایلها متوجه روند کار میشوید. ابتدا ماژول نیتیو خود را میسازیم تا بتوانیم کد نیتیو خود را از سمت جاوا اسکریپت آغاز کنیم.
- فایل LocationModule.java
این نخستین گام برای ساخت قابلیت Location مورد نظر ما محسوب میشود. این یک رویه معمول برای هر ماژول نیتیو که قرار است برای ریاکت نیتیو ساخته شود به حساب میآید. برای کسب اطلاعات بیشتر به مستندات (+) مراجعه کنید.
دو بخش مهمتر در این فایل شامل اینترفیسهای LocationEventReceiver و JSEventSender هستند. این دو اینترفیس برای نمایش دو مسئولیت هر کلاسی که به تناظر پیادهسازی میکنند ایجاد شدهاند:
- برای دریافت بهروزرسانیهای مکان از طریق یک BroadcastReceiver.
- برای ارسال رویدادها به سمت جاوا اسکریپت (با بهروزرسانیهای موقعیت).
لازم است اشاره کنیم که اگر بخواهید رویدادها را به سمت جاوا اسکریپت ارسال کنید، باید ارسال عملی رویدادها را در ماژولهای ریاکت خودتان انجام دهید، چون ReactApplicationContext به این منظور لازم است. اگر نمیخواهید از این گزینه استفاده کنید، اینترفیس JSEventSender میتواند مستقیماً از سوی سرویس پیشزمینه پیادهسازی شود.
این ماژول دو متد برای استفاده در سمت جاوا اسکریپت عرضه میکند:
- startBackgroundLocation
- stopBackgroundLocation
این دو متد مسئول اجرا و توقف سرویس پیشزمینه هستند.
در ادامه باید سرویس پیشزمینه را که مسئول تقریباً همه چیزها است کمی تحلیل کنیم:
- فایل LocationForegroundService.java
ما در این سرویس، LocationEventReceiver را همانند ماژول پیادهسازی میکنیم. شاید فکر کنید این کار در چنین دموی سادهای نوعی اضافهکاری محسوب میشود، اما هدف ما به دست آوردن سرویسی است که بهروزرسانیهای موقعیت کاربر را بداند، زیرا ممکن است بخواهید سرویس پیشزمینه شما، وظایف پسزمینه متفاوتی را با کارهای مختلف بسته به مکان کاربر اجرا کند و یا این که ممکن است بخواهید کار نیتیو خاصی را مانند حفظ لوکال مختصات کاربر اجرا کنید.
متد onStartCommand مبنای سرویس ما است و مسئولیت کارهای زیر را بر عهده دارد:
- ایجاد کانال نوتیفیکیشن (یک گام ضروری برای نسخههای 8 و بالاتر اندروید)
- ایجاد نوتیفیکیشنی که برای کاربران نمایش مییابد.
- فراخوانی متد startForeground که برای آغاز این سرویس به صورت سرویس پیشزمینه ضروری است.
- استفاده از AlarmManager برای زمانبندی وظیفه پسزمینه جهت واکشی مکان کاربر در بازههای 1 دقیقه (البته این بازه کاملاً قابلیت سفارشیسازی دارد.)
فراموش نکنید که اپلیکیشن شما باید بتواند از سرویسهای پیشزمینه استفاده کند و از این رو باید مجوزهایی را در فایل manifest به صورت زیر اعلان کند:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
واکشی مکان واقعی و مدیریت آن در یک وظیفه پسزمینه روی یک نخ متفاوت از نخ اصلی انجام میگیرد. سرویس پسزمینه زیر را بررسی کنید:
- فایل LocationBackgroundService.java
این سرویس کاملاً ساده است، این سرویس از FusedLocationProviderClient برای واکشی موقعیت کاربر با جزییات مشخص شده در LocationRequest استفاده میکند.
زمانی که ارائهدهنده موقعیت، کار واکشی مکان کاربر را به پایان میبرد، این اطلاعات مکانی را از طریق سازوکار broadcast خود به کلاس LocationCoordinates ارسال میکنیم تا هر کس که میخواهد به این مختصات گوش دهد از آنها استفاده کند.
این فایلها هسته اصلی راهحل ما را تشکیل میدهند و با استفاده از آنها میتوانید مکان کاربر را روی تقریباً همه گوشیهای هوشمند اندروید در یک راهحل ریاکت نیتیو به شیوهای با کمترین مصرف منابع و باتری کنترل کنید.
نکات نهایی
برای پشتیبانی از اندروید 4.4 چندین گام دیگر علاوه بر موارد مطرح شده فوق مورد نیاز است که در دمویی که لینک آن را ارائه کردیم وجود دارند، اما اشاره به آنها را چندان مفید نیافتیم و لذا توضیحی در مورد آن ارائه نشده است.
در فایل app.gradle باید یک وابستگی جدید در مورد فرایند build کردن APK اضافه کنیم که به صورت زیر است:
implementation “com.android.support:multidex:1.0.3”
علاوه بر آن در همین فایل باید نیاز به multi-dexing را اعلان کنیم. به این منظور از گزاره multiDexEnabled true زیر بخش defaultConfig پیکربندی استفاده میکنیم.
بدین ترتیب اپلیکیشن میداند که باید این مورد را لحاظ کند، اما برای این که همه چیز به خوبی کار کند، باید کد زیر را نیز به فایل MainApplication.java اضافه کنیم:
… MainApplication extends MultiDexApplication …
بدین ترتیب با این پیکربندی مطمئن میشویم که این اپلیکیشن اینک به طور کامل با اندروید 4.4 سازگار است.
سخن پایانی
ریاکت نیتیو یک پلتفرم عالی برای استفاده توسعهدهندگان است و قطعاً کارها را برای توسعهدهندگانی که به طور عمده دارای تجربه توسعه وب هستند، تسریع میکند. با این حال، موارد زیادی وجود دارند که نیازمند دانش نیتیو در سمت اندروید یا iOS هستند و این دمو که در این مقاله مطرح کردیم نمونه خوبی از این موضوع است. یک نکته مهم که در زمان انتخاب فناوری توسعه باید توجه داشته باشیم به صورت زیر است:
در زمان انتخاب فناوری مورد نظر برای توسعه اپلیکیشن به خصوص وقتی در یک محیط سازمانی هستید، باید بسیار مراقب باشید. ریاکت نیتیو به هیچ وجه یک فناوری پایدار محسوب نمیشود و در نسخههای مختلف با «تغییرهای ناسازگار» (breaking changes) با نسخههای قبل مواجه بودهایم. برای مثال به فهرست زیر توجه کنید:
- در نسخه 0.60 تغییرهای ناسازگار برای برخی کاربران مطرح شده است.
- در نسخه 0.59 تغییرهای ناسازگار برای کاربران اندروید مطرح شده است.
- در نسخه 0.58 تغییرهای ناسازگار برای برخی کامپوننتها مطرح شده است.
- در نسخه 0.57.2 تغییرهای ناسازگار به دلیل حذف برخی عناصر مطرح شده است.
با در نظر گرفتن این نکته، میبینیم که تغییرهای ناسازگاری که با نسخههای قبل ایجاد میشوند ممکن است مشکلاتی برای نگهداری اپلیکیشن در آینده و در زمان نیاز به ارتقای اپلیکیشن برای قابلیتهای جدید، بهینهسازی یا و اصلاح باگ پدید آورند. در هر حال در زمان بررسی این فناوری در برابر فناوریهای قدیمیتر مختلف، باید سود و زیان آن را به خوبی بررسی کنید، به خصوص اگر میخواهید چیزی بسازید که نگهداری بلندمدتی خواهد داشت و یا تیم متخصص یا بزرگی ندارید که درگیر نگهداری و ساخت محصول باشند، این بررسی مجدد اهمیت دوچندان مییابد.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- آموزش مقدماتی فریمورک React Native
- ساخت اپلیکیشن ساده آب و هوا با React Native و Expo — از صفر تا صد
- چگونه با React Native اپلیکیشن اندرویدی بنویسیم؟ — به زبان ساده
==