מפת המעבדות · מעבדת סירוב והרשאות
בסוף המעבדה המשתמש בוחר מצא מיקום מקורב או שדרג לדיוק גבוה. האפליקציה מבקשת הרשאת foreground רק בלחיצה, מקבלת נקודה אחת, מציגה רדיוס דיוק במטרים, ומעבירה את הנקודה לאפליקציית מפה רק בלחיצה נפרדת. סירוב, ספק מיקום כבוי, היעדר fix והיעדר אפליקציית מפה הם מצבים גלויים.
בסיס ההשוואה בפרויקט topics הוא master, וענף התוצאה הוא codex/location-maps. זהו תרחיש של הצגת מקומי על מפה, ללא מעקב רציף וללא שרת. אין צורך במפתח Google Maps או בהרשאת רקע לתוצאה הזו.
איזו רמת דיוק נחוצה?
| צורך משתמש | הרשאה | דוגמת מוצר | עלות פרטיות |
|---|---|---|---|
| למצוא שירותים בעיר | ACCESS_COARSE_LOCATION |
רשימת ספריות בסביבה | אזור כללי |
| להגיע לנקודה מדויקת | ACCESS_FINE_LOCATION יחד עם coarse |
מיקום כניסה לבניין | נקודה מדויקת יותר |
לפי מדריך הרשאות המיקום, Android 12+ מאפשר למשתמש להעניק Approximate גם כשמבקשים דיוק מלא. לכן, בעת שדרוג, מבקשים FINE ו־COARSE באותה בקשה וממשיכים לעבוד גם אם ניתנה רק COARSE. אל תפרשו getAccuracy() כהבטחה: זה אומדן רדיוס, ותנאי שטח/רשת/מכשיר משפיעים עליו. שינוי בהגדרת הדיוק עלול להפעיל מחדש את תהליך האפליקציה.
1. מכריזים רק על foreground
ב־app > manifests > AndroidManifest.xml, לפני <application>, הוסיפו:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
אין ACCESS_BACKGROUND_LOCATION: התרחיש זקוק לנקודה אחת כשהמסך פתוח. הוספת הרשאת רקע רק כדי להשלים רשימת APIs תהיה בקשה גדולה יותר ללא צורך מוצרי.
2. בונים מסך עם שלוש החלטות
ב־app > res > layout > activity_main.xml החליפו את Hello World! ב־LinearLayout אנכי constrained ל־top/start/end של ההורה, padding=24dp. ילדיו: TextView הסבר @string/location_intro בגודל 20sp; כפתורי approximate, precise, open_map; ו־TextView id=status עם accessibilityLiveRegion="polite". כפתור המפה מתחיל עם enabled="false" כי אין נקודה להציג. כל הילדים ברוחב match_parent ובגובה wrap_content. השאירו את טיפול ה־window insets שכבר קיים.
ב־app > res > values > strings.xml הוסיפו טקסט למצבי idle, denied, provider off, locating, no fix, no map app ותוצאת מיקום. בענף התוצאה ה־location_result משתמש ב־%2$.5f/%3$.5f לקואורדינטות וב־%4$.0f לרדיוס במטרים. הצגת הרדיוס עוזרת להסביר מדוע נקודה מקורבת אינה מקום מדויק.
3. מבקשים הרשאה ברגע הפעולה
ב־MainActivity.java הוסיפו את השדות ואת שני ה־contracts. הראשון מבקש רק coarse; השני מבקש את שתיהן יחד:
private CancellationSignal pending;
private Location shownLocation;
private boolean preciseRequested;
private final ActivityResultLauncher<String> askCoarse = registerForActivityResult(
new ActivityResultContracts.RequestPermission(), granted -> {
if (granted) locate();
else binding.status.setText(R.string.location_denied);
});
private final ActivityResultLauncher<String[]> askPrecise = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(), grants -> {
if (Boolean.TRUE.equals(grants.get(Manifest.permission.ACCESS_COARSE_LOCATION))) locate();
else binding.status.setText(R.string.location_denied);
});
חברו את הכפתורים ב־onCreate:
binding.approximate.setOnClickListener(v -> {
preciseRequested = false;
if (has(Manifest.permission.ACCESS_COARSE_LOCATION)) locate();
else askCoarse.launch(Manifest.permission.ACCESS_COARSE_LOCATION);
});
binding.precise.setOnClickListener(v -> {
preciseRequested = true;
if (has(Manifest.permission.ACCESS_FINE_LOCATION)) locate();
else askPrecise.launch(new String[]{Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION});
});
binding.openMap.setOnClickListener(v -> openMap());
has(permission) בענף התוצאה קוראת ל־ContextCompat.checkSelfPermission. אם המשתמש בחר מקורב בחלון השדרוג, ה־callback מקבל coarse ו־locate() בודקת מחדש אם fine באמת קיימת. ה־UI אינו מניח שהמשתמש בחר באפשרות שרצינו.
4. מבקשים נקודה אחת ומפסיקים בזמן
ב־locate() בטלו pending קודם, בדקו איזו הרשאה ניתנה, בדקו שספק GPS_PROVIDER פעיל, ואז קראו ל־LocationManager.getCurrentLocation(provider, pending, getMainExecutor(), callback). ב־callback, null הוא אין fix; נקודה אמיתית נשמרת רק ב־shownLocation, ומוצגים latitude, longitude ו־getAccuracy(). רק אז מאפשרים את כפתור המפה. עטפו את הקריאה ב־try/catch (SecurityException) למקרה שההרשאה השתנתה בין בדיקה לשימוש.
כל בקשה מגדילה requestGeneration וה־callback מתעלם מתוצאה שאינה שייכת לדור הנוכחי; התחלת בקשה גם מנקה את הנקודה הקודמת ומשביתה את כפתור המפה כדי שלא תישלח נקודה ישנה. onStop מגדילה את הדור ומבטלת את הבקשה. שמרו את preciseRequested ב־onSaveInstanceState, משום שתשובת ההרשאה עלולה להגיע אחרי יצירה מחדש של ה־Activity.
boolean precise = preciseRequested && has(Manifest.permission.ACCESS_FINE_LOCATION);
String provider = LocationManager.GPS_PROVIDER;
if (!manager.isProviderEnabled(provider)) {
binding.status.setText(R.string.location_off);
return;
}
pending = new CancellationSignal();
manager.getCurrentLocation(provider, pending, getMainExecutor(), location -> {
pending = null;
if (location == null) { binding.status.setText(R.string.no_fix); return; }
shownLocation = location;
binding.status.setText(getString(R.string.location_result,
precise ? "precise" : "approximate", location.getLatitude(),
location.getLongitude(), location.getAccuracy()));
binding.openMap.setEnabled(true);
});
זהו קטע ממוקד; בענף התוצאה נמצאים השיטה המלאה וה־try/catch. השתמשנו בספק GPS כדי להזריק נקודה באמולטור באופן שחוזר על עצמו. הרשאת coarse עדיין מחזירה נקודה מגושמת: בבדיקה נמדד רדיוס 2000m מול 5m בהרשאת fine. GPS עשוי להיות איטי או לא לתת fix בתוך מבנה. במוצר רב־מכשירי, שקלו ספק fused עם התאמת דיוק/הספק, fallback ו־timeout; תיעוד LocationManager מתאר גם את מגבלות getCurrentLocation. בקשה חד־פעמית וחסימת בקשה כשהמסך נסגר חוסכות עבודה ביחס להאזנה רציפה.
@Override
protected void onStop() {
if (pending != null) {
pending.cancel();
pending = null;
}
super.onStop();
}
5. מעבירים נקודה למפה רק אחרי לחיצה
openMap() בונה URI מסוג geo:latitude,longitude?z=14 עם Locale.US, פותחת Intent.ACTION_VIEW, ומציגה הודעה אם אין אפליקציה תומכת (ActivityNotFoundException). שימוש ב־Locale.US משאיר נקודה עשרונית ב־URI גם בטלפון ששפתו משתמשת בסימן אחר. תיעוד ה־geo intent מפרט את הפורמט. מסירת הנקודה לאפליקציית מפה היא חשיפת מידע לאפליקציה נוספת, לכן היא פעולה נפרדת שמובנת למשתמש.
בדיקות ותכנון מחדש
- באמולטור קבעו מיקום דרך Extended Controls > Location או
adb emu geo fix 34.7818 32.0853(longitude קודם, latitude אחר כך). לחצו על מקורב ואשרו. בדקו שהרדיוס גדול ושכפתור המפה נפתח רק אחרי fix. - שדרגו למדויק. בבדיקת המעבדה התקבלו
32.08530, 34.78180עם רדיוס5m; המקורב החזיר נקודה מטושטשת ורדיוס2000m. המפה נפתחה ביישום Maps באמולטור. - בטלו הרשאה, כבו Location במכשיר, נסו שוב בלי לקבוע נקודה ובדקו הודעות שונות. התחילו בקשה ואז עברו למסך אחר:
onStopמבטל אותה. - הסבירו מתי יעד משתמש מצדיק דיוק גבוה, ומתי רשימת מקומות קרובים יכולה לעבוד עם coarse. תכננו שדרוג ל־fused provider, timeout ושחזור state אחרי סיבוב לפני שימוש במוצר אמיתי.