פיתוח: תקשורת עם השרת – איך לעשות זאת נכון (Android, JSON, RESTful).


פורסם ב 24/09/2013 ע"י Royi Benyossef

שלום לכולם,
קודם כל; הכרה קצרה, הבלוג הזה מתבסס לחלוטין על עבודתי המשותפת עם +Ran Nachmany ו- +Amir Lazarovich על Developer lab בשם AndconLab שהעברנו בכנס MultiScreenX השנה.
את הבלוג הראשון אני רוצה להקדיש לנושא עבורו יש המון מידע ברשת אבל רובו המוחלט חלקי, מיושן או פשוט לא נכון ולכן כאן אני ארצה להסתכל על הנושא בצורה הרחבה ביותר שאפשר תוך כדי שאני אמנה את האפשרויות הגלומות ואסביר את ההיגיון מאחורי כמה שיותר מהבחירות שלי.

בחירה ראשונה – פורמט.

כאשר ניגשים לבחירת פורמט יש בדרך כלל 2 אפשרויות סטנדרטיות עיקריות; JSON, XML, וההבדלים ביניהם נעשים ברורים ביותר אחרי דוגמא קטנה.
בדוגמא זו אני מצרין מידע (לא שלם) עבור 2 אירועים שעזרתי בארגונם השנה:

JSON:

   {
    "events": [
     {
      "name": "Herzeliya GDG April",
      "lectures": [
            {
               title: "Google play services",
               speaker: "Royi Benyossef"
            },
            {
               title: "MVC in Android",
               speaker: "Mr. Ruslan Novikov"
            }
     },
     {
      "name": "Google I/O 2013 reloaded",
      "lectures": [
            {
               title: "Google play services #2",
               speaker: "Royi Benyossef"
            },
            {
               title: "The pro-tip trilogy",
               speaker: "Ran Nachmany "
            }
     }
    ]
   }

XML:

 <events>
    <event>
     <name>Herzliya GDG April</name>
     <lectures>
       <lecture>
         <title>Google play services</title>
         <speaker>Royi Benyossef</speaker>
          <title>MVC in Android</title>
         <speaker>Mr. Ruslan Novikov</speaker>
       </lecture>
     </lectures>
    </event>
    <event>
     <name>Google I/O 2013 reloaded</name>
     <lectures>
       <lecture>
         <title>Google play services #2</title>
         <speaker>Royi Benyossef</speaker>
          <title>The pro-tip trilogy</title>
         <speaker>Ran Nachmany</speaker>
       </lecture>
     </lectures>
    </event>
   </events>

תספרו כמה תווים לקח להצרין את אותו מידע ב-2 השיטות ותראו שב-XML זה לקח הרבה יותר,
לעומת זאת אני מניח שלרובכם היה קל יותר להבין מה המידע המוצרן מדוגמת ה-XML ורק אחרי שקראתם אותה הבנתם את דוגמת ה-JSON.
מה אכפת לי כמה תווים לוקח המידע?

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

חלק גדול מן התקשורת ל-mobile היא ע"ג חיבורי 3G בו הלקוח משלם על תעבורה,
יותר תווים לכל קריאה אומר שהאפליקציה שלכם יכולה להיות יקרה הרבה יותר למשתמש.
האם זה אומר כי JSON בהכרח הינו יותר יעיל בעבודה מול שרת?
התשובה היא לא! במקרים רבים מבחני benchmark הראו כי XML טוב יותר אך אלה היו
מקרים אליהם רוב אפליקציות ה-mobile לא מגיעות בדרך כלל (אפשר לקרוא על כך יותר

בחירה שניה – Service, Class או Singleton.

ברוב האפליקציות אותן אני מכיר יש מספר דרישות מ-Class שמנהל ומטפל בתקשורת אפליקציה-שרת:
  1. שיהיה זמין תמיד מכל מקום באפליקציה שצריך אותו.
  2. שיעשה את פעולתו ברקע כך שלא יפריע לפעולת ה-UI של האפליקציה.
  3. שלא ידרוש משאבים מרובים.
  4. שיוכל לסיים או להתחיל פעולות גם לא בזמן דרישה (by demand) אלא ע"פ חוקיות אחרת נדרשת.
כל הדרישות הללו או אפילו תת קבוצה שלהם למעשה דורש שימוש ב-Service וזה אכן מה שעשינו (סוף סוף קצת קוד :)):

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

  <uses-permission android:name="android.permission.INTERNET"/>

והנה ההגדרה של ה-Service ב-Manifest:

  <service android:name="CommunicationService"/>

והגרסא הבסיסית של ה-Service:

 public class CommunicationService extends Service{
      public static final String RESULTS_ARE_IN = "RESULTS_ARE_IN";
      private Thread mWorker;
   private HttpClient mHttpClient;
      private ResponseHandler<String> mResponseHandler;
      @Override
      public void onCreate() {
           super.onCreate();
           mHttpClient = new DefaultHttpClient();
     HttpParams params = mHttpClient.getParams();
     int serverConnectionTimeout = getResources().getInteger(R.integer.http_connection_timeout_in_seconds) * 1000;
     HttpConnectionParams.setConnectionTimeout(params, serverConnectionTimeout);
     HttpConnectionParams.setSoTimeout(params, serverConnectionTimeout);
           mResponseHandler = new BasicResponseHandler();
           initApiStrings();
      }
      @Override
      public IBinder onBind(Intent intent) {
           return null;
      }
      @Override
      public int onStartCommand(Intent intent, int flags, int startId) {
           if (null != mWorker && mWorker.isAlive()) {
                //we are already running a transaction, nothing to do here
                return Service.START_STICKY;
           }
           //start new job
           mWorker = new Thread(new TransactionJob());
           mWorker.start();
           return Service.START_STICKY;
      }
 }  

שימו לב לכמה פרטים:

  1. חסרה לכם מימוש של פונקציה בשם initApiStrings בה השתמשנו ע"מ לסדר את הפורמט של קריאות ה-API בהן השתמשנו אך מכיוון שהפוסט הזה עמוס גם כך החלטתי להשמיט אותה אך אתם בהחלט מוזמנים להשלים אותה או כל חוסר אחר בעזרת הקוד המלא ששחררנו כאן.
  2. ה-Service הינו מסוג START_STICKY מה שפחות או יותר אומר שאם Android יהרוג את ה-Service הנ"ל מכל סיבה אזי הוא גם ינסה לאתחל אותו מחדש ברגע שישתחררו המשאבים לכך.
  3. ה-Service משתמש ב-Thread וזאת משום ש-Service (בניגוד ל-IntentService) רץ על התהליך הראשי של האפליקציה ולכן עלול להפריע לפעילות התקינה של ה-UI.
והנה המימוש של ה-Thread המדובר:
 private class TransactionJob implements Runnable {
           private static final String TAG = "Service";
           @Override
           public void run() {
                try {
            HttpGet httpGet = new HttpGet(GET_EVENTS);
                  String responseBody = null;
                  try {
                      responseBody = mHttpClient.execute(httpGet, mResponseHandler);
                  } catch(Exception ex) {
                    ex.printStackTrace();
                  }
         List<Event> events = null;
                  if(responseBody != null) {
           events = JacksonUtils.sReadValue(responseBody, new TypeReference<List<Event>>() {}, false);
           if (events != null) {
             //fire an intent
             Intent intent = new Intent();
                intent.setAction(RESULTS_ARE_IN);
                CommunicationService.this.sendBroadcast(intent);
           }
                  }
          } catch (Exception e) {
               e.printStackTrace();
          }
           }
      }
ה-Thread הנ"ל מחזיק את הפונקציונליות הקשורה לשליחת הבקשה, קבלת התגובה מהשרת והפרסור של התגובה מן הפורמט שבחרנו (JSON) ל-POJO או Plain Old Java Object.
למען הפרסור אנחנו בחרנו להשתמש ב-Jackson שהינה ספריית קוד פתוח הבנויה לשם כך, ישנן עוד מספר אפשרויות בהן יכולנו לבחור כמו gsonjson-simple ועוד, שכולן ספריות טובות שמבחני benchmark כאלה ואחרים טוענים שהן טובות יותר במצבים כאלה ואחרים אבל… אנחנו בחרנו ב-Jackson ו…זה מה יש!
הקוד של הפרסור עצמו נמצא פה וידרוש מכם להוריד גם את ה-.jar המתאים.
ו… זהו. אני חושב שעשינו הרבה לפוסט אמיתי ראשון ואני מקווה שנכסה את כל השאר בפוסטים הבאים :)

אני רוצה להודות שוב ל- +Ran Nachmany ו- +Amir Lazarovich אשר רוב הקוד שראיתם הוא שלהם ולהפנות אתכם לקוד המקור המלא של AndConLab כאן: https://github.com/RanNachmany/AndconLab

הבלוג הזה הופיע במקור בבלוג האישי שלי, כאן – http://royiby.blogspot.co.il/2013/07/android-json-restful.html

FacebookTwitterGoogle+EmailPinterestWhatsAppLinkedInשתפו אותי

Royi Benyossef

I have been an Android developer since before the first Android phone existed and proceeded to work on development for Android based tablets and TVs long before they were launched by Google so you might say i'm somewhat Android pioneer whose expertise is in non-standard Android platforms and products which involve Android internals as well as application development. As an avid Open source enthusiast i have been Organizing events as well as blogging, speaking and mentoring on Android related topics since 2010 and it is due to the above that i am a Google developer expert since the program's inception which means that i'm not a Google employee but that Google believes that i know what i'm talking about as well as how to say it. Outside of Android i have been a full stack developer at a projects company where i was developing mobile applications in Android as well as iOS (Objective-c) and also dabbled in server side development in PHP, Jquery and more including a few projects of mobile applications using cross platform push technologies using socket.io, XMPP and other similar protocols as a server-side, i also had other wonderful projects which granted me experience in widgets, live wallpapers, network and battery efficiency and many more exciting topics.

2 Comments

  1. rock_artist
    24/09/2013 בשעה 18:16

    בלוג מעולה ורלוונטי. ישר כוח!

  2. 1_aviv
    27/09/2013 בשעה 11:30

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

להגיב