‏הצגת רשומות עם תוויות אנדרואיד. הצג את כל הרשומות
‏הצגת רשומות עם תוויות אנדרואיד. הצג את כל הרשומות

יום ראשון, אוקטובר 23

אודות אנדרואיד למפתחים


הבלוג עוסק בפיתוח תוכנה לאנדרואיד
הפוסטים דנים בהסבר הפונקציונליות של האנדרואיד, הארכיטקטורה, וכתיבת תוכנה למערכת תוך שימוש בדוגמאות מוסברות בעיקר. בסוף כל פוסט מצורף קישור להורדת כל קבצי המקור של הדוגמאות. התחלתי לעבוד על הבלוג באמצע אוקטובר 2010. על פי התכנון, עד סוף 2010\תחילת 2011, הבלוג יכיל את סט התכנים המינימלי הדרוש כדי להוות מקור לימוד טוב לאנדרואיד. הכוונה היא כמובן להמשיך ולהוסיף תכנים גם לאחר ה"השקה".
קהל היעד שהבלוג מכוון אליו כולל מפתחים בעלי רקע כלשהו בתכנות, מתחילים וגם מתקדמים. הפיתוח באנדרואיד נעשה בשפת ג'אווה. קורא שאינו מכיר ג'אווה אך בעל נסיון בשפת תכנות אחרת, יוכל לעקוב ולהבין את עיקר התוכן ובהמשך בהחלט יוכל להכנס לעניינים. גם קורא חסר ניסיון בתוכנה אך בעל רקע טכני  יוכל להפיק  תועלת, בעיקר מהחלקים הדנים בהיבטים מערכתיים. אגב, קיים מדריך מצוין לג'אווה באתר של אורקל -  קישור למדריך באתר של אורקל.
סביר להניח שקיימים מדריכי ג'אווה גם בעברית, אם כי לא חיפשתי עדין כאלה. אני שוקל להוסיף פוסטים מיוחדים להדרכת ג'אווה. זה תלוי בין השאר בדרישת הקהל.

לבלוג מתווספים פוסטים באופן קבוע.  אפשר לעקוב אחריו עם RSS.
מומלץ לקרוא את הפוסטים לפי הסדר. וזה בעברית. 
רונן.
נ.ב.
אשמח לקבל משוב בדוא"ל ronen567@gmail.com.
טל 052-4753350

יום שלישי, נובמבר 16

על המפה


אפליקציות הקשורות למיקום ולמפות הן מהדברים הכי מעניינים את בעלי הסמארטפונים. לפיכך, נקדיש לנושא זה מספר פוסטים. כוונתי להתחיל מהפשוט מאד ולהוסיף דוגמאות מורכבות יותר בהדרגה. בפוסט הזה נדגים הצגה של מפות+  הצגה של אתרים על המפה. בפוסט שאחריו, נדגים ציור על גבי המפה (overlay), ע"י ציור של סיכה המסמנת מיקום. באותו פוסט, או אולי בפוסט מאוחר יותר (נשקול את העניין, כשהמטרה היא ללמוד בשלבים), נדגים גם את נושא ה-Geocoding, כשנהפוך שם של מקום לקואורדינטות על המפה.
אגב, לכל המעוניינים, באתר המפתחים של גוגל יש מדריך מבוא למפות לא רע. בכל זאת, אני מקוה שהפוסט הזה ואלה שאחריו יהיו אולי יעילים יותר. הקישור - Google Map View.
עוד  נושא די מצער לפני שמתחילים: המפות של גוגל אמנם כוללות גם את ישראל, אבל ה-API של גוגל מאפשר הצגה של ישראל ברזולוציה מאד נמוכה - ראה את התמונה הבאה להמחשה:



רואים את הסיכה שעל המפה? זו עפולה. ומימינה - לא, זה לא כביש 65 של ישראל. זו כבר ירדן.  מפת ישראל ברזולוציה גבוהה אמנם זמינה בגוגל, אבל אפשר לגשת אליה רק דרך ה- Browser. הסיבות לכך קשורות לעניינים מסחריים, כנראה בין גוגל לבין הבעלים של מפות הישראל.  חבל. יש סיכוי שזה ישתנה?  מקווה שכן אבל אין (לי) שום מידע בקשר לזה. מצד שני לא לשכוח את חצי הכוס המלאה: יש מפות לשאר העולם. הקישור הבא מפרט את המדינות עבורן google Map API נתמך:
http://gmaps-samples.googlecode.com/svn/trunk/mapcoverage_filtered.html
נעבור לדוגמא שלנו. נציג על המסך מפה + כפתורי שליטה. המיקום הראשוני שיקבע יהיה פאריס. נוסיף מספר כפתורים כדי להציג יכולות: כפתור למעבר בין תמונת לויין למפה, כפתורי זום, כפתור  clickable שיבחר בין תמונה סטטית לבין אפשרות לתזוזה.
קישור להורדת קבצי הפרויקט - למטה בסוף.
הנה ה-UI:



כמה הערות אדמיניסטרטיביות חשובות: העבודה עם המפות מחייבת רשיונות\הרשאות. מדובר בהרשאה לשימוש בספריות של גוגל, הרשאה כללית לגישה לאינטרנט ו-  API Key עליו נדון כשנסקור את קובץ ה-Layout.  זאת מפני שגוגל שומרת על הפרדה בין הספריות של אנדרואיד לבין ספריות ה-API שלה. האם הם זוממים משהו שאנחנו לא יודעים? מקווה מאד שלא.
מכאן ואילך אם כן נתאר את הנושאים הבאים:
  1. דגשים מיוחדים הקשורים לתמיכה במפות בזמן בבניית הפרויקט.
  2. דגשים מיוחד הקשורים למפות בקובץ ה-manifest.xml.
  3. קובץ ה-layout.
  4. תוכנית ה-java.
1. דגשים בבניית הפרוייקט :
הדגש היחידי, בחירת build target מסוג Google API. לכל ורסיה החל מ-1.5 יש ורסיה תואמת. למה? מפני שגוגל שומרת על הפרדה בין הספריות של אנדרואיד לבין ספריות ה-API שלה. האם הם זוממים משהו שאנחנו לא יודעים? מקווה מאד שלא. ליצירת הפרויקט ב-Eclipse נבחר: File->New->Android Project. בחלון הדוגמא שלפניכם מצגת הבחירה עבור 2.2 - Froyo.


יתר המאפיינים - כרגיל.
2. נעבור לקובץ ה-manifest. נציג אותו כאן בשלמותו. האלמנטים המיוחדים כאן מודגשים (שורות 7 ו-17):
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3.       package="com.ahs.adroheb.mapdisplay"
  4.       android:versionCode="1"
  5.       android:versionName="1.0">
  6.     <application android:icon="@drawable/icon" android:label="@string/app_name">
  7.      <uses-library android:name="com.google.android.maps" />
  8.     
  9.         <activity android:name=".MyMapDisplay"
  10.                   android:label="@string/app_name">
  11.             <intent-filter>
  12.                 <action android:name="android.intent.action.MAIN" />
  13.                 <category android:name="android.intent.category.LAUNCHER" />
  14.             </intent-filter>
  15.         </activity>
  16.     </application>
  17. <uses-permission android:name="android.permission.INTERNET"/>
  18.     <uses-sdk android:minSdkVersion="3" />
  19. </manifest>
השורות המעניינות:
שורה 7: הכללת ספריית ההמפות.
שורה 17 היא הרשאה לגישה לאינטרנט.
מעבר לזה, אפשר ללמוד מהקובץ הנ"ל שהאפליקציה שלנו מכילה Activity אחד בלדבד.
3. נציג את קובץ ה-Layout בשלמותו:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <!-- This file is /res/layout/geocode.xml -->
  3. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent">
  6. <com.google.android.maps.MapView
  7. android:id="@+id/geoMap" android:clickable="true"
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:apiKey="Put your API Key here"
  11. />
  12. <LinearLayout android:layout_width="fill_parent"
  13. android:layout_alignParentBottom="true"
  14. android:layout_height="wrap_content"
  15. android:orientation="horizontal" >
  16. <Button
  17. android:id="@+id/button_sat_map"
  18. android:layout_width="wrap_content"
  19. android:layout_height="wrap_content"
  20. android:text="@string/button_sat_map"/>
  21. <Button
  22. android:id="@+id/button_zoom_out"
  23. android:layout_width="wrap_content"
  24. android:layout_height="wrap_content"
  25. android:text="@string/button_zoom_out"/>
  26. <Button
  27. android:id="@+id/button_zoom_in"
  28. android:layout_width="wrap_content"
  29. android:layout_height="wrap_content"
  30. android:text="@string/button_zoom_in"/>
  31. <Button
  32. android:id="@+id/button_clickable"
  33. android:layout_width="wrap_content"
  34. android:layout_height="wrap_content"
  35. android:text="@string/button_clickable"/>
  36. </LinearLayout>
  37. </RelativeLayout>


מעבר להגדרת הכפתורים עליהם לא נתעכב, נסתכל על השורות המודגשות: 6-10.
שורות אלה מגדירות את האלמנט MapView בו נשתמש לצירת המפה. את ה-API Key יש לשתול בשורה 10. הקלק על הקישור לקבלת מדריך ליצירת ה-API Key. ללא ה-Key לא ניתן לצפות במפות.

4.  שתי  מחלקות (classes) עושות את עיקר העבודה הקשורה למפות: MapView ו- MapActivity.
MapActivity היא הרחבה (subclass) של Activity (לטובת טיפול במפות, וכדי להשתמש ב MapView חייבים להשתמש בה.
נציג תחילה את השלד של האוביקט והמתודות שלו:

  1. public class MyMapDisplay extends MapActivity implements OnClickListener{
  2.     MapView mapView;
  3.     private Button     buttonSatMap;
  4.     private    Button    buttonZoomIn;
  5.     private    Button    buttonZoomOut;
  6.     private    Button    buttonClick;
  7.     @Override
  8.     protected void onCreate(Bundle savedInstanceState)
  9.     {
  10.      }
  11.    
  12.     private void controlButtonManage(){
  13.    }
  14.     public void onClick(View view) {
  15.  
  16.     }
  17.     @Override
  18.     protected boolean isLocationDisplayed() {
  19.         return false;
  20.     }
  21.     @Override
  22.     protected boolean isRouteDisplayed() {
  23.         return false;
  24.     }


 שורה 1: ה-class כאמור   extends MapActivity.
הפעם הוספנו גם את: implements OnClickListener. המשמעות: ה-callback שמופעל עם לחיצת הכפתור - onClick ימומש כמתודה בתוך ה-class (ראו שורה 14 + הסברים בהמשך). שימו לב, זהו מימוש מעט שונה מזה שהשתמשנו בו בחלק מהדוגמאות הקודמות.
 לגבי שורות 17-24: שתי המתודות הנ"ל: isLocationDisplayed ו- isRouteDisplayed חייבות להיות ממומשות עבור classes של ה-MapActivity. זה חוק מחייב מעוגן ברישיון של גוגל, שנועד לאפשר לשרתים שלהם לעקוב אחרי אפליקציות אלה. מה הם זוממים שם??
  isLocationDisplayed - יחזיר true אם האובייקט מציג את מיקומו על סמך שימוש בסנסורים - למשל GPS. במקרה שלנו נחזיר false.
isRouteDisplayed - יחזיר true אם מוצגת אינפורמציה של הכוונת נתיב (routing), למשל - הוראות כיוון נסיעה.  גם כאן נחזיר false.



המתודה בה מתבצעת עיקר הפעילות היא onCreate, נציג אותה שלמותה:


  1.     @Override
  2.     protected void onCreate(Bundle savedInstanceState)
  3.     {
  4.         super.onCreate(savedInstanceState);
  5.         setContentView(R.layout.main);
  6.         mapView = (MapView)findViewById(R.id.geoMap);
  7.         mapView.setBuiltInZoomControls(true);
  8. // Coordinates of Paris:        
  9.         int lat = (int)(48.856826*1000000);
  10.         int lng = (int)(2.351317*1000000);
  11.         GeoPoint loc = new GeoPoint(lat,lng);
  12.         mapView.getController().setZoom(15);
  13.         mapView.getController().setCenter(loc);
  14.         controlButtonManage();
  15.      }

שורה 6 שולפת את האלמנט MapView .
שורה 7, מפעילה את הכפתור המובנה במפה לשליטה בזום. אמנם הוספנו כפתור זום משלנו אך הוא די מיותר. למעשה, הוספנו אותו כדי להציג את המתודה של הזום.
שורה 11 יוצרת את האובייקט  loc מסוג GeoPoint. הוא מחזיק מיקום על המסך על סמך קואורדינטות. במקרה הנ"ל הכנסנו את הקואורדינטות של פאריס.
שורה 12 קובעת את רמת הזום שתוצג.
שורה 13 מעבירה את תצוגת המפה לפי loc.

זה עיקר העניין. שורה 14 מפעילה מתודת שרות שמפעילה את הכפתורים ומחברת אותם לפונקציונליות שלהם. הנה המתודה:
  1.     private void controlButtonManage(){        buttonSatMap         = (Button)findViewById(R.id.button_sat_map); 
  2.         buttonZoomIn         = (Button)findViewById(R.id.button_zoom_in); 
  3.         buttonZoomOut         = (Button)findViewById(R.id.button_zoom_out);
  4.         buttonClick         = (Button)findViewById(R.id.button_clickable);
  5.         buttonSatMap.setOnClickListener(this);
  6.         buttonZoomIn.setOnClickListener(this);
  7.         buttonZoomOut.setOnClickListener(this);
  8.         buttonClick.setOnClickListener(this);
      
    }

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



  1.     public void onClick(View view) {
  2.         switch(view.getId()){
  3.             case     R.id.button_sat_map:
  4.                 if(mapView.isSatellite())
  5.                     mapView.setSatellite(false);
  6.                 else
  7.                     mapView.setSatellite(true);
  8.             break;
  9.             case    R.id.button_zoom_in:
  10.                 mapView.getController().setZoom(mapView.getZoomLevel() + 1);
  11.             break;
  12.             case    R.id.button_zoom_out:
  13.                 mapView.getController().setZoom(mapView.getZoomLevel() - 1);
  14.             break;
  15.             case    R.id.button_clickable:
  16.                 if(mapView.isClickable())
  17.                     mapView.setClickable(false);
  18.                 else
  19.                     mapView.setClickable(true);
  20.             break;
  21.         }
  22.     }


לא נתעכב עליה יותר מדי. בשורה 2 מתבצע פיצול פי הכפתור שנלחץ. בהמשך כל כפתור מקבל טיפול בהתאם. שימו לב למתודות המטפלות בהצגת תמונת לווין (satellite), זום, ו-click.
הקלק להורדת קבצי הפרויקט.







יום חמישי, נובמבר 4

נגן אודיו בסיסי


בפוסט זה נציג את פעולת ה-MediaPlayer ונבנה נגן אודיו בסיסי. ה-MediaPlayer הוא class הכולל גם אפשרות טיפול בוידאו. ראה קישור לאתר המפתחים של אנדרואיד.
הנגן מנגן שיר אחד בלבד, של הזמר ריי ווטסון.  שם השיר: All My Desires. אני מודה שהכרתי אותו רק בעקבות  חיפושי אחרי קבצי אודיו חופשיים להורדה חינם ובאופן חוקי.
הפורמט של השיר הוא mp3. גם midi, pcm ו -ogg vorbis נתמכים.
את הקובץ allmydesire.mp3 יש לשמור תחת  res/raw.
הנגן שלנו יכיל כפתור יחיד להפעלה והפסקת ההשמעה. הוא כולל תצוגה של אורך השיר. הוא יזהה את סיום השיר וידפיס הודעה על המסך.
הוספתי לתצוגה גם נתונים על השיר ותמונה של הזמר. התצוגה של הנגן נראית כך:




מעבר לשמירת קובץ המוסיקה תחת /res/raw ושמירת התמונה תחת res/draw, הקבצים שבהם נתעניין הם קובץ ה-layout ואובייקט הג'אווה היחיד.
ב-layout אין שום חידוש. מופיעים שם 3 סוגים של אלמנטים: Button, שלושה  TextView ו-ImageView. לא אציג את הקובץ כאן הפעם. הוא נמצא כמובן בחבילת הקבצים של הפרויקט הניתנת להורדה בהקלקה על הקישור.
שים לב, הקבצים הפעם בגודל 12MB, בגלל קבצי ה-mp3.

תוכן הקובץ הכולל את ה-class היחידי בפרויקט מוצג להלן.


  1. public class myAudioMediaPlayer extends Activity {
  2.     MediaPlayer mp;
  3.    
  4.     Button         playButton;
  5.     Button         stopButton;
  6.     Button         setMessageButton;
  7.     Button         clearMessageButton;
  8.     TextView    textBox;
  9.     TextView    textBox2;
  10.     TextView    textBox3;
  11.    
  12.     /** Called when the activity is first created. */
  13.     @Override
  14.     public void onCreate(Bundle savedInstanceState) {
  15.         super.onCreate(savedInstanceState);
  16.         setContentView(R.layout.main);
  17.         playButton = (Button)findViewById(R.id.play_button);
  18.         textBox       = (TextView)findViewById(R.id.text_box);
  19.         textBox2       = (TextView)findViewById(R.id.text_box2);
  20.         textBox3       = (TextView)findViewById(R.id.text_box3);
  21.          mp = MediaPlayer.create(this, R.raw.allmydesire);
  22.           String min = String.valueOf((mp.getDuration()/1000)/60);
  23.          String sec = String.valueOf((mp.getDuration()/1000)%60);
  24.          textBox2.setText( " All My Desire :"+"שם השיר");
  25.          textBox3.setText( " Ray Watson :"+"שם המבצע");
  26.          if(10> (mp.getDuration()/1000)%60)
  27.              textBox.setText(min +":0"+sec + " :"+"משך השיר");
  28.          else
  29.              textBox.setText(min +":"+sec + " :"+"משך השיר");
  30.          mp.setOnCompletionListener(new
  31.                  MediaPlayer.OnCompletionListener() {
  32.                  public void onCompletion(MediaPlayer mp)
  33.                  {
  34.                      mp.stop();
  35.                      textBox.setText("Song is Over. You can start again");
  36.                      playButton.setText("נגן");
  37.                      createMediaPlayerHelper();
  38.                  }
  39.                  });
  40.         
  41.         playButton.setOnClickListener(new View.OnClickListener() {
  42.             public void onClick(View view) {
  43.                 if(mp.isPlaying()){
  44.                     mp.pause();
  45.                     playButton.setText("נגן");
  46.                 }
  47.                 else{
  48.                     mp.start();
  49.                     playButton.setText("הפסק");
  50.                 }
  51.              }
  52.         });
  53.  
  54.     }
  55.     private void createMediaPlayerHelper(){
  56.         mp = MediaPlayer.create(this, R.raw.allmydesire);
  57.     }
  58. }

המתודה העיקרית היא onCreate ויש גם מתודת עזר בשם createMediaPlayerHelper
הסבר לinCreate :
חלק 1:
עד שורה 23, אלה בעיקר האיתחולים מהסוג הרגיל, של אלמנטי ה- UI.
חלק 2: יצירת האובייקט של ה- MediaPlayer עם (create(Context context, int resid, כשה-id לקוח מה-class R.java.
השורות 2 ו 3, מחשבות את אורך השיר בדקות ושניות על סמך המתודה getDuration.

  1. mp = MediaPlayer.create(this, R.raw.allmydesire)
  2. String min = String.valueOf((mp.getDuration()/1000)/60)
  3. String sec = String.valueOf((mp.getDuration()/1000)%60)
חלק 3: הצגה של הטקסט - SetText בעזרתו מדפיסים על המסך את פרטי השיר ואורכו.

  1.         textBox2.setText( " All My Desire :"+"שם השיר");
  2.          textBox3.setText( " Ray Watson :"+"שם המבצע");
  3.          if(10> (mp.getDuration()/1000)%60)
  4.              textBox.setText(min +":0"+sec + " :"+"משך השיר");
  5.          else
  6.              textBox.setText(min +":"+sec + " :"+"משך השיר");

חלק 4: callback  שיופעל עם סיום השיר. שימוש במתודה  setOnCompletionListener לרגיסטרציה של ה-callback. שהשיר נגמר עוצרים את הנגן (שורה 5), מדפיים הודעה ל-UI (שורה 6), הופכים את הכותרת שעל הכפתור מ"הפסק" ל"נגן", קוראים למתודת השירות (שורה 8), שיוצרת MediaPlayer מחדש. (הערה -יתכן שמספיק לעשות prepare - לבדוק)


  1.         mp.setOnCompletionListener(new
  2.                  MediaPlayer.OnCompletionListener() {
  3.                  public void onCompletion(MediaPlayer mp)
  4.                  {
  5.                      mp.stop();
  6.                      textBox.setText("Song is Over. You can start again");
  7.                      playButton.setText("נגן");
  8.                      createMediaPlayerHelper();
  9.                  }
  10.                  });
חלק 5: יצירת callback עבור כפתור השליטה בנגן.
עם הלחיצה על הכפתור מפעילים את המתודה pause (שורה 4) או start (שורה 8), לפי התוצאה של המתודה isPlaying- שורה 3. באותה ההזדמנות הופכים את הכיתוב על הכפתור מ-"נגן" ל- " הפסק" ולהפך (שורות 5 ו-9).

  1.        playButton.setOnClickListener(new View.OnClickListener() {
  2.             public void onClick(View view) {
  3.                 if(mp.isPlaying()){
  4.                     mp.pause();
  5.                     playButton.setText("נגן");
  6.                 }
  7.                 else{
  8.                     mp.start();
  9.                     playButton.setText("הפסק");
  10.                 }
  11.              }
  12.         });
בהחלט קצר ופשוט. עוד נחזור לתרגילים מורכבים יותר של ה-MediaPlayer.

יום חמישי, אוקטובר 28

Explicit Intent - העברת הודעות בין Activities

עקרונות הפעולה של ה-Intents תוארו בפרק המבוא להעברת הודעות, כך שנוכל לעבור מיד להדגמה. באפליקציה שהכנתי מוגדרים שני Activities. הודעה נשלחת מ-Activity האחד לשני. השני מחזיר תשובה לראשון. כל אחד מהם מלווה פעולתו בהדפסות למסך. פעולת שליחת ההודעה מתחילה עם לחיצה על כפתור Send Message.
מצא קישור לקבצי המקור בתחתית הדף.
נראה 3 תצוגות המתקבלות בהרצת האפליקציה:
תצוגת המערכת לפני ההפעלה - פאזה 1:



תצוגת המערכת בסוף התהליך- פאזה 3:


תצוגת המערכת במצב הביניים, מיד עם קליטת התגובה ע" ה  -Receive Activity- פאזה 3:
היות שה-Receive Activity נמצא בפעולה למשך זמן קצר בלבד, לא ניתן להבחין בהודעה שהוא שולח למסך.נפצה על כך עם הודעת pop-up שאמנם תשלח על ידי ה- Receive Activity  אבל נוכל להבחין בה.  אחרי הקדמה זו, הנה תמונת המסך מיד אחרי קבלת ההודעה ע"י ה-Receive Activity:



נעבור לסקירת קוד עצמו. האפליקציה בנויה משני classes  לכן דרושים שני קבצי מקור של Java: אחד עבור ה- Activity ששולח את ההודעה והקובץ השני עבור מקבל ההודעה. לא נתעכב על קבצי ה-Layout וה- Manifest. אין בהם אלמנטים שלא נדונו בפרקים קודמים.
נתחיל עם הקוד של שולח ההודעה, הנמצא בבקובץ MessageCalling.java. לשם נוחות הקוד מובא כולו כאן, אך בהמשך נדון בחלקים המעניינים הדורשים הסבר.

package com.ahs.ronenh.expintent;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

  1. public class MessageCalling extends Activity {
  2.     public final static int RECEIVE_MESSAGE = 0;
  3.     /** Called when the activity is first created. */
  4.     @Override
  5.     public void onCreate(Bundle savedInstanceState) {
  6.         super.onCreate(savedInstanceState);
  7.         setContentView(R.layout.main);
  8.         Button sendMessage = (Button) findViewById(R.id.send);
  9.         Button CleanScreen = (Button) findViewById(R.id.clean);
  10.         sendMessage.setOnClickListener(new View.OnClickListener() {
  11.             public void onClick(View view) {
  12.                 TextView msg2screen = (TextView)findViewById(R.id.pong_message);
  13.                 msg2screen.setText("");
  14.                 Intent pingIntent = new Intent();
  15.                 pingIntent.setClass(MessageCalling.this, MessageReceiving.class);
  16.                 pingIntent.putExtra( "extra1", new String( "Message Received: Can you hear us?" ) );
  17.                 startActivityForResult(pingIntent, RECEIVE_MESSAGE);
  18.              }
  19.         });
  20.         CleanScreen.setOnClickListener(new View.OnClickListener() {
  21.             public void onClick(View view) {
  22.                 TextView msg2screen = (TextView)findViewById(R.id.pong_message);
  23.                  msg2screen.setText("");
  24.              }
  25.         });
  26.     }
  27.     protected void onActivityResult(int requestCode, int resultCode, Intent data){
  28.         switch(requestCode) {
  29.             case RECEIVE_MESSAGE:
  30.                 String     rec1    = data.getStringExtra("extra1");     
  31.                 Integer rec2    = data.getIntExtra("extra2",0);     
  32.      
  33.             if( rec1 != null ) {
  34.                 TextView msg2screen = (TextView)findViewById(R.id.pong_message);
  35.                 msg2screen.setText( rec1 + " " + rec2);
  36.             }
  37.          }
  38.     }
  39. }


ב- MessageCalling שתי מתודות עיקריות: ()onCreate, אודותיה כבר דנו, והיא המתודה המופעלת עם יצירת האובייקט. 
המתודה השניה ()onActivityResult היא callback שמופעל כאשר התשובה להודעה מגיעה.
שורה 6- כרגיל, כריעה ל-onCreate של class ה"אבא".
שורה 7 - בניית ה- UI על פי ה- layout.
שורה 8-9: הגדרת האובייקטים לכפתורי המסך: שליחת הודעה וניקוי תצוגה.
שורה 10-19: רגיסטרציה של callback עבור כפתור שליחת ההודעה.
שורה 11: המתודה onClick, היא ה-callback שיופעל עם הלחיצה על כפתור send.
שורה 12: השמת אוביקט מסוג TextView, המשמש לתצוגת טקסט ב- UI.
שורה 13: ניקוי התצוגה.

שורות 14-18: הכנת ה- Intent ושליחתו.
שורה 14: יצירת אוביקט מסוג Intent.
שורה 15: היות שמדובר ב explicit intent יש לציין במפורש את יעד ההודעה. זה נעשה ע"י המתודה של  ה-Intent הבא: setClass(context, class, כאשר היעד של ההודעה הוא MessageReceiving.class.
שורה 16: הוספת שדה ה-extra ב-intent. כאן מוכנס string שיודפס ע"י האובייקט המקבל.
שורה 17: שליחת ה-Intent ע"י המתודה  (startActivityForResult(intent, id. מתודה זו תפעיל את ה- Activity המקבל. ה- Activity השולח מצפה לתשובה אסינכרונית - זו התוצאה הנשלחת חזרה. עם קבלת התשובה, יופעל ה-callback אותו נסקור מיד.
שורות 20 עד 25 הם ה- callback של כפתור ניקוי המסך.
שורה 27: ה- callback שמופעל עם קבלת התשובה להודעה/
שורה 29: בדיקת הסוג של ההודעה- זה הזהוי שהוצמד להודעה המקורית.
שורות 30-31: שליפת שדות ה-extra מה- Intent. שדה אחד מסוג string והשני integer.
שורה 34: יצירת אוביקט מסוג TextVeiw. השדה שהוגדר לו ב- UI הוגדר בקובץ ה-Layout כ-pong_message לעומת ping_message שהוגדר עבור ההודעה היוצאת.
שורה 35: הדפסת שדות ה-extra על המסך. ראה התמונה של "פאזה 3".


כעת ניסקור את קובץ השני, MessageReceiving.java:



package com.ahs.ronenh.expintent;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

/**
 * @author ronen
 *
 */
  1. public class MessageReceiving extends Activity{
  2.     /** Called with the activity is first created. */
  3.     @Override
  4.     public void onCreate(Bundle savedInstanceState)
  5.     {
  6.         super.onCreate(savedInstanceState);
  7.         setContentView(R.layout.main);
  8.         String extraMsg1 = getIntent().getExtras().getString("extra1");
  9.         if( ( extraMsg1 != null ) ) {
  10.           TextView msg2screen = (TextView)findViewById(R.id.ping_message);
  11.           msg2screen.setText(extraMsg1);
  12.          Toast.makeText(this, "Received Message:" + extraMsg1, Toast.LENGTH_LONG).show();
  13.         }
  14.         else
  15.         {
  16.              Log.e("Received Extra is null", "null");
  17.         }
  18.         Intent pongIntent = new Intent();
  19.         pongIntent.putExtra("extra1", "Response received: Yes, we can hear yall!");
  20.         pongIntent.putExtra("extra2", new Integer(555) );
  21.         setResult(RESULT_OK,pongIntent);
  22.         finish();
  23.     }
  24. }
 ה- MessageReceiving class מקבל את ה- Intent ושולח תשובה. הוא מכיל את המתודה ()onCreate בלבד.

שורה 8: שולפת את שדה ה-extra מה- intent הנקלט.
שורה 10: יצירת אובייקט מסוג TextView, המשוייך לאלמנט ה-ping_message ב-Layout של ה- UI.
שורה 11: שליחת שדה ה-extra שהתקבל ל-UI. הערה: לא נוכל לספיק לראות תצוגה זו, היות שה-Activity הנ"ל יתחלף במהירות חזרה ל-Activity השולח, עם שליחת התשובה. כזכור, עם כניסת Activity חדש, התצוגה של הקודם נעלמת.

שורה 12: "פיצוי" על העלמות ההודעה ב- UI, כפי שהוסבר בהערה הנ"ל: במקום זה, נדפיס הודעה בעזרת האובייקט Toast שמייצר הודעות pop-up.
שורה 18-21: בניית התשובה ושליחתה.
שורה 18: יצירת אובייקט intent.

שורות 19-20: הכנסת ערכים לשדה ה-extra. הם יודפסו למסך על-ידי ה- ()onActivityResult כפי שתואר קודם.
שורה 21:()setResult שולחת את התשובה ומפעילה את האובייקט MessageCalling.

למדנו די הרבה היום...:-).
קישור לקבצי המקור.

יום שבת, אוקטובר 23

מבוא לאנדרואיד

אנדרואיד - מערכת הפעלה למכשירים ניידים. קצת היסטוריה: מערכת ההפעלה אנדרואיד נרכשה מחברת אנדרואיד על ידי גוגל בשנת 2005. בנובמבר 2007 הוכרז על הקמתה של ברית  חברות (Open Handset Alliance- OHA), שמטרתה פיתוח סטנדרטים פתוחים למכשירים ניידים. הפיתוח הראשון עליו הכריזה  ה- OHA היתה מערכת ההפעלה אנדרואיד המבוססת על לינוקס קרנל גרסה 2.6. בפברואר 2009 הופצה הגירסה הראשונה לשימוש כללי של לקוחות - גירסה .1.1
אז מהי אנדרואיד בעצם?
  • זוהי מערכת הפעלה המיועדת למכשירים לניידים.
  • מבוססת על לינוקס. 
  • במרכזה  מכונת ג'אווה וירטואלית (JVM - Java Virtual Machine) שפותחה במיוחד למערכת ושמה דלוויק Dalvik. הממשק למערכת האנדרואיד הוא דרך ספריות ג'אווה.
    • הערה: קיימת למעשה אפשרות להתממשק למערכת גם תוך מעקף ה-JVM ושימוש ב NDK- Native Development Kit בשפת c/c++. 
    • נדון ב- NDK בפוסט נפרד.


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

יום חמישי, אוקטובר 21

...בפיתוח - כמה מילים על מהלך חיי האפליקציה -Life Cycle

בפרק שדן בנושא ריבוי המשימות - Multitasking, הוזכר מושג ה-  Life Cycle או מחזור החיים של האפליקציה. הטיפול הנכון בכל אחד משלבי החיים של האפליקציה הוא אחד העקרונות הבסיסיים של האנדרואיד, ומאפשר תמיכה בריבוי משימות גם במערכת ניידת שמיגבלותיה ודרישותיה תוארו באותו פרק. האפליקציה בנויה מעיקרה על בסיס Activities ו- Services - ראה פרק הגדרות. נדון כאן בנפרד במהלך החיים של כל אחד שני מרכיבים אלה.
נתחיל עם מהלך החיים של ה-Activity.ל-Activity שלושה מצבים:
  1. מצב אקטיבי (Active) - ה Activity מופיעה על תצוגה והיא הפעילות שהמשתמש מטפל בה.
  2. מצב הפסקה (Pause) - ה-Activity מוצגת על המסך אך אינה בפוקוס של המשתמש. למשל - מוסתרת חלקית על ידי Activity אחרת.
  3. מצב עצירה (Stop) - ה- Activity נסתרת מהמשתמש.
כשה- Activity נמצאת במצב 2 או 3, המערכת יכולה להרוג את ה- Process שלה, כפי שהוסבר בפרק על ריבוי המשימות.

הדיאגרמה הבאה מתארת את מערכת המצבים והמעברים בינהם.



בין המצבים מתוארות המתודות (callback), המופעלות בזמן המעבר. המפתח לא צריך לדאוג למעברי המצבים של התוכנה. כל זה נשלט ע"י המערכת. המפתח יכול לעקוף (override) את המתודות הנ"ל עם מתודות שלו. אחסוך בהסברים לא ארחיב לגבי המעברים בין המצבים היות שהשרטוט אמור להסביר עצמו.
 נקודה  לציון:
onCeate - כל מחלקה מסוג Activity צריכה לממש את המתודה onCreate שנקראת בתחילת מהלך החיים של ה- Activity.
onPause - זו המתודה בה יש לשמור את פרמטרי המצב, היות שמנקודה זו ניתן להרוג את ה- Process. רב ה- Activities ממשים גם מתודה זו.

לעומת מחזור החיים המורכב של ה-Activity, אצל ה-Service שם אין ממשק למשתמש, קיימים שני מצבים: Start ו- Stop.

אנדרואיד - Multitasking

מערכת אנדרואיד מאפשרת לכמה אפליקציות לפעול ביחד, מה שמכונה "ריבוי משימות" או "Multi Tasking". פרק זה יסביר איך אנדרואיד מממשת את היכולת הזו. כבר ציינו שאנדרואיד היא מערכת הפעלה המיועדת למכשירים ניידים. ככזאת, היא מותאמת לעבודה בסביבה עם משאבי זיכרון מוגבלים. מערכת הפעלה קונבנציונלית ממשת ריבוי משימות על ידי מעבר מהיר של המעבד בין המשימות, על פי הצורך. לפני שהמעבד מפסיק טיפול במשימה הנוכחית ועובר לטיפול במשימה הבאה (task switching), עליו לשמור את המידע הקשור למשימה הנוכחית כך שיוכל להמשיך ולבצע אותה בהמשך. לצורך זה נדרש משטח עבודה (swap memory) מספיק גדול. למערכת הפעלה ניידת לעומת זאת יש  דרישות ומגבלות מיוחדות: 
  1. זיכרון מוגבל שלא יכול להבטיח תמיד מספיק מקום לשמירת המידע הנדרש לצורך החלפת המשימות.
  2. המשתמש לא נדרש לסגור את האפליקציות. ההנחה היא שהוא מעדיף להשאיר חלק גדול מהאפליקציות בהן הוא משתמש, ו"לדפדף" בינהן לפי נוחיותו.ההתנגשות למגבלה בסעיף 1 ברורה.
  3. יש צורך לספק מעבר מהיר בין אפליקציות - פחות משניה. למשתמש הנייד אין עודף סבלנות לחכות...
 השיטה בה פועל אנדרואיד מושתת על הפרדה בין ישות ה- Activity השייכת לאפליקציה לבין משאבי מערכת ההפעלה - ה Process. ברירת המחדל היא שכל אפליקציה רצה על Process נפרד של מערכת ההפעלה. יחד עם זאת,  בהחלט אפשרי שאפליקציה תרוץ על מספר Process, וכמו שנראה מיד, במצבים מסוימם יתכן שלאפליקציה לא יהיה מחובר אף   .Process
משמעות ההפרדה, כלומר העדר צימוד, בין ה- Activity של האפליקציה לבין ה- Process של הלינוקס קרנל, היא שברגע שמערכת ההפעלה זקוקה למשאבי זיכרון עבור משימה אותה היא מבצעת, ה- Kernel רשאי  "להרוג" Process של אפליקציה הנמצאת בהמתנה ולהשתמש במשאבים שהתפנו. הפעולה הזו נעשית באופן ברוטלי, בלי שום תהליך שמירה לפניו, סינכרון או אזהרה מוקדמת! את מי מהפעילויות שנמצאות בהמתנה תבחר המערכת להרוג? הבחירה תעשה על פי קריטריונים של חשיבות האפליקציה למשתמש (לפי נתונים מפעילות בעבר) ולפי השוואת זמני ההמתנה כשהעדיפות להרוג את מי שלא היה פעיל זמן רב יותר.
נציין שאם מערכת ההפעלה לא נמצאת במצוקת משאבים, היא בהחלט תאפשר ל- process לעבוד ברקע עד כמה שיצטרך. למרות הנאמר לעי"ל, האפליקציות שהרגו להן את ה- Process שלהן, יוכלו להמשיך לפעול ברגע שהמשתמש יחזיר אותן לפעולה מול התצוגה (ל - Foreground). זה מתאפשר היות שהנתונים החיוניים לחזרה לפעולה מהנקודה בה המשתמש צפה בה לאחרונה, נשמרים ברגע שהאפליקציה יורדת מהתצוגה ועוברת לרוץ ברקע (Background). תהליכי שמירת ושחזור הנתונים של האפליקציה מנוהלים על-פי מערכת מצבים "Process Lifecycle" או "מהלך החיים", שנתאר בהמשך.
ה"חיסול הברוטלי" של משימות הרצות ברקע, על ידי המערכת במקרה שהיא זקוקה למשאבים, עלול לעיתים להיות בעייתי. למשל: נגן mp3 שרץ ברקע, מערכת שעוקבת ברקע אחרי שינויים במקום המתקבלים מה-GPS, או שעון מעורר שרץ ברקע. הרי לא יהיה זה מן ההגיון להרוג את התהליכים האלה בכל מקרה שיחסרו משאבים ל -Activity אחר. ברור אם כן שנדרש מנגנון שיסמן למערכת ההפעלה לאפשר לתהליך לעבוד, למרות שהוא עובד ברקע ולכאורה בעדיפות לחיסול.
קיימים  מנגנוו כזה: ה- Service. 

  • ה- Service רץ ברקע ללא UI - User Interface.
  • כמות ה- Services במערכת אינה מוגבלת, כך שלא ניתן להבטיח שיהיה מספיק זיכרון עבור כולם.
  • במקרה שמערכת הפעלה נאלצת להרוג Process של Service, היא תדאג להפעילו מחדש ברגע שיתפנו משאבים מספיקים לכך.
  • ניתן להגדיר Service שיהיה חסין יותר מפני חיסול ע"י מערכת הפעלה. Service כזה, חייב לכלול הודעה למשתמש כל עוד הוא פועל. בניגוד למצב עם Service רגיל, המשתמש יכול לסגור אותו אם יחפוץ. זו הפשרה בעניין...דוגמא: נגן mp3 שרץ ברקע על service. הוא יוכל להיות מסומן ב-"בבקשה לא להרוג אותי", אבל יכיל אייקון שיסמן למשתמש שהוא עובד, וניתן יהיה להפסיק עבודתו.
עד כאן תאור אופן הטיפול בריבוי משימות, כולל ההסבר על המנגנונים שנועדו להגן מפני החיסול הברוטלי של Process ע"י המערכת.
הזכרתי במהלך הפרק את המושג "מהלך החיים" אן Life Cycle של האפליקציה. נייחד את הפרק הבא (או אחד הפרקים הבאים) לנושא.







יום שלישי, אוקטובר 19

Intents-מבוא להעברת הודעות במערכת

Intent הוא אחד מאבני הבנין החשובות של ה-Android. אני עלול לכתוב קביעות דומות גם לגבי מרכיבים אחרים, אבל בלי להגזים מדי, ה-Intent הוא ממש לב העניין.
באחד הפרקים הקודמים הגדרנו את המושג Intent כאמצעי להעברת הודעות במערכת.
השימושים להם נועד של ה-intent מגוונים:
  • אינטראקציה בין אפליקציות שונות הרצות על המערכת
  • הפעלה של Activities. זהו השימוש הנפוץ, בעזרתו ניתן לקפוץ בין Activities או במילים אחרות לקפוץ בין המסכים\UI.
  • הפצת הודעות Broadcast במערכת. הקליטה ונטיפול בהודעות המופצות נעשה בעזרת class מיוחד לעניין, ה-Broadcast Receiver. עם ה-Broadcast Receiver ניתן לטפל באירועים מיוחדים במערכת כמו סיום ה-Boot, שינויים בשעון, בעיות בסוללה, ואפילו טיפול ב-SMS. כל זה כאמור ע"י ה-Intents שאפשרים פעילות Event Driven. שליחת  Broadcast Intents נעשית ע"י המתודה (sendBroadcast(intent  . נעסוק ב-Broadcast Receiver בפרק נפרד.




הזכרנו קיומם של שני טיפוסי intent עיקריים: מפורש (Explicit), שבו הכתובת של פעילות היעד מצוינת במפורש כחלק מהמתודה שממשת את שליחת ההודעה, ומרומז (Implicit), שם ניתוב ההודעה ליעדה מנוהל ע"י מסנן המערכת - Intent Filter - המחליט על יעד ההודעה לפי פרמטרים המאפיינים אותה, כפי שמפורט להלן.
ה- Intent   מקבל את הפרמטרים הבאים :
  • שם הרכיב אליו ההודעה מיועדת. פרמטר זה לא חייב להיות מוגדר (ראה הגדרת Implicit Explicit כאן למעלה).
  • הפעולה (Action) אותה הוא אמור לבצע. למשל: ACTION_MAIN, שמשמעותה, הפעל את ה-Activity בלי להעביר שום פרמטרים או לצפות לתשובה מהרכיב מקבל ההודעה
  • הנתונים (Data). הנתונים שב- Intent תלויים כמובן בפעולה אותו הוא אמןר לבצע. אם למשל הפעולה היא ACTION_CALL הנתונים שיצורפו יכללו את מספר הטלפון. 
  • קטגוריה. תאור סוג הרכיב שצריך לטפל ב- Intent. ערך לדוגמא, CATEGORY_LAUNCHER. בהנתן קטגוריה זו, הרכיב שמקבל את ההודעה הוא כזה שמתחיל אפליקציה.
  • נתוני אקסטרה. אינפורמציה נוספת שניתן להצמיד להודעה.
ה-Intent Filter מקבל עם ההודעה את 3 השדות (Action, Category, Data). כדי שתהיה התאמה בין Intent ל - Activity צריכה להיות התאמה בשלושת השדות ואז תועבר ההודעה ליעדה. והוא בודק את שלושת השדות. הבדיקות מתבצעות כך:
ה-Action חייב להיות זהה לפחות ל-Action אחד המוגדr ב Filter, אבל אם ה-Intent לא כולל אף Action, הבדיקה תעבור בהצלחה בתנאי שה- Intent Filter מכיל לפחות Action אחד.
הקטגוריה - Category- חייבת להיות זהה לפחות לקטגוריה אחת המוגדרת בפילטר, אבל אם ה- Intent לא כולל אף קטגוריה, הבדיקה תעבור בהצלחה.
הערה לגבי Implicit Intents, המערכת מתיחסת אליהם כאל שיכים לקטגורית DEFAULT, לכן ה- Filter צריך להכיל את הקטגוריה הזו, אלא אם ה-Activity כוללת Action ו - Category מסוג MAIN ו- LAUNCHER בהתאמה. במקרה זה, אין צורך בקיום קטגורית DEFAULT ב- Filter.




נראה מספר דוגמאות. הדוגמא הראשונה, קובץ המניפסט -  שהוצג בפרק קודם. נציג שוב את השורות הרלוונטיות מאותו קובץ:


  1.         <activity android:name=".Hello"
  2.                   android:label="@string/app_name">
  3.             <intent-filter>
  4.                 <action android:name="android.intent.action.MAIN" />
  5.                 <category android:name="android.intent.category.LAUNCHER" 
  6. />
  7.             </intent-filter>
  8.         </activity>


הקטע הנ"ל שייך להגדרת ה-Activity ששמו Hello.
שורות 4 ו-5 מכילות את האינפורמציה שתשמש את ה- Intent Filter: ה-Activity מסוג MAIN מציין
נקודת כניסה ראשית לאפליקציה. MAIN היא Activity שלא נזקק לשום פרמטרים נלווים נוספים. הקטגוריה מסוג LAUNCHER מסמנת שיש להכניס את ה-Activity לרשימה של מפעיל האפליקציות - Application Launcher, כך שבמקרה הנ"ל למשל, הפעלת האפליקציה תגרום להפעלת ה- Hello Activity.
הוספתי שני פוסטים עם דוגמאות המממשות intents :
Explicit Intent
Implicit Intent

יום שני, אוקטובר 18

מושגי יסוד של אנדרואיד

מספר מושגי יסוד של האנדרואיד: 
גם אם בקריאה ראשונה ההגדרות עלולות להראות אבסטרקטיות מדי, אין ספק שבהמשך הן תתבהרנה.
  1. Activity
  2. Intent
  3. Service
 Activity - כל תצוגה על ממשק המשתמש (UI = User Interface) היא Activity. האפליקציה יכולה לכלול סדרה של תצוגות, שכל אחת מהן היא היא אקטיביטי בפני עצמה. כל Activity אחראית לשמור את הנתונים הקשורים אליה, כך שאם היא תופסק ותופעל מחדש, התצוגה תשתחזר.  הרחבה על היכולת לשחזר אינפורמציה בפרק שדן ב מחזור חיי האפליקציה.


Intent - זהו המנגנון באמצעותו מועברות הודעות במערכת. בעזרת מנגנון זה ניתן ליצור קשר עם אלמנטים בתוך האפליקציה ומחוצה לה, וכך לבצע פעולות כמו:   מעבר בין מסכי התצוגה שנעשה ע"י שליחת Intent להפעלת  Activity  הרצוי; העברת נתונים בין Activities; גישה לשרותים של מערכת האנדרואיד, למשל שליחת אימייל; יציאת קשר עם שרותים אחרים ע"ע Service כאן בהמשך; גישה למודולים של גורם שלישי הקיימים על הפלטפורמה, העברת הודעות Broadcast במערכת. קיימים שני טיפוסים של Intent - מפורש ומרומז.   ב-Intent מפורש (Explicit) השולח מציין במפורש את שם יעד ההודעה. במקרה של אינטנט מרומז (Implicit) השולח לא מציין את יעד ההודעה, אבל לפי הפרמטרים שבה, ההודעה מכוונת ע"י ה- Intent Filter ליעד המתאים. מנגנון זה מאפשר קוד גמיש בו אין צורך לדעת מראש את זהות היעד של הודעה.
באחד השיעורים הבאים נלמד את המנגנון לעומק.

Service-   פעילות שרצה ברקע, ללא אינטראקציה ישירה עם המשתמש, כלומר בלי UI. ה- Service יכול לקבל פקודות מ-Activities - שהוא כאמור בעל ממשק למשתמש - באמצעות  Intent. ה- Service מתאים לפעילויות רקע כגון נגן, טיימר וכדומה.


סביבת הפיתוח

אני מניח שכבר אינכם יכולים לחכות ומעוניינים להתחיל במלאכת הפיתוח. אך לפני כן יש להתקין את סביבת הפיתוח הכוללת את מכלול הכלים הדרושים לעבודה. הסביבה תכלול בין השאר:
  1. קומפיילר (Compiler) לבנית הקוד להרצה מתוך קוד המקור
  2. דיבגר (Debugger) המאפשר בין השאר, מעבר על הקוד צעד צעד, בדיקת תוכן הזיכרון וקביעת נקודות עצירה Breakpoints.
  3. אמולטור (Emulator) המדמה את מירב הפונקציות של מכשירי אנדרואיד כך שכל הפיתוח אפשרי גם ללא מכשיר נייד אמיתי.
  4. מגוון כלי עזר לניתור המערכת (monitoring).
  5. Eclipse המוכרת להרבה מפתחים שמהווה את סביבת הפיתוח (IDE) עליה מותקנת ערכת הפיתוח של האנדרואיד (SDK- Software Development Kit). אין זה הכרחי להשתמש באקליפס אבל בהחלט מומלץ. גם אני ממליץ. 
כל כלי הפיתוח ניתנים בחינם עבור סביבת לינוקס, חלונות ומק.
התקנה
צריך להתקין שלושה אלמנטים:
  1. JDK -Java Development Kit. להורדה מהאתר של SUN, ראה קישור למטה.
  2. Eclipse ורסיה  3.4 לפחות, ראה קישור למטה.
  3. כלי הפיתוח של אנדרואיד - SDK, ראה קישור למטה.
ההתקנה של האלמנטים הנ"ל אינה מסובכת ומנסיוני עוברת בלי בעיות. אשתדל לפרסם פוסט המפרט את תהליך ההתקנה צעד צעד.
קישורים להורדת קובצי התקנה:
  1. JDK ורסיה 6: http://www.oracle.com/technetwork/java/javase/downloads/index.html>
  2. אקליפס - לבחור Eclipse Classis להתקנה  http://www.eclipse.org/downloads
  3. אנדרואיד SDK: http://d.android.com/sdk/index.html

Hello Android


נפתח אפליקצית אנדרואיד המדפיסה hello world על המסך. כלי הפיתוח של אנדרואיד ה- ADT - Android Design Tool, מגיע עם תוכנית הדוגמא hello world מובנית. אנו נשפץ אותה מעט רק כדי לקבל תחושה על מה בעצם קורה שם.

פתיחת פרויקט
אני מניח שבשלב זה בוצעו ההתקנות של 3 המרכיבים: JDK, Eclipse, Android SDK.
אם לא, זה הזמן להתקין...

  • הפעל את Eclipse.
  • פתח באקליפס פרויקט אנדרואיד חדש: file->new->Android Project
  • הכנס את הנתונים הבאים- ראה תמונה למטה :
    • Project name: HelloWorld. זהו פרמטר של Eclipse ורק כאן יש לו משמעות.
    • Build Target: Android 2.2
    • Application name: Hello World. משמש לתצוגה.
    • Package name: com.example.hello. זהו שם חבילת ה-java. ראה פוסט בנושא.
    • Create Activity: Hello. זהו שם ה-Activity הראשון שיופעל.
    • Min SDK Version: 8

התמונה הבאה מתארת את חלון פתיחת פרויקט חדש (כפתורי הבחירה בתחתית החלון נקצצו מהתמונה):



  • לחץ על סיום (Finish).
  • כעת נריץ את הפרויקט על גבי האמולטור, ונקבל הדפסת "Hello World" על המסך. אח"כ נסביר קצת.
  • הרץ את הפרויקט:  קליק ימני על HelloAndroid ב- Project Explorer ובחר: Run as -> Android Application
  • כתוצאה מההרצה יופעל האמולטור. זה יראה כך:

כעת לחץ על Menu (כפתור שני משמאל, ראה בתמונה), ואז על בחירת חלון האפליקציות - יופיע במרכז המסך למטה. מתקבל המסך לו ציפינו, ראה תמונה:

לחץ על כפתור סמל הבית ואז על סמל המטריצה שיוביל לתצוגת האפליקציות. זהה את האייקון עם הכיתוב "Hello World". לחיצה עליו תפעיל את האפליקציה.

עד כאן ביצענו את הפעולות הבאות:
  • יצרנו פרויקט אנדרואיד חדש.
  • בנינו והרצנו אותו על האמולטור. קיבלנו הדפסה של Hello World על המסך - למרות שלא כ"כ ברור מאיפה זה הגיע...

כעת נבחן את הפרויקט וננסה להבינו. ניתן מבט על חבילת הקבצים של הפרויקט: בחלון- Package Explorer באקליפס נוכל לראות את עץ הפרויקט בו הקבצים מאורגנים בספריות (Directories), שמתפצלות מ HelloAndroid.
נביט על ספריות הפרויקט. מבנה העץ בו הן מסודרות קבוע:
  • ספרית src מכילה את קובצי המקור, הכתובים בג'אווה.
  • ספרית Android2.2 שמכילה את ספריות ג'אווה, אנדרואיד וכד'.
  • ספרית res המכילה קבצי  resource -קבצי עזר המאורגנים בתתי ספריות כפי שמפורט להלן:
    • ספריות draw - מכילות תמונות שונות לתצוגה, כולל אייקונים. נפתחו 3 ספריות- hdpi, mdpi, ldp עבור מכשירים עם רזולוציה גבוהה, בינונית ונמוכה.
    • ספרית layout - כוללת קבצים המתארים את מבנה מסך התצוגה. עבור כל מסך תצוגה נדרש קובץ כזה, הבנוי בפורמט XML. שימוש בקובץ layout מאפשר הפרדה בין קוד התוכנית לעיצוב המסך ובכך מאפשר שינוי עיצוב המסך בלי שום צורך לשנות את התוכנית עצמה. בהמשך נדון במבנה קובץ ה-layout.
    • ספרית values - כוללת קבצים המכילים ערכים קבועים, למשל שמות (מחרוזות\strings) להדפסה על המסך. קיימים (כיום) 6 סוגים של קבצי XML שניתן לשמור תחת values:
      • strings
      • styles
      • animations
      • dimensions
      • colors
      • arrays
    • ריכוז הערכים והמאפיינים בקובץ נפרד מאפשר לבצע בקלות שינויים  בלי צורך לשנות את התוכנית עצמה או את ה- layout. השיטה הזו בהחלט מאד דומה לשיטה של HTMS ו CSS. בדוגמא שלנו נשתמש ב- strings.xml, כשבפוסטים אחרים  נדגים שימוש בשאר הסוגים של קבצי ה-values.
בשיעור זה נתייחס ל 4 קבצים:
קובץ layout ששמו main.xml.
קובץ values ששמו strings.xml
קובץ draw ששמו icon.png
קובץ src ששמו Hello.java.

  • ספרית gen מכילה קבצים המיוצרים אוטומטית על ידי המערכת לצורכת בניית האפליקציה. הקובץ  שנמצא ב-gen  הוא R.java,  המכיל אינפורמציה שנאספה מקבצי ה- resourse ומאורגנת בפורמט המתאים לשפת ג'אווה. האינפורמציה מאורגנת ב- classes עם מספרי זהוי (id) על פיהם התוכנית יכולה להתייחס ל- resources. נציין ש-class  R מנוהל אוטומטית ע"י ה-plug-in של האנדרואיד באקליפס. המערכת תזהה כל שינוי שיעשה באלמנטים שבספריית res, ותעדכן את הקובץ R.java בהתאם. נסו למשל להוסיך קובץ תמונה לספריית draw ובידקו את העידכון האוטומטי ב- R.java. באותו אופן שנו את קובץ ה-layout וודאו את השינוי האוטומטי ב-R.java. בתוך הקובץ אפשר לראות מספרים הקסאדצימאלים לשימושן של מנהל ה-resources של אנדרואיד. מדובר במצביעים מסוג Integer. זוהי השיטה של אנדרואיד לנהל את המשאבים בצורה חסכונית. החיסרון - זוהי הצגה אבסטרקטית. בכל אופן - המפתח לא נדרש להכיא את תוכן הקובץ הזה בכלל!





     ה AndroidManifest.xml הוא קובץ המתאר את מרכיבי האפליקציה ומגדיר את החיבורים בינהם.נתייחס אליו באחד מהשיעורים הבאים.



    קובץ ה-Layout נראה כך:

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3.     android:orientation="vertical"
    4.     android:layout_width="fill_parent"
    5.     android:layout_height="fill_parent"
    6.     >
    7. <TextView 
    8.     android:layout_width="fill_parent"
    9.     android:layout_height="wrap_content"
    10.     android:text="@string/hello"
    11.     />
    12. </LinearLayout>

    השורה הראשונה מסמנת שמדובר בקובץ xml ורסיה 1.0 וקידוד הקובץ.
    מכאן ואילך יוגדר מבנה התצוגה. תחילה מעטפת התצוגה הכוללת, ואח"כ יוגדרו האופי והגודל של כל אחד ממרכיבי התצוגה. מרכיבי התצוגה יוגדרו אחד אחרי השני כשכל רכיב תצוגה תופס "פרוסה" \ Section מהתצוגה או בתרמינולוגית אנדרואיד מה UI - User Interface. באפליקציה הנ"ל יש רכיב תצוגה יחיד, ה TextView.
    השורה השניה מגדירה את מבנה המעטפת של המסך, ואכן המזהה LinearLayout עוטף את שאר ההגדרות בהתחלה ובסוף. הוא מגדיר מבנה בכיוון אנכי (orientation vertical) כך שמרכיבי התצוגה ימוקמו אחד אחרי השני בציר האנכי. רוחב התצוגה וגודל התצוגה מוגדרים fill_parent שמשמעותו - השתמש בכל המרחב העוטף הקיים.
    עד כאן הגדרות המעטפת שסומנו ע"י התג LinearLayout. בשורה 7 התג TextView מגדיר איזור להדפסת טקסט בתצוגה. גם כאן מגדירים את רוחב התצוגה (שורה 8)- שוב כ fill_parent אבל גובה התצוגה (שורה 9) מוגדר כאן כ-wrap_content שמשמעותו "השתמש בגודל הדרוש להכלת התוכן הקיים.
    וכעת להגדרת תוכן התצוגה (שורה 10):  "text="@string/hello. משמעות הסימון היא שהטקסט שיודפס נמצא בקובץ בשם string.xml והטקסט מוצמד למחרוזת ששמה hello. בדיוק לזה התכוונתי כשתארתי את תפקידו של קובץ strings.xml הנמצא בספרית values. נעבור כעת לצפות בו.




    strings.xml:
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="hello">Hello World, Hello!</string>
        <string name="app_name">Hello World</string>
    </resources>


    בקובץ מוגדרות שתי מחרוזות \ string: הראשונה hello אליה ראינו התיחסות בפסקה שתארה את קובץ ה- layout. כעת ניתן לראות שהערך שמושם למחרוזת בשם hello הוא Hello World, Hello!, ואכן זה היה הפלט שהודפס למסך. המחרוזת השניה המוגדרת בקובץ שלנו היא ה app_name, כלומר שם האפליקציה. שם זה מצורף בראש הפלט.
    להמשך הבנת התמונה נבחן את קובץ הגאווה hello.java.

      Hello.java

      package com.example.hello;
      1. import android.app.Activity;
      2. import android.os.Bundle;
      3. public class Hello extends Activity {
      4.     /** Called when the activity is first created. */
      5.     @Override
      6.     public void onCreate(Bundle savedInstanceState) {
      7.         super.onCreate(savedInstanceState);
      8.         setContentView(R.layout.main);
      9.     }
      10. }
      שורה 3 מגדירה מחלקה (class) בשם Hello. זהו השם שנתנו ל-activity בזמן יצירת הפרויקט. כל מחלקה נשמרת בקובץ נפרד ששמו כשם המחלקה. המחלקה Hello מרחיבה\יורשת את המחלקה Activity (עדיין שורה 3). המחלקה Activity מטפלת ביצירת חלון המאפשר את ממשק המשתמש UI.
      המתודה היחידה המוגדרת במחלקה היא onCreate. מתודה זו מופעלת תמיד עם הפעלת המחלקה. שורה 7 מפעילה את הפונקציה המקורית שנמצאת במחלקת Activity אותה אנו יורשים. שורה 8 מפעילה מתודה המופעלת על קובץ main.xml הנמצא בספרית layout. מתודה זו לוקחת את האינפורמציה קובץ ה- layout (אותו סקרנו בפרוט), ובונה את התצוגה לפיו. זהו.
      כאן אני מציע תרגיל קטן: נשנה את הפלט המודפס לפלט בעברית: "שלום אנדרואיד, להתראות בשיעור הבא". גש לקובץ strings.xml והחלף את Hello World, Hello! ב- שלום אנדרואיד, להתראות בשיעור הבא. שמור את הקובץ והרץ את האפליקציה מחדש.