- הגדרנו את המושג Service באחד הפוסטים הראשונים כפעילות ללא ממשק למשתמש. ל-Service יש מחזור חיים שונה משל ה-Activity, כך שגם אם המסך של ה-Activity שהפעיל את ה-Service יורד מהתצוגה, ה-Service יכול להמשיך לפעול. זאת בשונה מה-Activity שכידוע, עלול להיות מחוסל אם אינו מופיע ב-Foreground של התצוגה. דוגמא לשימוש אפשרי ב-service: נגן. נרצה שהנגן ימשיך לפעול גם אם המסך שהפעיל אותו כבר ירד מהתצוגה. אחת המתודות של ה-Service Class בה רצוי להשתמש במצב כזה היא ה-startForeground. המתודה הזו מפעילה דגל שמודיע שמסמן לא להרוג את ה-service אחרת תפגע פעולתו.
- ה-Service למעשה יכול לשמש אמצעי למענה על שני צרכים של המערכת:
- הצורך הראשון, ביצוע פעילות רקע של המערכת (background) שאינה קשורה ישירות לתצוגה שב-UI.
- הצורך השני, תקשורת בין Processes שונים במערכת
- (IPC - Inter Process Communication או בשם אחר שנפוץ בהרבה מערכות הפעלה RPC - Remote Process Communication) .
- מה זה IPC ולשם מה בכלל הוא נחוץ? על זה נדון בפוסט שייוחד ל-IPC.
- בפוסט הנוכחי נתרכז בשימוש הראשון של ה-service עליו דיברנו: הרצת פעילויות ברקע.
- אחת הנקודות החשובות אותה הייתי רוצה לדגיש היא שה- Service במהותו אינו "background task" במובן שהוא באופן אוטומטי רץ ב-Thread שונה מזה של ה-Activity שהפעיל אותו. בכלל לא - ה-service ירוץ באותו Thread שהפעיל אותו, בד"כ ה- UI Thread, ולפיכך הוא לא יוריד את העומס מה- Thread הראשי, (offload).
- מצד שני, ה-Service אכן יכול להריץ את פעילותו ב-Thread עצמאי, אותו יש לצור, ובכך להוריד עומס (= בעברית לעשות Offload ), מה- UI Thread.
- נציג דוגמא מעניינת לשימוש ב-service.
- האפליקציה מכילה שני classes: ה- myActivator שהוא subclass של Activity , המטפל ב-UI ו myService שהוא subclass של Service שיופעל ע"י ה- Activity. ה-Service יבצע ברקע פעילות אינטנסיבית - Loop באורך מליון, תוך כדי חיבור את כל המספרים מ1 עד מיליון.הפעילות הזו תתבצע ב-Thread עצמאי כך שה-UI יהיה זמין למשתמש כל הזמן. כל זה יומחש בזמן הריצה בעזרת הודעות על המסך, כולל נושא ה-LifeCycle של ה-Service.
- למרות שמיותר, אזכיר את הקישור להורדת קבצי הפרויקט שנמצא בתחתית העמוד.
- אני מציע שנעיף מבט על התוכנית, ונתחיל עם ה-Service. בתוך ה-class ממומשות סה"כ 4 מתודות + class קטן מסוג Thread. תוכן הקובץ של ה-Service מובא כאן ומיד אחריו הסברים:
- public class myService extends Service{
- Integer sum = 0;
- boolean isThreadOn = false;
- public final String TAG = "myService";
- @Override
- public void onCreate() {
- super.onCreate();
- Toast.makeText(this,"onCreate", Toast.LENGTH_LONG).show();
- Log.d(TAG," onCreate");
- }
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if(!isThreadOn)
- {
- isThreadOn = true;
- SumCalc sumCalc = new SumCalc();
- sumCalc.start();
- Toast.makeText(this,"onStartCommand. Run New Thread", Toast.LENGTH_LONG).show();
- }
- else
- Toast.makeText(this,"onStartCommand. sum is:" + sum, Toast.LENGTH_LONG).show();
- return START_STICKY;
- }
- @Override
- public void onDestroy() {
- super.onDestroy();
- Toast.makeText(this, "The Service was destroyed ...", Toast.LENGTH_LONG).show();
- Log.d(TAG," onDestroy");
- }
- @Override
- public IBinder onBind(Intent arg0) {
- return null;
- }
- public class SumCalc extends Thread {
- public void run() {
- sum = 0;
- for(Integer idx = 0; idx< 1000000; idx ++)
- {
- sum++;
- }
- isThreadOn = false;
- }
- }
- }
- 3 מתודות הן callbacks הקשורות ל-Lifecycle של ה- Service:
- onCreate-מופעלת בפעם אחת בלבד עם יצירת האובייקט, כתוצאה מהפעלת startService. הפעלות נספות של startService לא יפעילו את onCreate אלא אם ה-Servcie נעצר, למשל עקב stopService.
- onStartCommand- מופעלת עבור כל הפעלה של- startServce.
- onDestroy - מופעלת כ-callback של stopService.
כל אחת מ-3 ה-callback הנ"ל מדפיס הודעות למסך, בעזרתן ניתן לעקוב אחרי ה-LifeCycle של ה-Service.
- ה-Service אמנם לא קשור ל-UI אך ניתן להציג הודעות תוך שימוש ב-Toast class. ה-Toast Notification היא הודעה שקופצת על המסך לזמן מוגבל ונעלמת. היא יכולה להשלח מ- Service או מ-Activity.
- ב-onCreate (שורה 7) אין שום דבר מיוחד. כאמור יש Toast Notification אותה נראה רק בהפעלה הראשונה של ה-Service או אחרי שנהרוס אותו. אגב, שורה 10 מכילה הדפסת הודעה ל- Logger. אפשר לראות הודעות אלה ב- IDE תוך שימוש ב- LogCat.
- ה- onDestroy גם הוא טריוויאלי. גם הוא כולל הדפסת Toast Notification.
- נעבור ל-callBack השלישי והאחרון : onStartCommand, שהוא המקבילה של onStart ב- Activity, ומופעל בכל פעם שמפעילים את startService. המימוש אצלנו כולל הפעלת ה- Thread שמבצע את ה-Loop, והדפסת הודעות למסך. נציג את המתודה שוב:
- public int onStartCommand(Intent intent, int flags, int startId) {
- if(!isThreadOn)
- {
- isThreadOn = true;
- SumCalc sumCalc = new SumCalc();
- sumCalc.start();
- Toast.makeText(this,"onStartCommand. Run New Thread", Toast.LENGTH_LONG).show();
- }
- else
- Toast.makeText(this,"onStartCommand. sum is:" + sum, Toast.LENGTH_LONG).show();
- return START_STICKY;
- }
בשורה 2 בודקים את הדגל isThreadOn. אם הוא במצב true, ה-Thread רץ ברקע ולא ניצור instance נוסף שלו. אחרת: ניצור instance חדש של ה-Thread ונריץ אותו (שורות 5,6). שורה 4 משנה את מצב הדגל לסמן שה-Thread רץ (כאמןר, כדי לא להריץ אותו יותר מפעם בו זמנית), ושורה 8 מדפיסה למסך הודעה מתאימה.
- אם הדגל isThreadOn היה במצב true, לא היינו מבצעים דבר מלבד הדפסת המשתנה sum: ערכו הרגעי של הסכום המחושב ע"י ה-Thread. (שורה 10).
- את המתודה הרביעית, onBind, חייבים לממש בכל Service. היא קשורה לנושא ה-IPC - Inter Process Communication, כך שנסביר את תפקידה בפוסט על ה-IPC. בכל אופן, היות שאצלינו אין שימוש ב- IPC היא מחזירה false תמיד.
- בנוסף ל4 המתודות, שורה 39, class ה- -Thread בו מתבצע ה-loop.
- שורה 13: סיום המתודה. return START_STICKY; משמעות ה START_STICKY היא, שבמקרה שה-service יחוסל (killed) אחרי הפעלתו, הוא ישאר במצב "started". מאוחר יותר שה-service יוחזר לתפקוד, ו-onCreate תופעל שוב, יופעל גם ה-onStartCommand. לעומת זאת קיימים ערכים אחרים לחזרה למשל:
START_NOT_STICK: במקרה שה-service יופעל לאחר שהרגו אותו, ה- onStartCommand לא יופעל שוב, אלה אם יש בתור פקודות onStartCommand ממתינות.
- ולסיום, ה-Thread: לופ פשוט שמתבצע מיליון פעם (42-45). בסוף הביצוע הדגל isThreadOn מועבר מצב כדי לאפשר הפעלה נוספת של ה-Thread.
- public class SumCalc extends Thread {
- public void run() {
- sum = 0;
- for(Integer idx = 0; idx< 1000000; idx ++)
- {
- sum++;
- }
- isThreadOn = false;
- }
- }
- }
- סיימנו עם ה-service subclass ונעבור ל- Activity subclass האחראי על ה-UI, וממנו מופעל ה- Service ומכאן שמו: myActivator. הוא באמת טריויאלי - בטח למי שעיין לפחות במקצת מהפוסטים הקודמים.
- מוגדרים בו שני כפתורים + callback לכל אחד מהם.
- עבור כפתור Service Start מבצע ה-callback של הכפתור הפעלה של ה-service (שורה 3):
- buttonServiceStart.setOnClickListener(new OnClickListener() {
- public void onClick(View v){
- startService(new Intent(myActivator.this,myService.class));
- }
- });
- ההפעלה מתבצעת ע"י Intent שנשלח ל - myService.class.
- הפעולות שמתבצעות עם לחיצה על ה- Stop Service מתוארות בשורות הבאות:
- buttonServiceDestroy.setOnClickListener(new OnClickListener() {
- public void onClick(View v){
- stopService(new Intent(myActivator.this,myService.class));
- }
- });
- ושוב, קריאה ל- stopService ששולח Intent (שורה 5).
- הריצו את התוכנית, ועיקבו אחרי ההודעות שנשלחות מהמתודות של ה- Service:
- לחצו על כפתור ה- Start. התתקבל הודעה על המסך שנשלחה מה- onCreate ואחריה מה- onStart.
- לחצו שוב על ה-Start. הפעם רק הודעת ה-onStart תופיע. המשיכו ללחוץ על 1 וצפו בהגדלת הערך של sum מלחיצה ללחיצה. אם ה-Thread סיים עבודתו, תתקבל הודעה על הפעלת Thread חדש.
- לחצו על כפתור ה- Stop וצפו בהודעה מה- onDestroy.
- לחצו שוב על ה- start.יתקבלו הודעות כמו בשלב 1.
קישור לקבצים.
נ.ב.
גם service חייבים לרשום ב- manifest.XML:
נ.ב.
גם service חייבים לרשום ב- manifest.XML:
<service android:name=".myService">
</service>
</service>
בהצלחה!
היי,
השבמחקמצטער - אני לא מתכוון לבקר כל פירסום שלך, להיפך אני שואב הרבה ידע מהם - אם זאת יש נקודה ש"מפחידה" אותי בקוד שלך.
המימוש לא מרגיש לי ThreadSafe - שימוש בערך בוליאני בצורה כזאת לא מבטיח מניעה של גישה מחוטים שונים ?
תמיד אפשר לגרום לדברים להיות אוטונומים על ידי שימוש ב-synchronized
מחקתודה אחי עזרת לי מאוד!
השבמחקתודה! מאוד מועיל! רציתי לשאול אם אני יוצר service שמפעיל GPS וכולל בתוכה פונ' שמחזירה את המיקום הנוכחי. איך אפשר לקרוא לפונ' שבתוך הservice?
השבמחקתודה