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

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

در این مقاله روی موضوعی تمرکز می‌کنیم که در بسیاری از اپلیکیشن‌ها اهمیت دارد و آن به دست آوردن موقعیت مکانی کاربر است. راه‌حلی که معرفی می‌شود، به طور کامل در پس‌زمینه عمل می‌کند و جالب‌تر از همه این است که برای نسخه‌های اندروید 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
1package com.rnbglocation.location;
2
3import android.content.BroadcastReceiver;
4import android.content.Context;
5import android.content.Intent;
6import android.content.IntentFilter;
7
8import androidx.core.content.ContextCompat;
9
10import com.facebook.react.bridge.Arguments;
11import com.facebook.react.bridge.ReactApplicationContext;
12import com.facebook.react.bridge.ReactContext;
13import com.facebook.react.bridge.ReactContextBaseJavaModule;
14import com.facebook.react.bridge.ReactMethod;
15import com.facebook.react.bridge.WritableMap;
16import com.facebook.react.modules.core.DeviceEventManagerModule;
17import com.google.gson.Gson;
18
19import java.util.HashMap;
20import java.util.Map;
21
22import javax.annotation.Nonnull;
23import javax.annotation.Nullable;
24
25import static com.rnbglocation.location.LocationForegroundService.LOCATION_EVENT_DATA_NAME;
26
27public class LocationModule extends ReactContextBaseJavaModule implements LocationEventReceiver, JSEventSender {
28    private static final String MODULE_NAME = "LocationManager";
29    private static final String CONST_JS_LOCATION_EVENT_NAME = "JS_LOCATION_EVENT_NAME";
30    private static final String CONST_JS_LOCATION_LAT = "JS_LOCATION_LAT_KEY";
31    private static final String CONST_JS_LOCATION_LON = "JS_LOCATION_LON_KEY";
32    private static final String CONST_JS_LOCATION_TIME = "JS_LOCATION_TIME_KEY";
33
34    private Context mContext;
35    private Intent mForegroundServiceIntent;
36    private BroadcastReceiver mEventReceiver;
37    private Gson mGson;
38
39    LocationModule(@Nonnull ReactApplicationContext reactContext) {
40        super(reactContext);
41        mContext = reactContext;
42        mForegroundServiceIntent = new Intent(mContext, LocationForegroundService.class);
43        mGson = new Gson();
44        createEventReceiver();
45        registerEventReceiver();
46    }
47
48    @ReactMethod
49    public void startBackgroundLocation() {
50        ContextCompat.startForegroundService(mContext, mForegroundServiceIntent);
51    }
52
53    @ReactMethod
54    public void stopBackgroundLocation() {
55        mContext.stopService(mForegroundServiceIntent);
56    }
57
58    @Nullable
59    @Override
60    public Map<String, Object> getConstants() {
61        final Map<String, Object> constants = new HashMap<>();
62        constants.put(CONST_JS_LOCATION_EVENT_NAME, LocationForegroundService.JS_LOCATION_EVENT_NAME);
63        constants.put(CONST_JS_LOCATION_LAT, LocationForegroundService.JS_LOCATION_LAT_KEY);
64        constants.put(CONST_JS_LOCATION_LON, LocationForegroundService.JS_LOCATION_LON_KEY);
65        constants.put(CONST_JS_LOCATION_TIME, LocationForegroundService.JS_LOCATION_TIME_KEY);
66        return constants;
67    }
68
69    @Nonnull
70    @Override
71    public String getName() {
72        return MODULE_NAME;
73    }
74
75    @Override
76    public void createEventReceiver() {
77        mEventReceiver = new BroadcastReceiver() {
78            @Override
79            public void onReceive(Context context, Intent intent) {
80                LocationCoordinates locationCoordinates = mGson.fromJson(
81                        intent.getStringExtra(LOCATION_EVENT_DATA_NAME), LocationCoordinates.class);
82                WritableMap eventData = Arguments.createMap();
83                eventData.putDouble(
84                        LocationForegroundService.JS_LOCATION_LAT_KEY,
85                        locationCoordinates.getLatitude());
86                eventData.putDouble(
87                        LocationForegroundService.JS_LOCATION_LON_KEY,
88                        locationCoordinates.getLongitude());
89                eventData.putDouble(
90                        LocationForegroundService.JS_LOCATION_TIME_KEY,
91                        locationCoordinates.getTimestamp());
92                // if you actually want to send events to JS side, it needs to be in the "Module"
93                sendEventToJS(getReactApplicationContext(),
94                        LocationForegroundService.JS_LOCATION_EVENT_NAME, eventData);
95            }
96        };
97    }
98
99    @Override
100    public void registerEventReceiver() {
101        IntentFilter eventFilter = new IntentFilter();
102        eventFilter.addAction(LocationForegroundService.LOCATION_EVENT_NAME);
103        mContext.registerReceiver(mEventReceiver, eventFilter);
104    }
105
106    @Override
107    public void sendEventToJS(ReactContext reactContext, String eventName, @Nullable WritableMap params) {
108        reactContext
109                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
110                .emit(eventName, params);
111    }
112}

این نخستین گام برای ساخت قابلیت Location مورد نظر ما محسوب می‌شود. این یک رویه معمول برای هر ماژول نیتیو که قرار است برای ری‌اکت نیتیو ساخته شود به حساب می‌آید. برای کسب اطلاعات بیشتر به مستندات (+) مراجعه کنید.

دو بخش مهم‌تر در این فایل شامل اینترفیس‌های LocationEventReceiver و JSEventSender هستند. این دو اینترفیس برای نمایش دو مسئولیت هر کلاسی که به تناظر پیاده‌سازی می‌کنند ایجاد شده‌اند:

  1. برای دریافت به‌روزرسانی‌های مکان از طریق یک BroadcastReceiver.
  2. برای ارسال رویدادها به سمت جاوا اسکریپت (با به‌روزرسانی‌های موقعیت).

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

این ماژول دو متد برای استفاده در سمت جاوا اسکریپت عرضه می‌کند:

  • startBackgroundLocation
  • stopBackgroundLocation

این دو متد مسئول اجرا و توقف سرویس پیش‌زمینه هستند.

در ادامه باید سرویس پیش‌زمینه را که مسئول تقریباً همه چیزها است کمی تحلیل کنیم:

  • فایل LocationForegroundService.java
1package com.rnbglocation.location;
2
3import android.app.AlarmManager;
4import android.app.Notification;
5import android.app.NotificationChannel;
6import android.app.NotificationManager;
7import android.app.PendingIntent;
8import android.app.Service;
9import android.content.BroadcastReceiver;
10import android.content.Context;
11import android.content.Intent;
12import android.content.IntentFilter;
13import android.os.Build;
14import android.os.IBinder;
15
16import androidx.annotation.Nullable;
17import androidx.core.app.NotificationCompat;
18
19import com.google.gson.Gson;
20import com.rnbglocation.MainActivity;
21
22public class LocationForegroundService extends Service implements LocationEventReceiver {
23    public static final String CHANNEL_ID = "ForegroundServiceChannel";
24    public static final int NOTIFICATION_ID = 1;
25    public static final String LOCATION_EVENT_NAME = "com.rnbglocation.LOCATION_INFO";
26    public static final String LOCATION_EVENT_DATA_NAME = "LocationData";
27    public static final int LOCATION_UPDATE_INTERVAL = 60000;
28    public static final String JS_LOCATION_LAT_KEY = "latitude";
29    public static final String JS_LOCATION_LON_KEY = "longitude";
30    public static final String JS_LOCATION_TIME_KEY = "timestamp";
31    public static final String JS_LOCATION_EVENT_NAME = "location_received";
32
33    private AlarmManager mAlarmManager;
34    private BroadcastReceiver mEventReceiver;
35    private PendingIntent mLocationBackgroundServicePendingIntent;
36    private Gson mGson;
37
38    @Override
39    public void onCreate() {
40        super.onCreate();
41        mAlarmManager = (AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
42        mGson = new Gson();
43        createEventReceiver();
44        registerEventReceiver();
45    }
46
47    @Override
48    public int onStartCommand(Intent intent, int flags, int startId) {
49        createNotificationChannel();
50        startForeground(NOTIFICATION_ID, createNotification());
51
52        createLocationPendingIntent();
53        mAlarmManager.setRepeating(
54            AlarmManager.RTC,
55            System.currentTimeMillis(),
56            LOCATION_UPDATE_INTERVAL,
57            mLocationBackgroundServicePendingIntent
58        );
59
60        return START_NOT_STICKY;
61    }
62
63    @Override
64    public void createEventReceiver() {
65        mEventReceiver = new BroadcastReceiver() {
66            @Override
67            public void onReceive(Context context, Intent intent) {
68                LocationCoordinates locationCoordinates = mGson.fromJson(
69                        intent.getStringExtra(LOCATION_EVENT_DATA_NAME), LocationCoordinates.class);
70                /*
71                TODO: do any kind of logic in here with the LocationCoordinates class
72                e.g. like a request to an API, etc --> all on the native side
73                */
74            }
75        };
76    }
77
78    @Override
79    public void registerEventReceiver() {
80        IntentFilter eventFilter = new IntentFilter();
81        eventFilter.addAction(LOCATION_EVENT_NAME);
82        registerReceiver(mEventReceiver, eventFilter);
83    }
84
85    @Nullable
86    @Override
87    public IBinder onBind(Intent intent) {
88        return null;
89    }
90
91    @Override
92    public void onDestroy() {
93        unregisterReceiver(mEventReceiver);
94        mAlarmManager.cancel(mLocationBackgroundServicePendingIntent);
95        stopSelf();
96        super.onDestroy();
97    }
98
99    private void createNotificationChannel() {
100        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
101            NotificationChannel serviceChannel = new NotificationChannel(
102                    CHANNEL_ID,
103                    "Foreground Service Channel",
104                    NotificationManager.IMPORTANCE_DEFAULT
105            );
106
107            NotificationManager manager = getSystemService(NotificationManager.class);
108            manager.createNotificationChannel(serviceChannel);
109        }
110    }
111
112    private Notification createNotification() {
113        Intent notificationIntent = new Intent(this, MainActivity.class);
114        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
115
116        return new NotificationCompat.Builder(this, CHANNEL_ID)
117                .setContentIntent(pendingIntent)
118                .build();
119    }
120
121    private void createLocationPendingIntent() {
122        Intent i = new Intent(getApplicationContext(), LocationBackgroundService.class);
123        mLocationBackgroundServicePendingIntent = PendingIntent.getService(getApplicationContext(), 1, i, PendingIntent.FLAG_UPDATE_CURRENT);
124    }
125}

ما در این سرویس، LocationEventReceiver را همانند ماژول پیاده‌سازی می‌کنیم. شاید فکر کنید این کار در چنین دموی ساده‌ای نوعی اضافه‌کاری محسوب می‌شود، اما هدف ما به دست آوردن سرویسی است که به‌روزرسانی‌های موقعیت کاربر را بداند، زیرا ممکن است بخواهید سرویس پیش‌زمینه شما، ‌وظایف پس‌زمینه متفاوتی را با کارهای مختلف بسته به مکان کاربر اجرا کند و یا این که ممکن است بخواهید کار نیتیو خاصی را مانند حفظ لوکال مختصات کاربر اجرا کنید.

متد onStartCommand مبنای سرویس ما است و مسئولیت کارهای زیر را بر عهده دارد:

  • ایجاد کانال نوتیفیکیشن (یک گام ضروری برای نسخه‌های 8 و بالاتر اندروید)
  • ایجاد نوتیفیکیشنی که برای کاربران نمایش می‌یابد.
  • فراخوانی متد startForeground که برای آغاز این سرویس به صورت سرویس پیش‌زمینه ضروری است.
  • استفاده از AlarmManager برای زمان‌بندی وظیفه پس‌زمینه جهت واکشی مکان کاربر در بازه‌های 1 دقیقه (البته این بازه کاملاً قابلیت سفارشی‌سازی دارد.)

فراموش نکنید که اپلیکیشن شما باید بتواند از سرویس‌های پیش‌زمینه استفاده کند و از این رو باید مجوزهایی را در فایل manifest به صورت زیر اعلان کند:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

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

  • فایل LocationBackgroundService.java
1package com.rnbglocation.location;
2
3import android.annotation.SuppressLint;
4import android.app.IntentService;
5import android.content.Intent;
6import android.location.Location;
7import android.os.Handler;
8
9import androidx.annotation.Nullable;
10
11import com.google.android.gms.location.FusedLocationProviderClient;
12import com.google.android.gms.location.LocationCallback;
13import com.google.android.gms.location.LocationRequest;
14import com.google.android.gms.location.LocationResult;
15import com.google.android.gms.location.LocationServices;
16import com.google.gson.Gson;
17
18import java.util.Date;
19
20public class LocationBackgroundService extends IntentService {
21    private FusedLocationProviderClient mFusedLocationClient;
22    private LocationCallback mLocationCallback;
23    private Gson mGson;
24
25    public LocationBackgroundService() {
26        super(LocationForegroundService.class.getName());
27        mGson = new Gson();
28    }
29
30    @SuppressLint("MissingPermission")
31    @Override
32    protected void onHandleIntent(@Nullable Intent intent) {
33        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(getApplicationContext());
34        mLocationCallback = createLocationRequestCallback();
35
36        LocationRequest locationRequest = LocationRequest.create()
37                .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY)
38                .setInterval(0)
39                .setFastestInterval(0);
40
41        new Handler(getMainLooper()).post(() -> mFusedLocationClient.requestLocationUpdates(locationRequest, mLocationCallback, null));
42    }
43
44    private LocationCallback createLocationRequestCallback() {
45        return new LocationCallback() {
46            @Override
47            public void onLocationResult(LocationResult locationResult) {
48                if (locationResult == null) {
49                    return;
50                }
51                for (Location location : locationResult.getLocations()) {
52                    LocationCoordinates locationCoordinates = createCoordinates(location.getLatitude(), location.getLongitude());
53                    broadcastLocationReceived(locationCoordinates);
54                    mFusedLocationClient.removeLocationUpdates(mLocationCallback);
55                }
56            }
57        };
58    }
59
60    private LocationCoordinates createCoordinates(double latitude, double longitude) {
61        return new LocationCoordinates()
62                .setLatitude(latitude)
63                .setLongitude(longitude)
64                .setTimestamp(new Date().getTime());
65    }
66
67    private void broadcastLocationReceived(LocationCoordinates locationCoordinates) {
68        Intent eventIntent = new Intent(LocationForegroundService.LOCATION_EVENT_NAME);
69        eventIntent.putExtra(LocationForegroundService.LOCATION_EVENT_DATA_NAME, mGson.toJson(locationCoordinates));
70        getApplicationContext().sendBroadcast(eventIntent);
71    }
72}

این سرویس کاملاً ساده است، ‌این سرویس از 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 تغییرهای ناسازگار به دلیل حذف برخی عناصر مطرح شده است.

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

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

==

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

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