סקירה כללית על חיישנים

ברוב המכשירים עם Android יש חיישנים מובנים למדידת תנועה, כיוון ותנאי סביבה שונים. החיישנים האלה יכולים לספק נתונים גולמיים ברמת דיוק גבוהה, והם שימושיים אם רוצים לעקוב אחרי תנועה או מיקום תלת-ממדיים של מכשיר, או אחרי שינויים בסביבה הקרובה למכשיר. לדוגמה, משחק יכול לעקוב אחרי קריאות של חיישן הכבידה במכשיר כדי להסיק תנועות וגינונים מורכבים של המשתמש, כמו הטיה, רעד, סיבוב או תנועה קדימה ואחורה. באופן דומה, אפליקציית מזג אוויר עשויה להשתמש בחיישן הטמפרטורה ובחיישן הלחות של המכשיר כדי לחשב את נקודת הטל ולדווח עליה, או שאפליקציית נסיעות עשויה להשתמש בחיישן השדה הגיאומגנטי ובמד התאוצה כדי לדווח על כיוון מצפן.

פלטפורמת Android תומכת בשלוש קטגוריות רחבות של חיישנים:

  • חיישני תנועה

    החיישנים האלה מודדים את כוחות התאוצה ואת כוחות הסיבוב בשלושה צירים. הקטגוריה הזו כוללת מדי תאוצה, חיישני כוח כבידה, ג'ירוסקופים וחיישנים וקטוריים סיבוביים.

  • חיישנים סביבתיים

    החיישנים האלה מודדים פרמטרים סביבתיים שונים, כמו טמפרטורה ולחץ של האוויר בסביבה, תאורה ולחות. הקטגוריה הזו כוללת ברומטר, פוטומטר ותרמומטרים.

  • חיישני מיקום

    החיישנים האלה מודדים את המיקום הפיזי של המכשיר. הקטגוריה הזו כוללת חיישני כיוון ומגנטומטרים.

אפשר לגשת לחיישנים שזמינים במכשיר ולקבל נתוני חיישנים גולמיים באמצעות מסגרת החיישנים של Android. מסגרת החיישן מספקת כמה כיתות וממשקים שיעזרו לכם לבצע מגוון רחב של משימות שקשורות לחיישן. לדוגמה, אפשר להשתמש במסגרת החיישנים כדי:

  • איך בודקים אילו חיישנים זמינים במכשיר.
  • קביעת היכולות של חיישן ספציפי, למשל הטווח המקסימלי, היצרן, דרישות החשמל והרזולוציה.
  • איסוף נתוני חיישנים גולמיים והגדרת הקצב המינימלי שבו אתם אוספים נתוני חיישנים.
  • רישום וביטול רישום של פונקציות שמאזינות לאירועים של חיישנים ומנטרות שינויים בחיישנים.

בנושא הזה נספק סקירה כללית על החיישנים שזמינים בפלטפורמת Android. בנוסף, מופיעה בו מבוא למסגרת החיישן.

מבוא לחיישנים

מסגרת החיישנים של Android מאפשרת לכם לגשת למגוון סוגים של חיישנים. חלק מהחיישנים האלה מבוססי חומרה וחלקם מבוססי תוכנה. חיישנים מבוססי-חומרה הם רכיבים פיזיים שמוטמעים במכשיר נייד או בטאבלט. הם מפיקים את הנתונים שלהם על ידי מדידה ישירה של מאפייני סביבה ספציפיים, כמו תאוצה, עוצמת השדה הגיאומגנטי או שינוי זווית. חיישנים מבוססי-תוכנה הם לא מכשירים פיזיים, אבל הם מחקים חיישנים מבוססי-חומרה. חיישנים מבוססי תוכנה מפיקים את הנתונים שלהם מחיישנים באחד או יותר מהחיישנים מבוססי החומרה, ולפעמים נקראים 'חיישנים וירטואליים' או 'חיישנים סינתטיים'. חיישן התאוצה הליניארית וחישן הכבידה הם דוגמאות לחיישנים מבוססי-תוכנה. בטבלה 1 מפורט סיכום של החיישנים שנתמכים בפלטפורמת Android.

מעט מכשירים מבוססי Android כוללים את כל סוגי החיישנים. לדוגמה, רוב הטלפונים והטאבלטים כוללים מד תאוצה ומגנטומטר, אבל פחות מכשירים כוללים ברומטר או מדחום. בנוסף, למכשיר יכול להיות יותר ממכשיר אחד מסוג נתון. לדוגמה, במכשיר יכולים להיות שני חיישני כבידה, לכל אחד מהם טווח שונה.

טבלה 1. סוגי החיישנים שנתמכים בפלטפורמת Android.

חיישן סוג תיאור שימושים נפוצים
TYPE_ACCELEROMETER חומרה מדידת כוח התאוצה ב-m/s2 שמופעל על מכשיר בכל שלושת הצירים הפיזיים (x,‏ y ו-z), כולל כוח הכבידה. זיהוי תנועה (רעידות, הטיות וכו').
TYPE_AMBIENT_TEMPERATURE חומרה מדידת טמפרטורת החדר בסביבה במעלות צלזיוס (°C). ראו הערה בהמשך. מעקב אחרי טמפרטורות האוויר.
TYPE_GRAVITY תוכנה או חומרה מדידה של כוח הכבידה ב-m/s2 שמופעל על המכשיר בכל שלושת הצירים הפיזיים (x, y, z). זיהוי תנועה (ניעור, הטיה וכו').
TYPE_GYROSCOPE חומרה מדידת מהירות הסיבוב של המכשיר ברדיאנים לשנייה סביב כל אחד משלושת הצירים הפיזיים (x,‏ y ו-z). זיהוי סיבוב (סיבוב, סיבוב וכו').
TYPE_LIGHT חומרה מדידת רמת התאורה הסביבתית (הארה) ב-lx. שליטה בבהירות המסך.
TYPE_LINEAR_ACCELERATION תוכנה או חומרה מדידת כוח התאוצה ב-m/s2 שמופעל על מכשיר בכל שלושת הצירים הפיזיים (x,‏ y ו-z), לא כולל כוח הכבידה. מעקב אחר האצה בציר אחד.
TYPE_MAGNETIC_FIELD חומרה מדידת השדה הגיאומגנטי הסביבתי בכל שלושת הצירים הפיזיים (x, ‏ y, ‏ z) ביחידות מילי-טסלה (μT). יצירת מצפן.
TYPE_ORIENTATION תוכנות מדידת מעלות הסיבוב של המכשיר סביב כל שלושת הצירים הפיזיים (x, ‏ y, ‏ z). החל מרמת API 3, אפשר לקבל את מטריצת ההטיה ואת מטריצת הסיבוב של מכשיר באמצעות חיישן הכבידה וחיישן השדה הגיאומגנטי בשילוב עם השיטה getRotationMatrix(). קביעת המיקום של המכשיר.
TYPE_PRESSURE חומרה מדידת לחץ האוויר הסביבתי ב-hPa או ב-mbar. מעקב אחרי שינויים בלחץ האוויר.
TYPE_PROXIMITY חומרה מדידת הקרבה של אובייקט בסנטימטרים ביחס למסך התצוגה של המכשיר. החיישן הזה משמש בדרך כלל כדי לקבוע אם מכשיר הנייד מוחזק ליד האוזן של משתמש. מיקום הטלפון במהלך שיחה.
TYPE_RELATIVE_HUMIDITY חומרה מודדת את הלחות היחסית בסביבה באחוזים (%). מעקב אחרי נקודת הטל, הלחות המוחלטת והלחות היחסית.
TYPE_ROTATION_VECTOR תוכנה או חומרה מדידת הכיוון של מכשיר על ידי מתן שלושת הרכיבים של ווקטור הסיבוב של המכשיר. זיהוי תנועה וזיהוי סיבוב.
TYPE_TEMPERATURE חומרה מדידת הטמפרטורה של המכשיר במעלות צלזיוס (°C). הטמעת החיישן משתנה בין מכשירים, והוא הוחלף בחיישן TYPE_AMBIENT_TEMPERATURE ברמה 14 של ה-API. מעקב אחרי הטמפרטורות.

מסגרת חיישנים

אפשר לגשת לחיישנים האלה ולקבל נתוני חיישנים גולמיים באמצעות מסגרת החיישנים של Android. מסגרת החיישנים היא חלק מחבילת android.hardware, והיא כוללת את הכיתות והממשקים הבאים:

SensorManager
אפשר להשתמש בכיתה הזו כדי ליצור מופע של שירות החיישן. בכיתה הזו יש שיטות שונות לגישה ולרישום של חיישנים, רישום וביטול רישום של מאזיני אירועים של חיישנים והשגת פרטי כיוון. בסיווג הזה יש גם כמה קבועי חיישנים שמשמשים לדיווח על דיוק החיישנים, להגדרת שיעורי צירוף נתונים ולכיול חיישנים.
Sensor
אפשר להשתמש בכיתה הזו כדי ליצור מופע של חיישן ספציפי. בכיתה הזו יש שיטות שונות שמאפשרות לקבוע את היכולות של חיישן.
SensorEvent
המערכת משתמשת בכיתה הזו כדי ליצור אובייקט של אירוע חיישן, שמספק מידע על אירוע חיישן. אובייקט של אירוע חיישן כולל את המידע הבא: נתוני החיישן הגולמיים, סוג החיישן שיצר את האירוע, הדיוק של הנתונים וחותמת הזמן של האירוע.
SensorEventListener
אפשר להשתמש בממשק הזה כדי ליצור שתי שיטות קריאה חוזרת (callback) שמקבלות התראות (אירועי חיישן) כשערכים של חיישנים משתנים או כשהדיוק של החיישן משתנה.

באפליקציה רגילה, משתמשים בממשקי ה-API הקשורים לחיישנים האלה כדי לבצע שתי משימות בסיסיות:

  • זיהוי החיישנים והיכולות של החיישנים

    זיהוי חיישנים ויכולות של חיישנים בזמן הריצה שימושי אם באפליקציה יש תכונות שמסתמכות על סוגים ספציפיים של חיישנים או על יכולות ספציפיות של חיישנים. לדוגמה, יכול להיות שתרצו לזהות את כל החיישנים שנמצאים במכשיר ולהשבית את התכונות של האפליקציה שמסתמכות על חיישנים שלא נמצאים במכשיר. כמו כן, כדאי לזהות את כל החיישנים מסוג נתון כדי שתוכלו לבחור את הטמעת החיישן שמספקת את הביצועים האופטימליים לאפליקציה.

  • מעקב אחר אירועים של חיישנים

    מעקב אחר אירועי חיישנים מאפשר לכם לקבל נתוני חיישנים גולמיים. אירוע בחיישן מתרחש בכל פעם שחיישן מזהה שינוי בפרמטרים שהוא מודד. אירוע חיישן מספק ארבעה פריטים של מידע: שם החיישן שהפעיל את האירוע, חותמת הזמן של האירוע, הדיוק של האירוע ונתוני החיישן הגולמיים שהפעילו את האירוע.

זמינות החיישנים

זמינות החיישנים משתנה ממכשיר למכשיר, אבל היא יכולה להשתנות גם בין גרסאות Android. הסיבה לכך היא שהחיישנים של Android הוצגו במהלך כמה גרסאות של הפלטפורמה. לדוגמה, חיישנים רבים הושקו ב-Android 1.5 (רמת API 3), אבל חלקם לא הוטמעו ולא היו זמינים לשימוש עד Android 2.3 (רמת API 9). באופן דומה, מספר חיישנים הושקו ב-Android 2.3 (רמת API‏ 9) וב-Android 4.0 (רמת API‏ 14). שני חיישנים הוצאו משימוש והוחלפו בחיישנים חדשים וטובים יותר.

טבלה 2 מסכמת את הזמינות של כל חיישן על בסיס פלטפורמה-פלטפורמה. ברשימה מפורטות רק ארבע פלטפורמות כי אלה הפלטפורמות שבהן נערכו שינויים בחיישנים. חיישנים שמפורטים כמיושנים עדיין זמינים בפלטפורמות הבאות (בתנאי שהחיישן נמצא במכשיר), בהתאם למדיניות של Android בנושא תאימות עתידית.

טבלה 2. זמינות החיישן לפי פלטפורמה.

חיישן Android 4.0
(רמת API 14)
Android 2.3
(רמת API 9)
Android 2.2
(רמת API 8)
Android 1.5
(רמת API 3)
TYPE_ACCELEROMETER כן כן כן כן
TYPE_AMBIENT_TEMPERATURE כן לא רלוונטי לא רלוונטי לא רלוונטי
TYPE_GRAVITY כן כן לא רלוונטי לא רלוונטי
TYPE_GYROSCOPE כן כן לא רלוונטי1 לא רלוונטי1
TYPE_LIGHT כן כן כן כן
TYPE_LINEAR_ACCELERATION כן כן לא רלוונטי לא רלוונטי
TYPE_MAGNETIC_FIELD כן כן כן כן
TYPE_ORIENTATION כן2 כן2 כן2 כן
TYPE_PRESSURE כן כן לא רלוונטי1 לא רלוונטי1
TYPE_PROXIMITY כן כן כן כן
TYPE_RELATIVE_HUMIDITY כן לא רלוונטי לא רלוונטי לא רלוונטי
TYPE_ROTATION_VECTOR כן כן לא רלוונטי לא רלוונטי
TYPE_TEMPERATURE כן2 כן כן כן

1 סוג החיישן הזה נוסף ב-Android 1.5 (רמת API 3), אבל לא היה זמין לשימוש עד Android 2.3 (רמת API 9).

2 החיישן הזה זמין, אבל הוא הוצא משימוש.

זיהוי חיישנים ויכולות של חיישנים

במסגרת החיישנים של Android יש כמה שיטות שמאפשרות לקבוע בקלות בזמן הריצה אילו חיישנים נמצאים במכשיר. ב-API יש גם שיטות שמאפשרות לקבוע את היכולות של כל חיישן, כמו הטווח המקסימלי, הרזולוציה ודרישות המתח.

כדי לזהות את החיישנים שנמצאים במכשיר, קודם צריך לקבל הפניה לשירות החיישנים. כדי לעשות זאת, יוצרים מופע של המחלקה SensorManager על ידי קריאה ל-method getSystemService() והעברת הארגומנט SENSOR_SERVICE. לדוגמה:

Kotlin

private lateinit var sensorManager: SensorManager
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager

Java

private SensorManager sensorManager;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);

לאחר מכן, אפשר לקבל רשימה של כל החיישנים במכשיר באמצעות קריאה ל-method‏ getSensorList() ושימוש בערך הקבוע TYPE_ALL. לדוגמה:

Kotlin

val deviceSensors: List<Sensor> = sensorManager.getSensorList(Sensor.TYPE_ALL)

Java

List<Sensor> deviceSensors = sensorManager.getSensorList(Sensor.TYPE_ALL);

אם רוצים לרשום את כל החיישנים מסוג נתון, אפשר להשתמש בערך קבוע אחר במקום TYPE_ALL, כמו TYPE_GYROSCOPE, ‏ TYPE_LINEAR_ACCELERATION או TYPE_GRAVITY.

אפשר גם לקבוע אם קיים חיישן מסוג ספציפי במכשיר באמצעות השיטה getDefaultSensor() והעברת הקבוע של הסוג של חיישן ספציפי. אם במכשיר יש יותר מחיישן אחד מסוג מסוים, צריך להגדיר את אחד מהחיישנים כחיישן ברירת המחדל. אם לא קיים חיישן ברירת מחדל לסוג מסוים של חיישן, קריאת השיטה מחזירה null, כלומר אין במכשיר חיישן מהסוג הזה. לדוגמה, הקוד הבא בודק אם יש מגנטומטר במכשיר:

Kotlin

private lateinit var sensorManager: SensorManager
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
if (sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null) {
    // Success! There's a magnetometer.
} else {
    // Failure! No magnetometer.
}

Java

private SensorManager sensorManager;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null){
    // Success! There's a magnetometer.
} else {
    // Failure! No magnetometer.
}

הערה: מערכת Android לא מחייבת את יצרני המכשירים להטמיע סוגים מסוימים של חיישנים במכשירים מבוססי Android, כך שלמכשירים יכולות להיות הגדרות חיישנים רבות.

בנוסף לרישום החיישנים במכשיר, אפשר להשתמש בשיטות הציבוריות של הכיתה Sensor כדי לקבוע את היכולות והמאפיינים של חיישנים ספציפיים. האפשרות הזו שימושית אם רוצים שהאפליקציה תתנהג בצורה שונה בהתאם לחיישני המכשיר או ליכולות החיישן שזמינות בו. לדוגמה, אפשר להשתמש בשיטות getResolution() ו-getMaximumRange() כדי לקבל את הרזולוציה של חיישן ואת טווח המדידה המקסימלי שלו. אפשר גם להשתמש ב-method‏ getPower() כדי לקבל את דרישות החשמל של חיישן.

שתי השיטות הציבוריות שימושיות במיוחד אם רוצים לבצע אופטימיזציה של האפליקציה לחיישנים של יצרנים שונים או לגרסאות שונות של חיישן. לדוגמה, אם באפליקציה צריך לעקוב אחרי תנועות של משתמשים כמו הטיה וניעור, אפשר ליצור קבוצה אחת של אופטימיזציות וכללי סינון נתונים למכשירים חדשים יותר עם חיישן כבידה ספציפי של ספק מסוים, וקבוצה נוספת של כללים ואופטימיזציות לסינון נתונים למכשירים שאין בהם חיישן כוח כבידה ושיש להם רק מד תאוצה. בדוגמת הקוד הבאה מוסבר איך משתמשים בשיטות getVendor() ו-getVersion() כדי לעשות זאת. בדוגמה הזו, אנחנו מחפשים חיישן כבידה שבו Google LLC מופיעה כספק ומספר הגרסה הוא 3. אם החיישן הספציפי הזה לא קיים במכשיר, ננסה להשתמש במד התאוצה.

Kotlin

private lateinit var sensorManager: SensorManager
private var mSensor: Sensor? = null

...

sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager

if (sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) != null) {
    val gravSensors: List<Sensor> = sensorManager.getSensorList(Sensor.TYPE_GRAVITY)
    // Use the version 3 gravity sensor.
    mSensor = gravSensors.firstOrNull { it.vendor.contains("Google LLC") && it.version == 3 }
}
if (mSensor == null) {
    // Use the accelerometer.
    mSensor = if (sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null) {
        sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
    } else {
        // Sorry, there are no accelerometers on your device.
        // You can't play this game.
        null
    }
}

Java

private SensorManager sensorManager;
private Sensor mSensor;

...

sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensor = null;

if (sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY) != null){
    List<Sensor> gravSensors = sensorManager.getSensorList(Sensor.TYPE_GRAVITY);
    for(int i=0; i<gravSensors.size(); i++) {
        if ((gravSensors.get(i).getVendor().contains("Google LLC")) &&
           (gravSensors.get(i).getVersion() == 3)){
            // Use the version 3 gravity sensor.
            mSensor = gravSensors.get(i);
        }
    }
}
if (mSensor == null){
    // Use the accelerometer.
    if (sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER) != null){
        mSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    } else{
        // Sorry, there are no accelerometers on your device.
        // You can't play this game.
    }
}

שיטה שימושית נוספת היא השיטה getMinDelay(), שמחזירה את מרווח הזמן המינימלי (במיקרו-שניות) שבו חיישן יכול לזהות נתונים. כל חיישן שמחזיר ערך שאינו אפס בשיטה getMinDelay() הוא חיישן סטרימינג. חיישנים מסוג סטרימינג מזהים נתונים במרווחי זמן קבועים והם הוצגו ב-Android 2.3 (רמת API‏ 9). אם החיישן מחזיר אפס כשקוראים לשיטה getMinDelay(), סימן שהוא לא חיישן סטרימינג כי הוא מדווח על נתונים רק כשיש שינוי בפרמטרים שהוא מזהה.

השיטה getMinDelay() שימושית כי היא מאפשרת לקבוע את הקצב המקסימלי שבו חיישן יכול לקבל נתונים. אם תכונות מסוימות באפליקציה שלכם דורשות שיעורי צבירת נתונים גבוהים או חיישן סטרימינג, תוכלו להשתמש בשיטה הזו כדי לקבוע אם חיישן מסוים עומד בדרישות האלה, ולאחר מכן להפעיל או להשבית את התכונות הרלוונטיות באפליקציה בהתאם.

זהירות: קצב האיסוף המקסימלי של נתונים בחיישן לא תמיד זהה לקצב שבו מסגרת החיישן מעבירה את נתוני החיישן לאפליקציה. מסגרת החיישנים מדווחת על נתונים באמצעות אירועי חיישנים, ומספר גורמים משפיעים על הקצב שבו האפליקציה מקבלת אירועי חיישנים. מידע נוסף זמין במאמר מעקב אחרי אירועים בחיישני תנועה.

מעקב אחרי אירועים של חיישנים

כדי לעקוב אחרי נתוני חיישן גולמיים, צריך להטמיע שתי שיטות קריאה חוזרת (callback) שחשופות דרך הממשק SensorEventListener: onAccuracyChanged() ו-onSensorChanged(). מערכת Android מפעילה את השיטות האלה במקרים הבאים:

  • רמת הדיוק של חיישן משתנה.

    במקרה כזה, המערכת מפעילה את ה-method onAccuracyChanged() ומספקת הפניה לאובייקט Sensor שהשתנה, ורמת הדיוק החדשה של החיישן. מידת הדיוק מיוצגת על ידי אחד מתוך ארבעה קבועים של סטטוס: SENSOR_STATUS_ACCURACY_LOW, SENSOR_STATUS_ACCURACY_MEDIUM, SENSOR_STATUS_ACCURACY_HIGH, או SENSOR_STATUS_UNRELIABLE.

  • חיישן מדווח על ערך חדש.

    במקרה כזה, המערכת מפעילה את ה-method onSensorChanged() ומספקת לכם אובייקט SensorEvent. אובייקט SensorEvent מכיל מידע על נתוני החיישן החדשים, כולל: הדיוק של הנתונים, החיישן שיצר את הנתונים, חותמת הזמן שבה הנתונים נוצרו והנתונים החדשים שהחיישן תיעד.

הקוד הבא מראה איך להשתמש בשיטה onSensorChanged() כדי לעקוב אחרי נתונים מחיישני האור. בדוגמה הזו מוצגים נתוני החיישן הגולמיים ב-TextView שמוגדר בקובץ main.xml בתור sensor_data.

Kotlin

class SensorActivity : Activity(), SensorEventListener {
    private lateinit var sensorManager: SensorManager
    private var mLight: Sensor? = null

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)

        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
        mLight = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT)
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes.
    }

    override fun onSensorChanged(event: SensorEvent) {
        // The light sensor returns a single value.
        // Many sensors return 3 values, one for each axis.
        val lux = event.values[0]
        // Do something with this sensor value.
    }

    override fun onResume() {
        super.onResume()
        mLight?.also { light ->
            sensorManager.registerListener(this, light, SensorManager.SENSOR_DELAY_NORMAL)
        }
    }

    override fun onPause() {
        super.onPause()
        sensorManager.unregisterListener(this)
    }
}

Java

public class SensorActivity extends Activity implements SensorEventListener {
    private SensorManager sensorManager;
    private Sensor mLight;

    @Override
    public final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mLight = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
    }

    @Override
    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes.
    }

    @Override
    public final void onSensorChanged(SensorEvent event) {
        // The light sensor returns a single value.
        // Many sensors return 3 values, one for each axis.
        float lux = event.values[0];
        // Do something with this sensor value.
    }

    @Override
    protected void onResume() {
        super.onResume();
        sensorManager.registerListener(this, mLight, SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    protected void onPause() {
        super.onPause();
        sensorManager.unregisterListener(this);
    }
}

בדוגמה הזו, ברירת המחדל של ההשהיה בנתונים (SENSOR_DELAY_NORMAL) מצוינת כשמופעלת השיטה registerListener(). זמן האחזור של הנתונים (או קצב הדגימה) קובע את המרווח שבו אירועי החיישן נשלחים לאפליקציה באמצעות שיטת ה-callback‏ onSensorChanged(). עיכוב ברירת המחדל של הנתונים מתאים למעקב אחרי שינויים אופייניים בכיוון המסך, והוא משתמש בעיכוב של 200,000 מיקרו-שניות. אפשר לציין עיכובים אחרים בנתונים, כמו SENSOR_DELAY_GAME (עיכוב של 20,000 מיקרו-שניות), SENSOR_DELAY_UI (השהיה של 60,000 מיקרו-שניות) או SENSOR_DELAY_FASTEST (השהיה של 0 מיקרו-שניות). החל מגרסה 3.0 של Android‏ (רמה 11 של API), אפשר לציין את העיכוב גם כערך אבסולוטי (במיליוניות השנייה).

ההשהיה שציינתם היא רק הצעה להשהיה. מערכת Android ואפליקציות אחרות יכולות לשנות את ההשהיה הזו. מומלץ לציין את העיכוב הגדול ביותר שאפשר כי המערכת בדרך כלל משתמשת בעיכוב קטן יותר מזה שציינתם (כלומר, צריך לבחור את קצב הדגימה האיטי ביותר שעדיין מתאים לצרכים של האפליקציה). שימוש בהשהיה ארוכה יותר יוצר עומס נמוך יותר על המעבד, ולכן צורך פחות חשמל.

אין שיטה ציבורית לקביעת הקצב שבו מסגרת החיישנים שולחת אירועי חיישנים לאפליקציה. עם זאת, אפשר להשתמש בחותמות הזמן שמשויכות לכל אירוע חיישנים כדי לחשב את קצב הדגימה במספר אירועים. אין צורך לשנות את קצב הדגימה (העיכוב) אחרי שמגדירים אותו. אם מסיבה כלשהי אתם צריכים לשנות את העיכוב, תצטרכו לבטל את הרישום של מאזין החיישן ולרשום אותו מחדש.

חשוב גם לציין שבדוגמה הזו נעשה שימוש בשיטות ה-callback‏ onResume() ו-onPause() כדי לרשום ולבטל את הרישום של מאזין האירועים של החיישן. מומלץ תמיד להשבית חיישנים שאתם לא צריכים, במיוחד כשהפעילות שלכם מושהית. אם לא תעשו זאת, הסוללה עלולה להתרוקן תוך כמה שעות בלבד, כי לחלק מהחיישנים יש דרישות הספק גבוהות והם יכולים לרוקן את הסוללה במהירות. המערכת לא משביתה את החיישנים באופן אוטומטי כשהמסך כבוי.

טיפול בתצורות שונות של חיישנים

ב-Android לא מצוין הגדרת חיישן רגילה למכשירים, כלומר יצרני המכשירים יכולים לשלב במכשירים שלהם עם Android כל הגדרת חיישן שהם רוצים. כתוצאה מכך, המכשירים יכולים לכלול מגוון חיישנים במגוון רחב של הגדרות. אם האפליקציה שלכם מסתמכת על סוג מסוים של חיישן, עליכם לוודא שהחיישן נמצא במכשיר כדי שהאפליקציה תפעל כמו שצריך.

יש שתי אפשרויות לוודא שחיישן מסוים נמצא במכשיר:

  • זיהוי חיישנים בזמן הריצה והפעלה או השבתה של תכונות האפליקציה בהתאם.
  • להשתמש במסננים של Google Play כדי לטרגט מכשירים עם תצורות חיישנים ספציפיות.

בסעיפים הבאים נסביר על כל אפשרות.

זיהוי חיישנים בזמן ריצה

אם האפליקציה שלכם משתמשת בסוג מסוים של חיישן, אבל לא מסתמכת עליו, תוכלו להשתמש במסגרת החיישנים כדי לזהות את החיישן בזמן הריצה, ואז להשבית או להפעיל את תכונות האפליקציה בהתאם. לדוגמה, אפליקציית ניווט עשויה להשתמש בחיישן הטמפרטורה, בחיישן הלחץ, בחיישן ה-GPS ובחיישן השדה הגיאומגנטי כדי להציג את הטמפרטורה, הלחץ הברומטרי, המיקום והכיוון לפי מצפן. אם במכשיר אין חיישן לחץ, אפשר להשתמש במסגרת החיישן כדי לזהות אם חיישן הלחץ חסר בזמן הריצה, ואז להשבית את החלק בממשק המשתמש של האפליקציה שמציג לחץ. לדוגמה, הקוד הבא בודק אם יש חיישן לחץ במכשיר:

Kotlin

private lateinit var sensorManager: SensorManager
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager

if (sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE) != null) {
    // Success! There's a pressure sensor.
} else {
    // Failure! No pressure sensor.
}

Java

private SensorManager sensorManager;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
if (sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE) != null){
    // Success! There's a pressure sensor.
} else {
    // Failure! No pressure sensor.
}

שימוש במסננים של Google Play לטירגוט תצורות חיישנים ספציפיות

אם אתם מפרסמים את האפליקציה ב-Google Play, תוכלו להשתמש ברכיב <uses-feature> בקובץ המניפסט כדי לסנן את האפליקציה ממכשירים שאין בהם את הגדרת החיישן המתאימה לאפליקציה. לרכיב <uses-feature> יש כמה מתארי חומרה שמאפשרים לסנן אפליקציות על סמך נוכחות של חיישנים ספציפיים. החיישנים שאפשר לציין הם: מד תאוצה, ברומטר, מצפן (שדה מגנטי גיאו), ג'ירוסקופ, אור וקרבה. לפניכם רשומת מניפסט לדוגמה שמסננת אפליקציות שאין להן מד תאוצה:

<uses-feature android:name="android.hardware.sensor.accelerometer"
              android:required="true" />

אם תוסיפו את הרכיב ואת המאפיין הזה למניפסט של האפליקציה, המשתמשים יראו את האפליקציה ב-Google Play רק אם יש במכשיר שלהם תאוצה.

צריך להגדיר את המתאר ל-android:required="true" רק אם האפליקציה מסתמך רק על חיישן מסוים. אם באפליקציה נעשה שימוש בחיישן לפונקציונליות מסוימת אבל עדיין פועלת בלי החיישן, צריך לרשום את החיישן ברכיב <uses-feature>, אבל להגדיר את התיאור ל-android:required="false". כך תוכלו לוודא שאפשר יהיה להתקין את האפליקציה במכשירים גם אם אין בהם את החיישן הספציפי הזה. זו גם שיטה מומלצת לניהול פרויקטים שתעזור לכם לעקוב אחרי התכונות שבהן האפליקציה משתמשת. חשוב לזכור: אם האפליקציה שלכם משתמשת בחיישן מסוים אבל עדיין פועלת בלי החיישן, עליכם לזהות את החיישן בזמן הריצה ולהשבית או להפעיל את תכונות האפליקציה בהתאם.

מערכת לתיאום חיישנים

באופן כללי, מסגרת החיישן משתמשת במערכת קואורדינטות סטנדרטית עם 3 צירים כדי להביע את ערכי הנתונים. ברוב החיישנים, מערכת הקואורדינטות מוגדרת ביחס למסך המכשיר כשהמכשיר מוחזק בכיוון ברירת המחדל שלו (ראו איור 1). כשמכשיר מוחזק בכיוון ברירת המחדל שלו, ציר X הוא אופקי וכיוון ימינה, ציר Y הוא אנכי וכיוון למעלה וציר Z כיוון אל מחוץ לפני המסך. במערכת הזו, לקואורדינטות מאחורי המסך יש ערכי Z שליליים. חיישני ה-GPS הבאים משתמשים במערכת הקואורדינטות הזו:

איור 1. מערכת קואורדינטות (ביחס למכשיר) שבה נעשה שימוש ב-Sensor API.

הנקודה החשובה ביותר שצריך להבין לגבי מערכת הקואורדינטות הזו היא שהצירים לא מוחלפים כשכיוון המסך של המכשיר משתנה – כלומר, מערכת הקואורדינטות של החיישן אף פעם לא משתנה כשהמכשיר זז. ההתנהגות הזו זהה להתנהגות של מערכת הקואורדינטות של OpenGL.

חשוב גם להבין שאסור להניח שהכיוון הטבעי (ברירת המחדל) של המכשיר הוא לרוחב. הכיוון הטבעי של הרבה מכשירי טאבלט הוא לרוחב. בנוסף, מערכת הקואורדינטות של החיישן תמיד מבוססת על הכיוון הטבעי של המכשיר.

לבסוף, אם האפליקציה מתאימה את נתוני החיישנים לצג על המסך, צריך להשתמש בשיטה getRotation() כדי לקבוע את סיבוב המסך, ואז להשתמש בשיטה remapCoordinateSystem() כדי למפות קואורדינטות של חיישנים לקואורדינטות של המסך. צריך לעשות זאת גם אם המניפסט מציין תצוגה בפורמט לאורך בלבד.

הערה: יש חיישנים ושיטות שמשתמשים במערכת קואורדינטות יחסית למסגרת העזר של העולם (בניגוד למסגרת העזר של המכשיר). החיישנים והשיטות האלה מחזירים נתונים שמייצגים את תנועת המכשיר או את המיקום שלו ביחס לכדור הארץ. למידע נוסף, ראו method getOrientation(), method getRotationMatrix(), Orientation חיישן וחיישן וקטורי סיבוב.

הגבלת קצב של יצירת בקשות בחיישן

כדי להגן על מידע רגיש פוטנציאלי של משתמשים, אם האפליקציה שלכם מטרגטת את Android 12 (רמת API 31) ואילך, המערכת מגבילה את קצב הרענון של נתונים מסוימים ממכשירי חיישן תנועה ומכשירי חיישן מיקום. הנתונים האלה כוללים ערכים שמתועדים באמצעות מד התאוצה, הג'ירוסקופ וחיישן השדה הגאומגנטי של המכשיר.

המגבלה על קצב הרענון תלויה באופן שבו אתם ניגשים לנתוני החיישנים:

אם האפליקציה שלכם צריכה לאסוף נתונים מחיישן תנועה בקצב גבוה יותר, עליכם להצהיר על ההרשאה HIGH_SAMPLING_RATE_SENSORS, כפי שמתואר בקטע הקוד הבא. אחרת, אם האפליקציה תנסה לאסוף נתונים של חיישן תנועה בתדירות גבוהה יותר בלי להצהיר על ההרשאה הזו, תתרחש הודעת SecurityException.

AndroidManifest.xml

<manifest ...>
    <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS"/>
    <application ...>
        ...
    </application>
</manifest>

שיטות מומלצות לגישה לחיישנים ולשימוש בהם

כשאתם מתכננים את הטמעת החיישן, חשוב לפעול לפי ההנחיות שמפורטות בקטע הזה. ההנחיות האלה הן שיטות מומלצות לכל מי שמשתמש במסגרת החיישנים כדי לגשת לחיישנים ולקבל נתוני חיישנים.

איסוף נתוני חיישנים רק בחזית

במכשירים עם Android מגרסה 9 (API ברמה 28) ואילך, לאפליקציות שפועלות ברקע יש את ההגבלות הבאות:

  • חיישנים שמשתמשים במצב דיווח רציף, כמו מדי תאוצה וג'ירוסקופים, לא מקבלים אירועים.
  • חיישנים שמשתמשים במצבי הדיווח on-change או one-shot לא מקבלים אירועים.

לאור המגבלות האלה, מומלץ לזהות אירועי חיישנים כשהאפליקציה בחזית או כחלק משירות בחזית.

ביטול הרישום של מעבדי האירועים של החיישנים

חשוב לבטל את רישום המאזינים של החיישן בסיום השימוש בחיישן או כשהפעילות של החיישן מושהית. אם מאזין חיישן רשום והפעילות שלו מושהית, החיישן ימשיך לאסוף נתונים ולהשתמש במשאבי הסוללה, אלא אם תבטלו את הרישום של החיישן. הקוד הבא מראה איך להשתמש בשיטה onPause() כדי לבטל את הרישום של מאזין:

Kotlin

private lateinit var sensorManager: SensorManager
...
override fun onPause() {
    super.onPause()
    sensorManager.unregisterListener(this)
}

Java

private SensorManager sensorManager;
...
@Override
protected void onPause() {
    super.onPause();
    sensorManager.unregisterListener(this);
}

מידע נוסף זמין בכתובת unregisterListener(SensorEventListener).

בדיקה באמצעות Android Emulator

במהדורת Android Emulator יש קבוצה של אמצעי בקרה וירטואליים לחיישנים, שמאפשרים לבדוק חיישנים כמו מד תאוצה, טמפרטורה סביבתית, מגנטומטר, קרבה, אור ועוד.

האמולטור משתמש בחיבור למכשיר Android שבו פועלת האפליקציה SdkControllerSensor. שימו לב שהאפליקציה הזו זמינה רק במכשירים עם Android מגרסה 4.0 (רמת API 14) ואילך. (אם במכשיר מותקנת מערכת Android מגרסה 4.0, צריכה להיות מותקנת בו גרסה 2). האפליקציה SdkControllerSensor עוקבת אחרי השינויים בחיישני המכשיר ומעבירה אותם למה emultor. לאחר מכן, הסימולטור עובר טרנספורמציה על סמך הערכים החדשים שהוא מקבל מהחיישנים במכשיר.

אפשר לראות את קוד המקור של האפליקציה SdkControllerSensor במיקום הבא:

$ your-android-sdk-directory/tools/apps/SdkController

כדי להעביר נתונים בין המכשיר לבין הסימולטור:

  1. מוודאים שניפוי באגים ב-USB מופעל במכשיר.
  2. מחברים את המכשיר למכונת הפיתוח באמצעות כבל USB.
  3. מפעילים את האפליקציה SdkControllerSensor במכשיר.
  4. באפליקציה, בוחרים את החיישנים שרוצים לדמות.
  5. מריצים את הפקודה הבאה ב-adb:

  6. $ adb forward tcp:1968 tcp:1968
    
  7. מפעילים את האמולטור. עכשיו תוכלו להחיל טרנספורמציות על הסימולטור על ידי הזזת המכשיר.

הערה: אם התנועות שביצעתם במכשיר הפיזי לא משנות את האמולטור, נסו להריץ שוב את הפקודה adb משלב 5.

מידע נוסף זמין במדריך לאמולטור Android.

לא לחסום את השיטה onSensorChanged()‎

נתוני החיישנים יכולים להשתנות בתדירות גבוהה, ולכן יכול להיות שהמערכת תבצע קריאה ל-method‏ onSensorChanged(SensorEvent) לעיתים קרובות. מומלץ לעשות כמה שפחות באמצעות method onSensorChanged(SensorEvent) כדי לא לחסום אותה. אם באפליקציה נדרשת סינון נתונים או צמצום של נתוני החיישנים, צריך לבצע את העבודה מחוץ ל-method onSensorChanged(SensorEvent).

אין להשתמש בשיטות או בסוגי חיישנים שהוצאו משימוש

כמה שיטות וקבועים הוצאו משימוש. באופן ספציפי, סוג החיישן TYPE_ORIENTATION הוצא משימוש. כדי לקבל נתוני כיוון, צריך להשתמש בשיטה getOrientation() במקום זאת. כמו כן, סוג החיישן TYPE_TEMPERATURE הוצא משימוש. במקום זאת, יש להשתמש בחיישן מסוג TYPE_AMBIENT_TEMPERATURE במכשירים שבהם פועלת מערכת Android 4.0.

אימות החיישנים לפני השימוש בהם

תמיד צריך לוודא שיש חיישן במכשיר לפני שמנסים לאסוף ממנו נתונים. אל תניחו שחיישן קיים רק בגלל שהוא חיישן נפוץ. יצרני המכשירים לא נדרשים לספק חיישנים ספציפיים במכשירים שלהם.

חשוב לבחור את עיכובי החיישנים בקפידה

כשרושמים חיישן באמצעות השיטה registerListener(), חשוב לבחור קצב העברה שמתאים לאפליקציה או לתרחיש לדוגמה. חיישנים יכולים לספק נתונים במהירויות גבוהות מאוד. אם תאפשרו למערכת לשלוח נתונים נוספים שאתם לא צריכים, תגרמו לבזבוז משאבי המערכת ולשימוש באנרגיה מהסוללה.