איחוד התראות בנייד

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

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

איחוד התראות קבועות

רכיבים

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


‫Navigation SDK מכיל שיטות סטטיות לאתחול ולקבלת ה-singleton של ForegroundServiceManager. אפשר להפעיל את הסינגלטון הזה רק פעם אחת במהלך חיי ה-Navigation SDK. לכן, אם אתם משתמשים באחת מהקריאות לאתחול (initForegroundServiceManagerMessageAndIntent() או initForegroundServiceManagerProvider()), צריך להקיף אותה בבלוק try-catch למקרה שהנתיב הזה יופעל שוב. אם קוראים לאחת מהשיטות יותר מפעם אחת, ה-Navigation SDK מקפיץ חריגה בזמן הריצה, אלא אם קודם מנקים את כל ההפניות ל-ForegroundServiceManager וקוראים ל-clearForegroundServiceManager() לפני כל קריאה עוקבת.

ארבעת הפרמטרים של initForegroundServiceManagerMessageAndIntent() הם application,‏ notificationId,‏ defaultMessage ו-resumeIntent. אם שלושת הפרמטרים האחרונים הם null, ההתראה היא ההתראה הרגילה של Navigation SDK. עדיין אפשר להסתיר שירותים אחרים שפועלים בחזית באפליקציה מאחורי ההתראה הזו. הפרמטר notificationId מציין את מזהה ההתראה שצריך להשתמש בו להתראה. אם הערך הוא null, המערכת תשתמש בערך שרירותי. אפשר להגדיר אותו באופן מפורש כדי לעקוף התנגשויות עם התראות אחרות, כמו התראות מ-SDK אחר. הערך של defaultMessage הוא מחרוזת שמוצגת כשהמערכת לא מנווטת. ה-resumeIntent הוא Intent שמופעל כשלוחצים על ההתראה. אם הערך של resumeIntent הוא null, המערכת מתעלמת מהקליקים על ההתראה.

שלושת הפרמטרים של initForegroundServiceManagerProvider() הם application,‏ notificationId ו-notificationProvider. אם שני הפרמטרים האחרונים הם null, ההתראה היא ההתראה הרגילה של Navigation SDK. הפרמטר notificationId מציין את מזהה ההתראה שצריך להשתמש בו להתראה. אם הוא null, המערכת משתמשת בערך שרירותי. אפשר להגדיר אותו באופן מפורש כדי לעקוף התנגשויות עם התראות אחרות, כמו התראות מ-SDK אחר. אם מוגדר notificationProvider, הספק תמיד אחראי ליצירת ההתראה שתוצג.

ה-method‏ getForegroundServiceManager() של Navigation SDK מחזירה את ה-singleton של מנהל השירות שפועל בחזית. אם עדיין לא יצרתם מפתח, זה שווה ערך לקריאה ל-initForegroundServiceManagerMessageAndIntent() עם פרמטרים ריקים ל-notificationId, ל-defaultMessage ול-resumeIntent.

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

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

תרחישי שימוש

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

הסתרת התראות קבועות של שירותים שפועלים בחזית של אפליקציות אחרות
התרחיש הכי פשוט הוא לשמור על ההתנהגות הנוכחית, ולהשתמש בהתראה הקבועה רק כדי להציג מידע מ-Navigation SDK. שירותים אחרים יכולים להסתתר מאחורי ההתראה הזו באמצעות השיטות startForeground() ו-stopForeground() של מנהל השירות שפועל בחזית.
הסתרת התראות קבועות של שירותים אחרים שפועלים בחזית, אבל הגדרת טקסט ברירת מחדל שמוצג כשלא מנווטים
התרחיש השני הכי פשוט הוא לשמור על ההתנהגות הנוכחית, ולהשתמש בהתראה הקבועה רק כדי להציג מידע מ-Navigation SDK, אלא אם המערכת לא מנווטת. כשהמערכת לא מנווטת, מוצגת המחרוזת שסופקה ל-initForegroundServiceManagerMessageAndIntent() במקום מחרוזת ברירת המחדל של Navigation SDK שמזכירה את "מפות Google". אפשר גם להשתמש בקריאה הזו כדי להגדיר את ה-intent להמשך, שמופעל כשלוחצים על ההתראה.
שליטה מלאה בעיבוד של ההתראה הקבועה
בתרחיש האחרון צריך להגדיר וליצור ספק התראות ולהעביר אותו אל ForegroundServiceManager באמצעות initForegroundServiceManagerProvider(). האפשרות הזו נותנת לכם שליטה מלאה על מה שמוצג בהתראה, אבל היא גם מנתקת את פרטי ההתראה של Navigation SDK מההתראה, וכך מסירה את ההנחיות המפורטות והמועילות שמוצגות בהתראה. ‫Google לא מספקת דרך פשוטה לאחזר את המידע הזה ולהוסיף אותו להתראה.

ספק התראות לדוגמה

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

public class NotificationContentProviderImpl
   extends NotificationContentProviderBase
   implements NotificationContentProvider {
 private String channelId;
 private Context context;
 private String message;

 /** Constructor */
 public NotificationContentProviderImpl(Application application) {
   super(application);
   message = "-- uninitialized --";
   channelId = null;
   this.context = application;
 }

 /**
  * Sets message to display in the notification. Calls updateNotification
  * to display the message immediately.
  *
  * @param msg The message to display in the notification.
  */
 public void setMessage(String msg) {
   message = msg;
   updateNotification();
 }

 /**
  * Returns the notification as it should be rendered.
  */
 @Override
 public Notification getNotification() {
   Notification notification;

   if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
     Spanned styledText = Html.fromHtml(message, FROM_HTML_MODE_LEGACY);
     String channelId = getChannelId(context);
     notification =
         new Notification.Builder(context, channelId)
             .setContentTitle("Notifications Demo")
             .setStyle(new Notification.BigTextStyle()
                 .bigText(styledText))
             .setSmallIcon(R.drawable.ic_navigation_white_24dp)
             .setTicker("ticker text")
             .build();
   } else {
     notification = new Notification.Builder(context)
         .setContentTitle("Notification Demo")
         .setContentText("testing non-O text")
         .build();
   }

   return notification;
 }

 // Helper to set up a channel ID.
 private String getChannelId(Context context) {
   if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
     if (channelId == null) {
       NotificationManager notificationManager =
           (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
       NotificationChannel channel = new NotificationChannel(
           "default", "navigation", NotificationManager.IMPORTANCE_DEFAULT);
       channel.setDescription("For navigation persistent notification.");
       notificationManager.createNotificationChannel(channel);
       channelId = channel.getId();
     }
     return channelId;
   } else {
     return "";
   }
 }
}

אחרי שיוצרים את NotificationContentProviderImpl, מקשרים אליו את Navigation SDK באמצעות הקוד הבא:

ForegroundServiceManager f = NavigationApi.getForegroundServiceManager(getApplication());
mNotification = new NotificationContentProviderImpl(getApplication());
NavigationApi.clearForegroundServiceManager();
NavigationApi.initForegroundServiceManagerProvider(getApplication(), null, mNotification);

אזהרות ותוכניות לעתיד

  • חשוב להפעיל את initForegroundServiceManagerMessageAndIntent() או את initForegroundServiceManagerProvider() בשלב מוקדם כדי שתרחיש השימוש הצפוי יהיה מוגדר היטב. צריך להפעיל את ה-method הזו לפני שיוצרים אובייקט Navigator חדש.
  • חשוב לטפל בחריגות בקריאות ל-initForegroundServiceManagerMessageAndIntent() או ל-initForegroundServiceManagerProvider(), למקרה שהקוד מופעל יותר מפעם אחת. בגרסה 2.0 של Navigation SDK, קריאה לשיטה הזו כמה פעמים גורמת לחריגה מסוג checked exception במקום לחריגה מסוג runtime exception.
  • יכול להיות ש-Google עדיין תצטרך לעבוד כדי לקבל סגנון עקבי לאורך כל משך ההתראה שתואם לסגנון הכותרת.
  • כשמגדירים ספק התראות, אפשר לשלוט בהתנהגות של ההתראות הקופצות באמצעות העדיפות.
  • ‫Google לא מספקת דרך פשוטה לאחזור מידע על מסלול מפורט שספק ההתראות יכול להוסיף להתראה.