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

ברוב המכשירים עם 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 מפורטת זמינות כל חיישן לפי פלטפורמה. רק ארבע פלטפורמות מופיעות ברשימה כי הן הפלטפורמות שבהן בוצעו שינויים בחיישני ה-GPS. חיישנים שמפורטים כמיושנים עדיין זמינים בפלטפורמות הבאות (בתנאי שהחיישן נמצא במכשיר), בהתאם למדיניות של 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. אם החיישן הספציפי הזה לא נמצא במכשיר, אנחנו מנסים להשתמש בחיישן ה-accelerometer.

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 קוראת לשיטות האלה בכל אחד מהמקרים הבאים:

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

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

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

    במקרה כזה, המערכת מפעילה את השיטה 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‏ (רמת API 11), אפשר לציין את העיכוב גם כערך מוחלט (במיליוניות השנייה).

ההשהיה שציינתם היא רק הצעה להשהיה. מערכת 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> יש כמה מתארי חומרה שמאפשרים לסנן אפליקציות על סמך נוכחות של חיישנים ספציפיים. החיישנים שאפשר לציין הם: מד תאוצה, ברומטר, מצפן (שדה מגנטי גיאו), ג'ירוסקופ, אור וקרבה. זוהי דוגמה לרשומה ב-manifest שמסננת אפליקציות שאין להן תאוצה:

<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.

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

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

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

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

כדי להגן על מידע רגיש פוטנציאלי של משתמשים, אם האפליקציה שלכם מטרגטת את 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 עוקבת אחרי השינויים בחיישני המכשיר ומעבירה אותם למהדר. לאחר מכן, הסימולטור עובר טרנספורמציה על סמך הערכים החדשים שהוא מקבל מהחיישנים במכשיר.

אפשר לראות את קוד המקור של האפליקציה 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 Emulator.

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

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

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

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

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

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

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

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