טיפול באירועים של שכבות נתונים ב-Wear

כששולחים קריאה ל-Data Layer API, אפשר לקבל את הסטטוס של הקריאה כשהיא מסתיימת. אתם יכולים גם להאזין לאירועי נתונים שנובעים משינויים בנתונים שהאפליקציה שלכם מבצעת בכל מקום ברשת Wear OS by Google.

דוגמה לשימוש יעיל ב-Data Layer API מופיעה באפליקציה Android DataLayer Sample.

המתנה לסטטוס של קריאות לשכבת הנתונים

קריאות ל-Data Layer API – כמו קריאה באמצעות השיטה putDataItem של המחלקה DataClient – מחזירות לפעמים אובייקט Task<ResultType>. ברגע שאובייקט Task נוצר, הפעולה מתווספת לתור ברקע. אם לא תבצעו פעולה נוספת אחרי זה, הפעולה תסתיים בסופו של דבר ללא התראה.

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

שיחות אסינכרוניות

אם הקוד שלכם פועל ב-שרשור UI הראשי, אל תבצעו קריאות חוסמות ל-Data Layer API, ותשתמשו ב-Coroutine כדי לקרוא ל-putDataItem:

private suspend fun Context.sendDataAsync(count: Int) {
    try {
        val putDataReq: PutDataRequest = PutDataMapRequest.create("/count").run {
            dataMap.putInt("count_key", count)
            asPutDataRequest()
        }
        val dataItem = Wearable.getDataClient(this).putDataItem(putDataReq).await()
        handleDataItem(dataItem)
    } catch (e: Exception) {
        handleDataItemError(e)
    } finally {
        handleTaskComplete()
    }
}

private fun handleDataItem(dataItem: DataItem) { }
private fun handleDataItemError(exception: Exception) { }
private fun handleTaskComplete() { }

אפשר לעיין בTask API כדי לראות אפשרויות נוספות, כולל שרשור של ביצוע משימות שונות.

שיחות סינכרוניות

אם הקוד פועל ב-thread נפרד של handler בשירות הפועל ברקע, כמו ב-WearableListenerService, צריך להשתמש ב-runBlocking כדי לבצע קריאה חוסמת ל-putDataItem.

הערה: אל תתקשרו למספר הזה כשאתם בשרשור הראשי.

private fun Context.sendDataSync(count: Int) = runBlocking {
    val putDataReq = PutDataMapRequest.create("/count").run {
        dataMap.putInt("count_key", count)
        asPutDataRequest()
    }

    try {
        val result = Wearable.getDataClient(this@sendDataSync)
            .putDataItem(putDataReq)
            .await()
        // Logic for success
    } catch (e: Exception) {
        // Handle failure
    }
}

האזנה לאירועים בשכבת הנתונים

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

יש שתי אפשרויות להאזנה לאירועים בשכבת הנתונים:

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

הערה: כשבוחרים הטמעה של listener, חשוב לקחת בחשבון את השימוש בסוללה של האפליקציה. ‫WearableListenerService רשום במניפסט של האפליקציה ויכול להפעיל את האפליקציה אם היא לא פועלת כבר. אם אתם צריכים להאזין לאירועים רק כשהאפליקציה כבר פועלת, כמו במקרים רבים של אפליקציות אינטראקטיביות, אל תשתמשו ב-WearableListenerService. במקום זאת, צריך לרשום מעבד אירוע של שידור חי. לדוגמה, אפשר להשתמש בשיטה addListener של המחלקה DataClient. הפעולה הזו יכולה להפחית את העומס על המערכת ואת השימוש בסוללה.

שימוש ב-WearableListenerService

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

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

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

  • onDataChanged(): בכל פעם שאובייקט של פריט נתונים נוצר, נמחק או משתנה, המערכת מפעילה את הקריאה החוזרת הזו בכל הצמתים המחוברים.
  • onMessageReceived(): הודעה שנשלחת מצומת מפעילה את הקריאה החוזרת הזו בצומת היעד.
  • onCapabilityChanged(): כשפונקציונליות שמקרה של האפליקציה שלכם מפרסם הופכת לזמינה ברשת, האירוע הזה מפעיל את הקריאה החוזרת (callback) הזו. אם אתם מחפשים צומת בקרבת מקום, אתם יכולים לשלוח שאילתה לשיטה isNearby() של הצמתים שסופקו בקריאה החוזרת.

אפשר גם להאזין לאירועים מ-ChannelClient.ChannelCallback, כמו onChannelOpened().

כל האירועים הקודמים מופעלים בשרשור ברקע, ולא בשרשור הראשי.

כדי ליצור WearableListenerService, פועלים לפי השלבים הבאים:

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

בדוגמה הבאה אפשר לראות איך מטמיעים WearableListenerService:

class DataLayerListenerService : WearableListenerService() {

    override fun onDataChanged(dataEvents: DataEventBuffer) {
        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onDataChanged: $dataEvents")
        }

        // Loop through the events and send a message
        // to the node that created the data item.
        dataEvents
            .map { it.dataItem.uri }
            .forEach { uri ->
                // Get the node ID from the host value of the URI.
                val nodeId: String = uri.host!!
                // Set the data of the message to be the bytes of the URI.
                val payload: ByteArray = uri.toString().toByteArray()

                // Send the RPC.
                Wearable.getMessageClient(this)
                    .sendMessage(
                        nodeId,
                        DATA_ITEM_RECEIVED_PATH,
                        payload
                    )
            }
    }
}

בקטע הבא מוסבר איך להשתמש במסנן Intent עם listener כזה.

שימוש בפילטרים עם WearableListenerService

מסנן Intent לדוגמה WearableListenerService שמוצגת בקטע הקודם יכול להיראות כך:

<service
    android:name=".snippets.datalayer.DataLayerListenerService"
    android:exported="true"
    tools:ignore="ExportedService" >
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
        <data
            android:scheme="wear"
            android:host="*"
            android:path="/start-activity" />
    </intent-filter>
</service>

מסנן הפעולות DATA_CHANGED מציין למערכת שהאפליקציה שלכם מתעניינת באירועים של שכבת הנתונים.

בדוגמה הזו, השעון מאזין לפריט הנתונים /start-activity, והטלפון מאזין לתגובת ההודעה /data-item-received (DATA_ITEM_RECEIVED_PATH).

הכללים הרגילים להתאמת מסננים ב-Android חלים. אפשר לציין כמה שירותים במניפסט אחד, כמה מסנני כוונות לכל שירות, כמה פעולות לכל מסנן וכמה קטעי נתונים לכל מסנן. המסננים יכולים להתאים לשם מארח עם wildcard או לשם מארח ספציפי. כדי להתאים למארח עם תו כללי לחיפוש, משתמשים ב-host="*". כדי להתאים למארח ספציפי, מציינים host=<node_id>.

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

מידע נוסף על סוגי המסננים שנתמכים ב-Wear OS זמין במאמרי העזרה של ה-API בנושא WearableListenerService.

מידע נוסף על מסנני נתונים וכללי התאמה מופיע במסמכי העזרה של ה-API בנושא רכיב המניפסט <data>.

כשמתאימים מסנני כוונות, חשוב לזכור שני כללים:

  • אם לא מציינים סכימה למסנן Intent, המערכת מתעלמת מכל שאר מאפייני ה-URI.
  • אם לא מציינים מארח למסנן, המערכת מתעלמת מכל מאפייני הנתיב.

שימוש במאזין חי

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

כדי להשתמש בגישה נקייה ובטוחה יותר, כדאי להשתמש בlifecycle observer. באמצעות lifecycle observer, מעבירים את לוגיקת הרישום מ-LifecycleResumeEvent של הפעילות למחלקה נפרדת שאפשר לעשות בה שימוש חוזר ומיישמת את DefaultLifecycleObserver.

הגישה הזו שומרת על פעילות רזה ומונעת באגים נפוצים כמו שכחת ביטול הרישום של ה-listener.

1. יצירת מאזין שמודע למחזור החיים

המחלקות האלה עוטפות את DataClient.OnDataChangedListener ומנהלות באופן אוטומטי את המינוי שלהן על סמך מחזור החיים של הפעילות.

class WearDataLayerObserver(
    private val dataClient: DataClient,
    private val onDataReceived: (DataEventBuffer) -> Unit
) : DefaultLifecycleObserver, DataClient.OnDataChangedListener {

    // Implementation of the DataClient listener
    override fun onDataChanged(dataEvents: DataEventBuffer) {
        onDataReceived(dataEvents)
    }

    // Automatically register when the Activity starts
    override fun onResume(owner: LifecycleOwner) {
        dataClient.addListener(this)
    }

    // Automatically unregister when the Activity pauses
    override fun onPause(owner: LifecycleOwner) {
        dataClient.removeListener(this)
    }
}

2. השימוש בפעילות שלכם

הפעילות שלכם לא צריכה יותר להשתמש ב-LifecycleResumeEvent או ב-onPause עבור Wear API. רושמים את הצופה פעם אחת ב-LaunchedEvent (או ב-onCreate).

class DataLayerLifecycleActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val dataClient = Wearable.getDataClient(this)

        // Create the observer and link it to the activity's lifecycle
        val wearObserver = WearDataLayerObserver(dataClient) { dataEvents ->
            handleDataEvents(dataEvents)
        }

        lifecycle.addObserver(wearObserver)
    }

    private fun handleDataEvents(dataEvents: DataEventBuffer) {
        // ... filter and process events ...
    }
}

למה זה עדיף:

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

שימוש במסננים עם מאזינים בזמן אמת

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

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