העברת אפליקציות מ-Wear אל GoogleApi

החל מ-גרסה גרסה 11.8.0 של Google Play Services, אפליקציות ל-Wear OS אמורות להפסיק את השימוש באפליקציות ל-Wear OS מGoogleApiClient ובמקום זאת להשתמש באובייקטים של לקוח שמבוססים על המחלקה GoogleApi.

השימוש ב- GoogleApi מקל על הגדרה של פעולות אסינכרוניות. לדוגמה, כפי שמתואר במבוא Tasks API, אפשר לקבל אובייקט Task במקום אובייקט PendingResult.

הדף הזה כולל:

  • טבלה של רכיבים חלופיים
  • דוגמה לעדכון של אפליקציה קיימת לשימוש ב-Tasks API

הערה: העדכון הזה לא חל על אפליקציות Wear OS עבור סין, שבה משתמשים בדרך כלל בגרסה 10.2.0 של Google Play Services.

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

החלפה של רכיבים שהוצאו משימוש

כשמשתמשים בכיתות שמרחיבות את המחלקה GoogleApi, כמו DataClient ו- MessageClient, ה-SDK של Google Play Services מנהל חיבורים אל Google Play Services בשבילך.

באפליקציות שמשתמשות במחלקות החלופיות שבהמשך לא צריך ליצור ניהול GoogleApiClient אובייקטים. ראה גם גישה Google APIs ו דף עזר לסיווג 'גאדג'ט לביש'.

הטבלה הבאה מכילה רכיבים שהוצאו משימוש ואת החלפות:

רכיב שהוצא משימוש רכיב חלופי
CapabilityApi CapabilityClient
Channel ChannelClient.Channel
ChannelApi ChannelClient
DataApi DataClient
MessageApi MessageClient
NodeApi NodeClient

כמו כן, חשוב לשים לב לדברים הבאים:

דוגמה להעברה של אפליקציית Wear

כדוגמה להעברה, קטעי הקוד הבאים ממחישים איך נתוני Wear דוגמת השכבה, המשתמשת ב-Data Layer API, הייתה עודכנה לגרסה 11.8.0 של Google Play Services. אם לאפליקציה יש מודול של טלפון, העדכונים עשויים להיות דומים לאלו של מודול Wear.

עדכון התלות ב-Google Play Services

מכיוון שהאפליקציה עשויה להיות תלויה בגרסה קודמת של Google Play Services, מעדכנים את התלות הבאה בקובץ build.gradle של מודול Wear:

dependencies {
...
compile 'com.google.android.gms:play-services-wearable:11.8.0'
}

עדכון הצהרות הייבוא של האפליקציה

לייבא את הכיתות הנדרשות, כולל כיתות ב-Tasks API.

לדוגמה, בעבר שכבת הנתונים של Wear דוגמה כללה את הצהרת הייבוא הבאה קובץ MainActivity.java. דף החשבון הזה של import יש להסיר:

Kotlin

...
import com.google.android.gms.common.api.GoogleApiClient
...

Java

...
import com.google.android.gms.common.api.GoogleApiClient;
...

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

Kotlin

...
import com.google.android.gms.tasks.Tasks
import java.util.concurrent.ExecutionException
...

Java

...
import com.google.android.gms.tasks.Tasks;
import java.util.concurrent.ExecutionException;
...

הטמעת ממשקי הלקוח החדשים

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

הפעילות העיקרית של מדגם Wear Data Layer (כפי שמצוין בהבדלים) ב- GitHub). ממשק CapabilityApi.CapabilityListener. אבל עכשיו, הטמעה של הפעילות העיקרית CapabilityClient.OnCapabilityChangedListener

בהמשך מוצגת השוואה בין הגדרות הכיתה.

קטע טקסט לפני השימוש בגרסה 11.8.0 של Google Play שירותים:

Kotlin

class MainActivity :
        Activity(),
        GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener,
        DataApi.DataListener,
        MessageApi.MessageListener,
        CapabilityApi.CapabilityListener

Java

public class MainActivity extends Activity implements
  ConnectionCallbacks,
  OnConnectionFailedListener,
  DataApi.DataListener,
  MessageApi.MessageListener,
  CapabilityApi.CapabilityListener

הנה קטע טקסט אחרי השימוש בגרסה 11.8.0 של Google Play שירותים:

Kotlin

class MainActivity :
        Activity(),
        DataClient.OnDataChangedListener,
        MessageClient.OnMessageReceivedListener,
        CapabilityClient.OnCapabilityChangedListener

Java

public class MainActivity extends Activity implements
  DataClient.OnDataChangedListener,
  MessageClient.OnMessageReceivedListener,
  CapabilityClient.OnCapabilityChangedListener

הסרה והוספה של מאזינים

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

למטה מופיע קטע טקסט משכבת הנתונים המתוקנת של Wear דוגמה:

Kotlin

override fun onResume() {
    super.onResume()
    Wearable.getDataClient(this).addListener(this)
    Wearable.getMessageClient(this).addListener(this)
    Wearable.getCapabilityClient(this)
            .addListener(
                    this,
                    Uri.parse("wear://"),
                    CapabilityClient.FILTER_REACHABLE
            )
}

override fun onPause() {
    super.onPause()
    Wearable.getDataClient(this).removeListener(this)
    Wearable.getMessageClient(this).removeListener(this)
    Wearable.getCapabilityClient(this).removeListener(this)
}

Java

@Override
protected void onResume() {
  super.onResume();
  Wearable.getDataClient(this).addListener(this);
  Wearable.getMessageClient(this).addListener(this);
  Wearable.getCapabilityClient(this)
  .addListener(
    this, Uri.parse("wear://"), CapabilityClient.FILTER_REACHABLE);
}

@Override
protected void onPause() {
  super.onPause();
  Wearable.getDataClient(this).removeListener(this);
  Wearable.getMessageClient(this).removeListener(this);
  Wearable.getCapabilityClient(this).removeListener(this);
}

בקשת מידע באמצעות Tasks API

כדאי לבקש מידע מחוץ למאזינים שיעדכנו לאפליקציה כאשר יש שינוי בנתונים. במקרים כאלה, אפשר לשלוח בקשה באמצעות לקוח כמו DataClient, בשילוב עם Tasks API ו- מחלקה של תוצאה (כלומר, Task<ResultType>).

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

Kotlin

private fun showNodes(vararg capabilityNames: String) {
    Wearable.getCapabilityClient(this)
            .getAllCapabilities(CapabilityClient.FILTER_REACHABLE).apply {
                addOnSuccessListener { capabilityInfoMap ->
                    val nodes: Set<Node> = capabilityInfoMap
                            .filter { capabilityNames.contains(it.key) }
                            .flatMap { it.value.nodes }
                            .toSet()
                    showDiscoveredNodes(nodes)
                }
            }
}

private fun showDiscoveredNodes(nodes: Set<Node>) {
    val nodesList: Set<String> = nodes.map { it.displayName }.toSet()
    val msg: String = if (nodesList.isEmpty()) {
        Log.d(TAG, "Connected Nodes: No connected device was found for the given capabilities")
        getString(R.string.no_device)
    } else {
        Log.d(TAG, "Connected Nodes: ${nodesList.joinToString(separator = ", ")}")
        getString(R.string.connected_nodes, nodesList)
    }
    Toast.makeText(this@MainActivity, msg, Toast.LENGTH_LONG).show()
}

Java

private void showNodes(final String... capabilityNames) {
  Task<Map<String, CapabilityInfo>> capabilitiesTask =
    Wearable.getCapabilityClient(this)
            .getAllCapabilities(CapabilityClient.FILTER_REACHABLE);
  capabilitiesTask.addOnSuccessListener(new
    OnSuccessListener<Map<String, CapabilityInfo>>() {
      @Override
      public void onSuccess(Map<String, CapabilityInfo>
        capabilityInfoMap) {
          Set<Node> nodes = new HashSet<>();
          if (capabilityInfoMap.isEmpty()) {
            showDiscoveredNodes(nodes);
            return;
          }
          for (String capabilityName : capabilityNames) {
            CapabilityInfo capabilityInfo = capabilityInfoMap.get(capabilityName);
            if (capabilityInfo != null) {
              nodes.addAll(capabilityInfo.getNodes());
            }
          }
          showDiscoveredNodes(nodes);
      }
  });
}

private void showDiscoveredNodes(Set<Node> nodes) {
  List<String> nodesList = new ArrayList<>();
  for (Node node : nodes) {
    nodesList.add(node.getDisplayName());
  }
  LOGD(TAG, "Connected Nodes: " + (nodesList.isEmpty()
    ? "No connected device was found for the given capabilities"
    : TextUtils.join(",", nodesList)));
  String msg;
  if (!nodesList.isEmpty()) {
    msg = getString(R.string.connected_nodes, TextUtils.join(", ", nodesList));
  } else {
    msg = getString(R.string.no_device);
  }
  Toast.makeText(MainActivity.this, msg, Toast.LENGTH_LONG).show();
}

לקוד נוסף שמשתמש בממשקי ה-API של Wearable ו-Tasks: שכבת נתונים של Wear דוגמה. דוגמה לשימוש במשימות כבדות מחוץ לשרשור של ממשק המשתמש או בשירות, יש אפשרות אחרת. כאן יש דוגמה איך חוסמים משימה ומקבלים את התוצאה באופן סינכרוני:

Kotlin

override fun doInBackground(vararg params: Asset): Bitmap? {
    if (params.isNotEmpty()) {
        val asset = params[0]
        val getFdForAssetResponseTask: Task<DataClient.GetFdForAssetResponse> =
                Wearable.getDataClient(applicationContext).getFdForAsset(asset)
        return try {
            // Block on a task and get the result synchronously. This is generally done
            // when executing a task inside a separately managed background thread. Doing
            // this on the main (UI) thread can cause your application to become
            // unresponsive.
            val getFdForAssetResponse: DataClient.GetFdForAssetResponse =
                    Tasks.await(getFdForAssetResponseTask)
            getFdForAssetResponse.inputStream?.let { assetInputStream ->
                BitmapFactory.decodeStream(assetInputStream)
            } ?: run {
                Log.w(TAG, "Requested an unknown Asset.")
                null
            }

        } catch (exception: ExecutionException) {
            Log.e(TAG, "Failed retrieving asset, Task failed: $exception")
            return null
        } catch (exception: InterruptedException) {
            Log.e(TAG, "Failed retrieving asset, interrupt occurred: $exception")
            return null
        }

    } else {
        Log.e(TAG, "Asset must be non-null")
        return null
    }
}

override fun onPostExecute(bitmap: Bitmap?) {
    bitmap?.also {
        Log.d(TAG, "Setting background image on second page..")
        moveToPage(1)
        assetFragment.setBackgroundImage(it)
    }
}

Java

@Override
protected Bitmap doInBackground(Asset... params) {
  if (params.length > 0) {
    Asset asset = params[0];
    Task<DataClient.GetFdForAssetResponse> getFdForAssetResponseTask =
      Wearable.getDataClient(getApplicationContext()).getFdForAsset(asset);
    try {
      // Block on a task and get the result synchronously. This is generally done
      // when executing a task inside a separately managed background thread. Doing
      // this on the main (UI) thread can cause your application to become
      // unresponsive.
      DataClient.GetFdForAssetResponse getFdForAssetResponse =
        Tasks.await(getFdForAssetResponseTask);
      InputStream assetInputStream = getFdForAssetResponse.getInputStream();
      if (assetInputStream != null) {
        return BitmapFactory.decodeStream(assetInputStream);
      } else {
        Log.w(TAG, "Requested an unknown Asset.");
        return null;
      }

    } catch (ExecutionException exception) {
      Log.e(TAG, "Failed retrieving asset, Task failed: " + exception);
      return null;
    } catch (InterruptedException exception) {
      Log.e(TAG, "Failed retrieving asset, interrupt occurred: " + exception);
      return null;
    }
  } else {
    Log.e(TAG, "Asset must be non-null");
    return null;
  }
}

@Override
protected void onPostExecute(Bitmap bitmap) {
  if (bitmap != null) {
    LOGD(TAG, "Setting background image on second page..");
    moveToPage(1);
    assetFragment.setBackgroundImage(bitmap);
  }
}