«

»

Dec 08

Service

Services:

 
 

רקע תאורטי:

 
Service זה חלק מהאפליקציה שרץ ברקע, בברירת מחדל הוא רץ לא ב process נפרד אלא על אותו process של האפליקציה, אבל הוא גם לא כחלק מ Activity יחיד. (service הוא מושג יחודי של android)
מתודות המחזור חיים של ה service רצות על ה main thread (תהליכון ה UI) של ה process של האפליקציה שמריצה אותו ולכן עדיף שלא לבצע פעולות IO או פעולות שהם blocking, אלא לפתוח ממנו תהליכון חדש שיבצע את הפעולות הללו.
 

נשתמש ב service כאשר רוצים:

1. לחשוף חלק מהפונקציונליות של האפליקציה שלך לאפליקציות אחרות.
2. בשביל לתקשר בין ה activities באפליקציה שלך.
3. בשביל לבצע פעולות ארוכות שלא דורשות את המשתמש.
 

גם ל service יש מחזור חיים ויש לו onCreate וגם onDestroy.

ב onDestroy חשוב לעצור ולהרוג את תהליכונים שרצים, לעשות unregister ל broadcast receivers וכו’.

 

במאמר זה נתיחס רק ל Services שהם באותו ה Process של שאר האפליקציה שלך.

לגבי הנושא של Remove Services – Services שהם לא באותו ה Process שלך, אני מקדיש מאמר נפרד.
אני מקדיש מאמר נפרד גם לגבי Intent Services – Services ספציפיים עם תהליכון Worker מובנה.
 
 

ישנם שני מצבים שבהם Service יכול להיות: Started ו Bound:

 

Service שהוא Started:

בשביל להפעיל service נקרא ל:

Intent intentToService = new Intent(<context>, <ServiceClassName.class>);
<context>.startService(intentToService);

ומה שבפועל יקרה הוא שתקרא מתודת ה onCreate של ה service ואז תקרא מתודת ה onStartCommand.
 
כאשר רוצים לסגור את ה service אפשר לעשות זאת בשתי דרכים:
1. דרך Activity:

Intent intentToService = new Intent(<context>, <ServiceClassName.class>);
<context>.stopService(intentToService);

2. דרך ה Service עצמו ע”י קריאה מתוך ה service למתודה:

stopSelft();

יש לשים שכמה קריאות רצופות ל startService אכן יפעילו כל פעם את ה onStartCommand אבל בשביל לסגור את ה service מספיק לקריאה אחת ל stopService, לא משנה כמה פעמים לפני קראנו ל startService.
 
 
מערכת יכולה להרוג את התהליך של האפליקציה שלך בזמן שה Service שלך רץ כאשר מערכת ההפעלה בלחץ של זכרון.
 
אפשר לקבוע כמה סוגי התנהגויות ל services במקרה שמערכת ההפעלה הרגה את ה Service בזמן שהוא רץ (שכבר נקראה המתודה של ה onStartCommand) קובעים אותם ע”י מה שאנחנו מחזירים מ onStartCommand:

 

START_STICKY

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

 

START_NOT_STICKY

מערכת ההפעלה לא תיצור מופע חדש של ה service גם כאשר יהיה לה מספיק זכרון פנוי.
דוגמא למצב שמתאים להשתמש בזה – Service שמפעילים אותו כל זמן קצוב ולא מפריע שהוא לא יסיים את העבודה שלו אם הוא נהרג כי הוא יותחל שוב:
כאשר אנחנו רוצים לעדכן מידע מהשרת כל כמה דקות – ניצור Service ששולף באופן קבוע ללא תלות בפרמטרים מהשרת
נפעיל alarm וב OnStartCommand נפעיל עוד אחת לעוד כמה דקות.
אם הוא יפול בגלל שמערכת ההפעלה צריכה זכרון, אז נחמיץ דגימה אחת, ובעוד כמה דקות הוא יורץ שוב פעם.

 

START_REDELIVER_INTENT

מערכת ההפעלה כאשר יהיה לה מספיק זכרון תפעיל את ה service עם ה intent המקורי שהפעיל אותו. (או intents אחרים שחיכו להפעיל אותו – pending intents).
דוגמא למצב שמתאים להשתמש בזה – כאשר יש לנו Service שמקבל Intent שלפיו הוא מוריד קבצים גדולים שונים מהשרת. אם התהליך שלנו נהרג במהלך ההורדה, נרצה שכאשר יהיה מספיק זכרון ה Service יחזור לפעול וינסה להוריד את הקובץ הספציפי לפי ה Intent ששלחנו מחדש.

 

 

Service שהוא Bound:

 

אפשר להשתמש ב
activityToBind.bindService(<intent_to_the_service>, <service_connection_object>, <bind_type_flag>)

בשביל ליצור service שאפשר לגשת אליו.
בדרך יצירה זאת, אם ה service עדיין לא נוצר אז נקרא ה onCreate שלו אבל לא ה onStartCommant, והמשתמש יקבל אובייקט IBinder שמאפשר לו לעשות קריאות בחזרה לאותו מופע של ה service.
הערה: אני לא ארחיב על מספר ה flags השונים שאפשר להעביר ל service מכיוון שהם רק החל מ API14 ומעלה.
 

נקבל את הרפרנס ל service:

בצד ה Service תקרא מתודת ה

onBind(intent)

ובצד ה Activity נקבל את המשתנה ל Service דרך מתודת ה onServiceConnected באובייקט ה ServiceConnection שיצרנו.
 

ישנם שתי מתודות מהותיות ב service שנותנות מידע ל service לגבי ה acitivies שהן bound אליו:

onUnbind – נקרא כאשר כל ה Activities שהיו bound ל service עשו unbind. ז.א. כאשר ה service הוא כבר לא bound. מחזירים true בשביל להודיע למערכת ההפעלה שתקרא ל onUnbind שממומש ב Service

	@Override
	public boolean onUnbind(Intent intent) {

		Toast.makeText(this, "Service onUnbind", Toast.LENGTH_SHORT).show();
		return true;

	}

onRebind – נקרא כאשר onUnbind נקרא. ז.א. שה service הוא unbound. ועכשיו איזה Activity עשה לו bind שוב פעם וה service הפך להיות bound.

	@Override
	public void onRebind(Intent intent) {

		Toast.makeText(this, "Service onRebind", Toast.LENGTH_SHORT).show();

	}

 
 
 
 

דוגמה והסבר:

 

1. הכרזה על service ב manifest:

כל service שאחנו יוצרים יש להכריז עליו ב manifest תחת application ממש בדומה ל activity:
service שרץ באותו התהליך נכריז כך:

        <service android:name=".TestService" />

בשביל להריץ service בתהליך נפרד נכריז:

        <service android:name=".TestService" android:process=":remoteTestService"/>

ב android:process פשוט נבחר לו שם.

אם ה service הוא באותו process אפשר לקבל אותו ע”י המרה ספציפית:

 


2. בצד ה service מגדירים מה יחזור במתודת ה onServiceConnection

 

private final IBinder _binder = new LocalBinder();

בגלל שאנחנו יודעים שה service רץ באותו process אין בעיה להחזיר מחלקה פנימית שמרחיבה את ה Binder:

	    public class LocalBinder extends Binder {
	    	   	TestService getService() {

	            return TestService.this;

	        }
	    }

מחזירים מופע של LocalBinder שה getService שלו מחזיר את המופע של ה service.

	    @Override
	    public IBinder onBind(Intent intent) {

	        return _binder;

	    }

מומלץ ב onDestroy של ה activity לעשות unbind ל service ולנקות את הרפרנס ל service ע”י:

_boundService=null.

 
 


3. בצד ה Activity:

 
הכרזה על ה service שלנו:

private TestService 		_boundService;

 
בקשה לעשות bind של ה service ל Activity:

Intent intentToTheService = new Intent(activityToBind, serviceClassToBind);
activityToBind.bindService(intentToTheService, _serviceConnection, Context.BIND_AUTO_CREATE);

 

כאשר התבצע חיבור ל service נקראת המתודה באובייקט ה ServiceConnection שניצור

private ServiceConnection _serviceConnection = new ServiceConnection() {

	public void onServiceConnected(ComponentName className, IBinder service) {

 

אנחנו יודעים שסוג ה IBinder שיחזור הוא מופע דינמי TestService.LocalBinder

		IBinder binderReturnedFromService = (TestService.LocalBinder) service;
		if(binderReturnedFromService != null) {

 

ומה שיחזור מה LocalBinder.getService הוא מופע דינמי של ה TestService שלנו

			_boundService = ((TestService.LocalBinder) service).getService();

 
 

הערה חשובה:

 

אם ה service מפעיל תהליכונים לבצע פעולה ארוכה ואז ה service נסגר, אז התהליכונים שהוא הריץ ממשיכים לרוץ – למרות שה service מת!

לדוגמא: אם activity קרא ל stopService וה service הוא לא bound אז יקרא ה onDestroy שלו והוא יסגר.
שימו לב, זה יכול לגרום לבעיות, לדוגמא בפרוייקט אחד שעבדתי בו ה service הריץ התהליכונים שמבצעים פעולות מול מסד הנתונים ושהוא נסגר הוא סגר את מסד הנתונים. התהליכונים שפעלו מול מסד הנתונים זרקו חריג שהם מנסים לעשות פעולה מול מסד הנתונים למרות שהוא סגור (ושצריך לקרוא ל open למסד הנתונים).
הדרך שטיפלנו בזה היא להפוך את ה service ל foreground service ושה activities לא יקראו ל close שלו. אלא ה foreground service יסגור את עצמו כאשר אין activity שהוא bound אליו. (אפשר לעשות זה נגיד ע”י ניהול תור של תהליכונים )

 

Service יכול להיות גם started וגם binded:
במקרה הזה המערכת תשאיר את ה service רץ כל עוד הוא started או שיש לו Activity שהוא bound אליו.

ברגע שאף אחד מהתנאים לעיל לא מתקיים יקרא ה onDestroy שלו וה service יסגר.
 
 

לינק להורדת הפרויקט לדוגמה

 

בהצלחה !

2 comments

  1. רני

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

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

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

    - סעיף 1ב, הוא משפט שלא מסתיים. בסוף השורה ישנן נקודותיים, ואחריהן שום דבר
    (“…אפשר לקבל אותו ע”י המרה ספציפית:”)

    - סעיף 2, נפתח בביטוי המכיל מונח (אוביקט) בשם IBinder. מה פישרו?
    (הזכרת לפני, שאוביקט מסוגו מאפשר למשתמש לעשות קריאות בחזרה לאותו מופע של ה service,
    והדגמת שימוש בו בדוגמא, אבל…. מה הוא?)
    - אותו דבר לגבי אוביקט ServiceConnection.
    - למעשה הקוד היבש בדוגמא הוא דיי קריא, והכל כתוב באופן דיי ברור,
    ועדיין – מה בכלל קורה בו? ומאחורי הקלעים? זה ניראה מסועף, מבולגן, וחסרה התעמקות
    טקסטואלית ותיאור פרטני יותר.

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

    1. ליאור זיוי

      היי רני,

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

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

      לגבי התיקונים הסמנטיים, אני אתקן אותם, תודה שהפנת לתשומת ליבי.

      שאלה:
      - סעיף 2, נפתח בביטוי המכיל מונח (אוביקט) בשם IBinder. מה פישרו?
      תשובה:
      זהו האובייקט שנועד לתקשורת שממנו מקבלים את המופע של ה Service.

      שאלה:
      - אותו דבר לגבי אוביקט ServiceConnection.
      תשובה:
      אותו הדבר, זה API שאנחנו צריכים לממש.

      אם אתה מעוניין בעוד מידע, אשמח לעזור לך,
      אתה מוזמן גם לפנות אלי למייל!

      ליאור

Leave a Reply

Your email address will not be published.

אתם יכולים להשתמש באפשרויות ותגי ה-HTMLהבאים: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>