«

»

Jan 26

Remote Service

Remote Service

אייקון - Remote
 

רקע תאורטי:

 

Process-ים (תהליכים) שונים באנדרואיד לא חולקים את אותו מרחב הזכרון ולא יכולים לגשת אחד למרחב הזכרון של השני.

הם מתקשרים ביניהם בתהליך הנקרא IPC:

IPC – Inter Process Communication.

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

פעולה זאת נקראת Serialization או Marshalling או באנדרואיד: Parceling (בדיוק כמו התהליך שקורה כאשר מעבירים אובייקטים בין Activities, הם צריכים לממש את Parcelable).

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

 

בשביל להגדיר את ה Interface הזה בין התהליכים נשתמש בקובץ AIDL.

AIDL – Android Interface Definition Language

קובץ ה AIDL מגדיר את הממשק שאנחנו רוצים שיקשר בין ה Service המרוחק ל Activity שלנו.

אפשר להעביר בממשק זה את כל הטיפוסים הפרימיטיביים, String, CharSquence, List, Map וכל אובייקט אחר שמממש את הממשק Parcelable.

 

Binder Thread:

לכל תהליך שתצרו באנרואיד, אם תריצו אותו וב Eclipse תפתחו את ה Debug. תראו שיש 2 תהליכונים שנוצרים:

Binder Thread #1

Binder Thread #2

מערכת ההפעלה של אנדרואיד מקצה לכל תהליך Pool של תהליכונים, של לפחות 2 תהליכוני Binder. אחד התפקידים שלהם הוא לטפל בקריאות של IPC בין תהליכים. ז.א.כאשר מתודה שהוגדרה ב AIDL בתהליך נקראת ע”י תהליך אחר דרך ה IPC. אז אחד מה Binder Thread יריצו אותה.

 

כאשר מתבצעת קריאה מתהליכון א’ בתהליך א’ למתודה בתהליך ב’:

1. מערכת ההפעלה תעביר את כל הפרמטרים שהוגדרו במתודה כ in או inout (אני אסביר בהמשך המאמר על סוגי הפרמטרים השונים) בקובץ ה AIDL את תהליך הסריאליזציה ותעביר אותם לתהליך ב’. (הפרמטרים כמובן לא עוברים by reference מכיוון שהתהליכים לא חולקים את אותו מרחב זכרון, ולכן נעשה תהליך הסריאליזציה)

2. בתהליך ב’ יוקצה אחד מתהליכוני ה Binder שלו לטפל בבקשה – במתודה.

בזמן הזה תהליכון א’ יחכה ולא ימשיך לרוץ, עד אשר תהליכון ה Binder של תהליך ב’ יסיים את הטיפול במתודה.

3. עם סיום המתודה מערכת ההפעלה תהפוך את כל הפרמטרים שהוגדרו במתודה כ out או inout בקובץ ה AIDL את תהליך הסריאליזציה ותעביר אותם בחזרה להתהליך א’.

4. מערכת ההפעלה תשנה את האובייקטים שהוגדרו כ out או in בזכרון של תהליך א’ ותהליכון א’ ימשיך לרוץ.

 

יש לשים לב:

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

2. נותנים לנו לבחור את סוג הפרמטרים המועברים ולא קובעים שכולם יהיו inout מכיוון שישנה עלות לא קטנה בלהעביר אובייקטים בין תהליכים מכיוון שתהליך הפרסור (Serialization/Deserialization) אינו פשוט ואם לא צריך אותו נותנים למפתח את האפשרות לציין את זה בשביל לחסוך זמן ועבודה מיותרת.

3. בקובץ ה AIDL אפשר להגדיר פרמטר כ out או inout רק אם הוא אובייקט לא פרימיטיבי.

4. רק כאשר מדובר ב Remote Service אפשר לקבוע שתהליכון א’ לא יצטרך לחכות לסיום המתודה ע”י תהליכון ב’ בתהליך ב’. בשביל זה צריך לקבוע את הממשק כ oneway. זאת עושים ע”י הגדרה בקובץ ה AIDL:

oneway interface IRemoteServiceOnewayAIDL {
    // הגדרת מתודות void
}

החסרון הוא שכל המתודות בממשק שמוגדר כ oneway צריכות להיות void ולא יכולות להחזיר ערכים.

ובנוסף גם אם נגדיר את הפרמטרים שיועברו כ out או inout הערכים שלהם עדיין לא ישתנו ולכן כל הפרמטרים המועברים הם בפועל כ in.

 

הערה: מוזר לי למה יש את המגבלה הזאת ולא יכולים להגדיר באופן ספציפי מתודות
void כ oneway. אבל ככה זה עובד ואם מישהו יודע למה זה אני אשמח אם הוא יכתוב תגובה למאמר.

 

תהליך הפיתוח:

1. הגדרת ושמירת קובץ ה AIDL – לדוגמה IRemoteServiceAIDL.aidl

יווצר לנו בתיקיית ה gen קובץ java שהוא ההמשק שהשם שלו הוא השם שקבענו ל AIDL (רק עם סיומת java). לדוגמה IRemoteServiceAIDL.java

2. כתיבת ה RemoteService ומימוש ה AIDL:

    2.1. הגדרה ב Manifest שה RemoteService שירוץ על תהליך נפרד ולא על אותו תהליך של האפליקציה שלנו.

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

     2.2. מימוש ה Binder ה stub שנוצר ב gen: IRemoteServiceAIDL.Stub.

     2.3. החזרת המימוש שלנו במתודה onBind של ה Service.

3. כתיבת ה Client (בדוגמא, ה Activity) שיתחבר ל Service כדי שנוכל לקרוא למתודות שהוגדרו ב AIDL.

 
 
 

דוגמה והסבר:

RemoteService Code Structure

 

1. הגדרת ושמירת קובץ ה AIDL – לדוגמה IRemoteServiceAIDL.aidl

אז ראשית כל נגדיר את הממשק שאנחנו רוצים שיקשר בין ה Service המרוחק ל Activity שלנו. ניצור את קובץ ה AIDL באחת מתיקיות ה source שלנו – ניצור קובץ text רגיל רק עם סיומת .aidl ונגדיר אותו:

package co.il.lior.remote_process_example;

import co.il.lior.remote_process_example.ParcelableClass;

interface IRemoteServiceAIDL {

	void passObject(in ParcelableClass parcelableClass);

	void passString(in String name);

	String getString();

	void testOutParam(in int[] inparams, out int[] outparams);

	void testInOutParam(inout int[] theOldInAndOut);

	void checkChangingValues(inout int[] values);

}

בקובץ ה AIDL צריך בשורה הראשונה להגדיר את ה package שבו שמרנו את הקובץ.

כפי שאפשר לראות הקובץ דיי דומה להגדרת Interface רגילה.

 

דוגמה והרחבה על סוגי הפרמטרים להגדרה למתודה בקובץ ה AIDL:

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

in – כמו שאמרנו, מערכת ההפעלה תעביר אותו את תהליך הסריאלזציה ותעביר אותו מתהליך א’ לתהליך ב’.

נשתמש בזה לדוגמה במתודה:

void passString(in String name);

שמעבירה String לתהליך המרוחק.

out –אם נרצה להגדיר פונקציה שהאובייקט המועבר לא יעבור את תהליך הפרסור לתהליך ב’.

נשתמש בזה לדוגמה במתודה:

void testOutParam(in int[] inparams, out int[] outparams);

שמעתיקה מערך אחד לשני ובסיום הקריאה המרוחקת במערך ה out שהעברנו בצד ה client יהיה את הערכים החדשים שהתהליך המרוחק שינה. ומכיוון שלא משנה הערכים שנעביר ב outparams נגדיר אותו כ out ולא inout.

inout – נשתמש בזה לדוגמה במתודה:

void checkChangingValues(inout int[] values);

שבה המערך values יועבר למתודה בתהליך ב’ שתבצע עליו שינויים ובסיום המתודה מערך ה values בתהליך א’ יקבל את הערך ששונה בתהליך ב’.

העברת אובייקט parcelable – נשתמש בזה לדוגמה במתודה:

void passObject(in ParcelableClass parcelableClass);

בשביל להעביר אובייקט שהגדרנו כמו ה ParcelableClass נצטרך שהאובייקט הזה (כמו שהשם מרמז) יהיה Parcelable.

לאחר שמימשנו אובייקט Parcelable שנרצה להעביר, ניצור לו קובץ AIDL חדש שבו נגדיר אותו:

package co.il.lior.remote_process_example;

parcelable ParcelableClass;

ובקובץ ה AIDL הראשי נוסיף את קובץ ה AIDL הזה כ import:

import co.il.lior.remote_process_example.ParcelableClass;

לאחר סיום כתיבת קובץ ה AIDL ושמירתו. כאשר נבנה מחדש את הפרוייקט, הקומפיילר, בדומה ליצירת קובץ ה R, ייצור לנו קובץ java מקובץ הAIDL ששמרנו – קובץ ה java יהיה באותו השם של קובץ ה AIDL רק עם סיומת של java: IRemoteServiceAIDL.java

 

2. כתיבת ה RemoteService ומימוש ה AIDL:

הגדרת ה Service שירוץ על תהליך מרוחק ב Manifest:

<service
    android:name=".RemoteService"
    android:process=":RemoteService" />

android:process זה השם שנקבע לתהליך המרוחק. ה “:” מציינים שקודם כל השם יהיה שם ה package של האפליקציה ואז “:” והשם שרשמנו.

 

נגדיר את ה Binder ב Service:

ה binder שאנחנו מגדירים יהיה מחלקה פנימית סטטית אבסטרקטית שתמיד השם שלה יהיה Stub.

המחלקה הזאת תהיה מחלקה פנימית לממשק שהקומפיילר של אנדרואיד יצר בתיקיית ה gen מקובץ ה AIDL. (בדוגמה, המחלקה נקראת IRemoteServiceAIDL).

ניצור מימוש שלה ונממש את המתודות שהצהרנו עליהן בקובץ ה AIDL:

private final IRemoteServiceAIDL.Stub _binder = new IRemoteServiceAIDL.Stub() {

	//Overriden and implemented the methods defined in the AIDL

};

בשלב השני נחזיר את המימוש שלנו ל Binder:

@Override
public IBinder onBind(Intent intent) {

     return _binder;

}

 

3. כתיבת ה Client:

הגדרת אובייקט מסוג הממשק שנוצר ב gen שה binder מחזיר. דרך הממשק הזה נבצע את הקריאות לתהליך המרוחק:

IRemoteServiceAIDL _iRemoteServiceBinder = null;

הגדרת אובייקט ה connection:

	private ServiceConnection _connection = new ServiceConnection() {

		public void onServiceConnected(ComponentName className, IBinder service) {

השמת ה Binder כממשק שנוצר:

			_iRemoteServiceBinder = IRemoteServiceAIDL.Stub.asInterface(service);

		}

		public void onServiceDisconnected(ComponentName className) {

			_iRemoteServiceBinder = null;

		}

	};

 
נקשר את ה Remote Service ל Activity שלנו:
נעשה bind ל service ל Activity שלנו. (מכיוון שבדוגמה הזאת ה RemoteService יושב באותה אפליקציה – אותו פרוייקט, אז אפשר להעביר לבנאי של ה intent את ה RemoteService.class)

final Intent intentToService = new Intent(ClientActivity.this, RemoteService.class);
bindService(intentToService, _connection, Context.BIND_AUTO_CREATE);

 
קריאה למתודה בתהליך המרוחק:

יש לשים לב שכל קריאה צריך לעטוף ב try ו catch לחריג מסוג RemoteException.

זאת מכיוון שיכול להיות שהתהליך המרוחק יהרג בזמן הבקשה שלנו, ובמקרה הזה יזרק לתהליך הקורא חריג RemoteException.

if(_iRemoteServiceBinder != null) {

	ParcelableClass parcelableClass = new ParcelableClass(1, 2, "String");
	try {

		_iRemoteServiceBinder.passObject(parcelableClass);

	} catch (RemoteException e) {

		e.printStackTrace();

	}

}

 
 

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

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

 

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

בהצלחה !

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>