במדריך הזה מפורטות שיטות מומלצות וארכיטקטורה מומלצת ליצירת אפליקציות איכותיות וחזקות.
חוויית המשתמש באפליקציות לנייד
אפליקציה רגילה ל-Android מכילה כמה רכיבי אפליקציה, כולל פעילויות, קטעים, שירותים, ספקי תוכן ומקבלי שידור. רוב רכיבי האפליקציה האלה מפורטים במניפסט של האפליקציה. לאחר מכן, מערכת Android OS משתמשת בקובץ הזה כדי להחליט איך לשלב את האפליקציה שלכם בחוויית המשתמש הכוללת של המכשיר. מכיוון שאפליקציה רגילה ל-Android עשויה להכיל כמה רכיבים, ולרוב המשתמשים מקיימים אינטראקציה עם כמה אפליקציות בתקופה קצרה, האפליקציות צריכות להתאים לסוגים שונים של תהליכי עבודה ומשימות שמנוהלים על ידי המשתמשים.
חשוב לזכור שמכשירים ניידים גם מוגבלים במשאבים, ולכן מערכת ההפעלה עשויה להפסיק תהליכים של אפליקציות מסוימות בכל שלב כדי לפנות מקום לתהליכים חדשים.
לאור התנאים בסביבה הזו, יכול להיות שרכיבי האפליקציה יופעלו בנפרד ובאופן לא סדור, ומערכת ההפעלה או המשתמש יכולים להשמיד אותם בכל שלב. מכיוון שאי אפשר לשלוט באירועים האלה, אסור לאחסן או לשמור בזיכרון נתונים או מצב של אפליקציה ברכיבי האפליקציה, ורכיבי האפליקציה לא צריכים להיות תלויים זה בזה.
עקרונות ארכיטקטוניים נפוצים
אם אסור להשתמש ברכיבי אפליקציה כדי לאחסן את נתוני האפליקציה ואת המצב שלה, איך צריך לעצב את האפליקציה במקום זאת?
ככל שהאפליקציות ל-Android גדלות, חשוב להגדיר ארכיטקטורה שמאפשרת להתאים את האפליקציה לעומס, מגבירה את העמידות שלה ומקלה על הבדיקה שלה.
ארכיטקטורת האפליקציה מגדירה את הגבולות בין חלקי האפליקציה ואת האחריות של כל חלק. כדי לעמוד בצרכים שצוינו למעלה, צריך לתכנן את הארכיטקטורה של האפליקציה בהתאם לכמה עקרונות ספציפיים.
הפרדת הבעיות
העיקרון החשוב ביותר הוא הפרדת הבעיות.
טעות נפוצה היא לכתוב את כל הקוד ב-Activity
או ב-Fragment
. הכיתות שמבוססות על ממשק המשתמש צריכות להכיל רק לוגיקה שמטפלת באינטראקציות בין ממשק המשתמש לבין מערכת ההפעלה. אם תמנעו מהוספת תכונות מיותרות לכיתות האלה, תוכלו למנוע הרבה בעיות שקשורות למחזור החיים של הרכיבים ולשפר את היכולת לבדוק את הכיתות האלה.
חשוב לזכור שאתם לא הבעלים של ההטמעות של Activity
ו-Fragment
, אלא אלה רק כיתות מקשרות שמייצגות את החוזה בין מערכת ההפעלה Android לאפליקציה שלכם. מערכת ההפעלה יכולה למחוק אותן בכל שלב על סמך אינטראקציות של משתמשים או בגלל תנאי מערכת כמו זיכרון נמוך. כדי לספק חוויית משתמש מספקת ולנהל את האפליקציה בצורה נוחה יותר, מומלץ לצמצם את התלות בהם.
ממשק המשתמש של Drive ממודלים של נתונים
עקרון חשוב נוסף הוא שצריך להסתמך על מודלים של נתונים כדי ליצור את ממשק המשתמש, רצוי מודלים עקביים. מודלים של נתונים מייצגים את הנתונים של אפליקציה. הם עצמאיים מרכיבי ממשק המשתמש ומרכיבים אחרים באפליקציה. כלומר, הם לא קשורים למחזור החיים של ממשק המשתמש ורכיבי האפליקציה, אבל הם עדיין יימחקו כשמערכת ההפעלה תחליט להסיר את התהליך של האפליקציה מהזיכרון.
מודלים מתמידים הם אידיאליים מהסיבות הבאות:
המשתמשים לא יאבדו נתונים אם מערכת ההפעלה של Android תהרוס את האפליקציה כדי לפנות משאבים.
האפליקציה ממשיכה לפעול גם במקרים שבהם החיבור לרשת לא יציב או לא זמין.
אם מבססים את ארכיטקטורת האפליקציה על כיתות של מודל נתונים, האפליקציה נעשית חזקה יותר וניתנת לבדיקה בקלות רבה יותר.
מקור מרוכז אחד
כשמגדירים סוג נתונים חדש באפליקציה, צריך להקצות לו מקור נתונים יחיד (SSOT). ה-SSOT הוא הבעלים של הנתונים האלה, ורק הוא יכול לשנות אותם. כדי לעשות זאת, ה-SSOT חושף את הנתונים באמצעות סוג שלא ניתן לשינוי, וכדי לשנות את הנתונים, ה-SSOT חושף פונקציות או מקבל אירועים שסוגי נתונים אחרים יכולים להפעיל.
לדפוס הזה יש כמה יתרונות:
- הוא מרכז את כל השינויים בסוג נתונים מסוים במקום אחד.
- הוא מגן על הנתונים כדי שסוגי נתונים אחרים לא יוכלו לשבש אותם.
- כך קל יותר לעקוב אחרי השינויים בנתונים. כך קל יותר לזהות באגים.
באפליקציה שמתמקדת בעבודה אופליין, מקור האמת של נתוני האפליקציה הוא בדרך כלל מסד נתונים. במקרים אחרים, מקור האמת יכול להיות ViewModel או אפילו ממשק המשתמש.
זרימת נתונים חד-כיוונית
העיקרון של מקור נתונים יחיד מופיע לעיתים קרובות במדריכים שלנו עם התבנית של תעבורת נתונים חד-כיוונית (UDF). ב-UDF, המצב זורם בכיוון אחד בלבד. האירועים שמשנים את זרימת הנתונים בכיוון ההפוך.
ב-Android, בדרך כלל המצב או הנתונים עוברים מהסוגים ברמת ההיררכיה הגבוהה יותר לסוגים ברמת ההיררכיה הנמוכה יותר. בדרך כלל, אירועים מופעלים מהסוגים ברמת ההיקף הנמוכה יותר עד שהם מגיעים ל-SSOT של סוג הנתונים התואם. לדוגמה, נתוני האפליקציה בדרך כלל עוברים ממקורות הנתונים לממשק המשתמש. אירועי משתמשים, כמו לחיצות על לחצנים, עוברים מממשק המשתמש אל SSOT, שבו נתוני האפליקציה משתנים ומוצגים בסוג שאינו ניתן לשינוי.
התבנית הזו מבטיחה עקביות טובה יותר של הנתונים, היא פחות נוטה לשגיאות, קל יותר לנפות בה באגים והיא כוללת את כל היתרונות של תבנית SSOT.
ארכיטקטורה מומלצת לאפליקציות
בקטע הזה מוסבר איך לבנות את האפליקציה בהתאם לשיטות המומלצות.
בהתאם לעקרונות הארכיטקטוניים הנפוצים שצוינו בקטע הקודם, לכל אפליקציה צריכות להיות לפחות שתי שכבות:
- שכבת ממשק המשתמש שמציגה את נתוני האפליקציה במסך.
- שכבת הנתונים שמכילה את הלוגיקה העסקית של האפליקציה ומציגה את נתוני האפליקציה.
אפשר להוסיף שכבה נוספת שנקראת שכבת הדומיין כדי לפשט את האינטראקציות בין ממשק המשתמש לשכבות הנתונים ולאפשר שימוש חוזר בהן.
![בארכיטקטורה רגילה של אפליקציה, שכבת ממשק המשתמש מקבלת את נתוני האפליקציה משכבת הנתונים או משכבת הדומיין האופציונלית, שנמצאת בין שכבת ממשק המשתמש לשכבת הנתונים.](https://developer.android.google.cn/static/topic/libraries/architecture/images/mad-arch-overview.png?hl=he)
ארכיטקטורה מודרנית של אפליקציות
ארכיטקטורת האפליקציות המודרנית מעודדת שימוש, בין היתר, בשיטות הבאות:
- ארכיטקטורה תגובה ומרובדת.
- תעבורת נתונים חד-כיוונית (UDF) בכל השכבות של האפליקציה.
- שכבת ממשק משתמש עם מאגרי מצבים לניהול המורכבות של ממשק המשתמש.
- קורוטינים וזרמים.
- שיטות מומלצות להזרקת יחסי תלות.
מידע נוסף זמין בקטעים הבאים, בדפים האחרים בנושא ארכיטקטורה שבתוכן העניינים ובדף ההמלצות שמכיל סיכום של השיטות המומלצות החשובות ביותר.
שכבת ממשק המשתמש
תפקיד שכבת ממשק המשתמש (או שכבת ההצגה) הוא להציג את נתוני האפליקציה במסך. בכל פעם שהנתונים משתנים, בין שבגלל אינטראקציה של משתמש (למשל לחיצה על לחצן) ובין שבגלל קלט חיצוני (למשל תגובה מהרשת), ממשק המשתמש צריך להתעדכן כך שישקף את השינויים.
שכבת ממשק המשתמש מורכבת משני דברים:
- רכיבי ממשק המשתמש שמציגים את הנתונים במסך. אפשר ליצור את הרכיבים האלה באמצעות תצוגות או פונקציות של Jetpack פיתוח נייטיב.
- מאגרי מצב (כמו הכיתות של ViewModel) שמאחסנים נתונים, חושפים אותם לממשק המשתמש ומטפלים בלוגיקה.
![בארכיטקטורה רגילה, רכיבי ממשק המשתמש של שכבת ממשק המשתמש תלויים בבעלים של המצב, שבתורה תלויים בכיתות משכבת הנתונים או משכבת הדומיין האופציונלית.](https://developer.android.google.cn/static/topic/libraries/architecture/images/mad-arch-overview-ui.png?hl=he)
מידע נוסף על השכבה הזו זמין בדף שכבת ממשק המשתמש.
שכבת נתונים
שכבת הנתונים של אפליקציה מכילה את הלוגיקה העסקית. הלוגיקה העסקית היא זו שמעניקה ערך לאפליקציה – היא מורכבת מכללים שקובעים איך האפליקציה יוצרת, מאחסנת ומשנה נתונים.
שכבת הנתונים מורכבת ממאגרים, שכל אחד מהם יכול להכיל 0 עד הרבה מקורות נתונים. צריך ליצור כיתה של מאגר לכל סוג נתונים שונה שאתם מטפלים בו באפליקציה. לדוגמה, אפשר ליצור כיתה MoviesRepository
לנתונים שקשורים לסרטים, או כיתה PaymentsRepository
לנתונים שקשורים לתשלומים.
![בארכיטקטורה רגילה, המאגרים של שכבת הנתונים מספקים נתונים לשאר האפליקציה ותלויים במקורות הנתונים.](https://developer.android.google.cn/static/topic/libraries/architecture/images/mad-arch-overview-data.png?hl=he)
כיתות המאגר אחראיות על המשימות הבאות:
- חשיפת נתונים לשאר האפליקציה.
- ריכוז השינויים בנתונים.
- פתרון התנגשויות בין כמה מקורות נתונים.
- הסרת מקורות הנתונים משאר האפליקציה.
- מכיל לוגיקה עסקית.
כל סוג של מקור נתונים צריך להיות אחראי לעבודה עם מקור נתונים אחד בלבד, שיכול להיות קובץ, מקור ברשת או מסד נתונים מקומי. הכיתות של מקורות הנתונים הן הגשר בין האפליקציה לבין המערכת לפעולות נתונים.
מידע נוסף על השכבה הזו זמין בדף שכבת הנתונים.
שכבת הדומיין
שכבת הדומיין היא שכבה אופציונלית שנמצאת בין שכבת ממשק המשתמש לשכבות הנתונים.
שכבת הדומיין אחראית לאנקפסולציה של לוגיקה עסקית מורכבת, או של לוגיקה עסקית פשוטה שנעשה בה שימוש חוזר על ידי כמה ViewModels. השכבה הזו היא אופציונלית כי לא כל האפליקציות יהיו כפופות לדרישות האלה. כדאי להשתמש בה רק במקרים שבהם יש צורך – למשל, כדי להתמודד עם מורכבות או כדי לאפשר שימוש חוזר.
![כשהיא כלולה, שכבת הדומיין האופציונלית מספקת יחסי תלות לשכבת ממשק המשתמש, ותלויה בשכבת הנתונים.](https://developer.android.google.cn/static/topic/libraries/architecture/images/mad-arch-overview-domain.png?hl=he)
בדרך כלל, הכיתות בשכבה הזו נקראות תרחישים לדוגמה או רכיבי אינטראקציה. לכל תרחיש שימוש צריכה להיות אחריות על פונקציונליות יחידה. לדוגמה, יכול להיות שבאפליקציה תהיה כיתה GetTimeZoneUseCase
אם כמה ViewModels מסתמכים על אזורי זמן כדי להציג את ההודעה המתאימה במסך.
מידע נוסף על השכבה הזו זמין בדף השכבה של הדומיין.
ניהול יחסי התלות בין רכיבים
כדי שהכיתות באפליקציה יפעלו כראוי, הן תלויות בכיתות אחרות. אפשר להשתמש באחד מדפוסי התכנון הבאים כדי לאסוף את יחסי התלות של כיתה מסוימת:
- הזרקת תלויות (DI): הזרקת תלויות מאפשרת לכיתות להגדיר את יחסי התלות שלהן בלי ליצור אותן. במהלך זמן הריצה, כיתה אחרת אחראית לספק את יחסי התלות האלה.
- Service locator: התבנית Service locator מספקת מרשם שבו הכיתות יכולות לקבל את יחסי התלות שלהן במקום ליצור אותם.
התבניות האלה מאפשרות להתאים את הקוד לעומס, כי הן מספקות תבניות ברורות לניהול יחסי התלות בלי שכפול קוד או הוספת מורכבות. בנוסף, התבניות האלה מאפשרות לעבור במהירות בין הטמעות לבדיקה לטמעות בסביבת הייצור.
אנחנו ממליצים לפעול לפי דפוסי הזרקת התלות ולהשתמש בספריית Hilt באפליקציות ל-Android. Hilt יוצר אובייקטים באופן אוטומטי על ידי סריקה של עץ יחסי התלות, מספק ערבויות לגבי יחסי התלות בזמן הידור ויוצר קונטיינרים של יחסי תלות לכיתות של מסגרת Android.
שיטות מומלצות כלליות
תכנות היא תחום יצירתי, ופיתוח אפליקציות ל-Android הוא לא יוצא דופן. יש הרבה דרכים לפתור בעיה. אפשר להעביר נתונים בין כמה פעילויות או קטעים, לאחזר נתונים מרחוק ולשמור אותם באופן מקומי למצב אופליין, או לטפל במספר רב של תרחישים נפוצים אחרים שאפליקציות מורכבות נתקלות בהם.
ההמלצות הבאות הן לא חובה, אבל ברוב המקרים כדאי לפעול לפיהן כדי שבסיס הקוד יהיה עמיד יותר, קל יותר לבדיקה ולתחזוקה בטווח הארוך:
לא לאחסן נתונים ברכיבי האפליקציה.
מומלץ לא להגדיר את נקודות הכניסה של האפליקציה – כמו פעילויות, שירותים ומקלטי שידור – כמקורות נתונים. במקום זאת, הם צריכים לתאם רק עם רכיבים אחרים כדי לאחזר את קבוצת המשנה של הנתונים שרלוונטיים לנקודת הכניסה הזו. כל רכיב באפליקציה נמשך זמן קצר יחסית, בהתאם לאינטראקציה של המשתמש עם המכשיר ולמצב המערכת הכולל הנוכחי.
צמצום יחסי התלות בכיתות של Android
רכיבי האפליקציה צריכים להיות הכיתות היחידות שמסתמכות על ממשקי API של Android framework SDK, כמו Context
או Toast
. הפשטה של כיתות אחרות באפליקציה מאפשרת לבצע בדיקות בקלות ומפחיתה את הצירוף באפליקציה.
יצירת תחומי אחריות מוגדרים היטב בין המודולים השונים באפליקציה
לדוגמה, אל תפיץ את הקוד שמעלה נתונים מהרשת במספר כיתות או חבילות בבסיס הקוד. באופן דומה, אל תגדירו כמה אחריויות לא קשורות – כמו אחסון נתונים במטמון וקישור נתונים – באותה כיתה. כדי לעשות זאת, מומלץ לפעול בהתאם לארכיטקטורה המומלצת לאפליקציות.
חשיפת כמה שפחות מכל מודול
לדוגמה, אל תתפתו ליצור קיצור דרך שחושף פרט מפורט של הטמעה פנימית מתוך מודול. אולי תוכלו לחסוך קצת זמן בטווח הקצר, אבל סביר להניח שתצברו חובות טכניים רבים במהלך התפתחות קוד הבסיס.
התמקדו בליבה הייחודית של האפליקציה כדי שהיא תבלוט מול אפליקציות אחרות.
אל תכתבו שוב ושוב את אותו קוד סטנדרטי. במקום זאת, כדאי להתמקד בזמן ובאנרגיה שלכם בדברים שמאפיינים את האפליקציה שלכם, ולתת לספריות Jetpack ולספריות מומלצות אחרות לטפל בקוד הסטנדרטי החוזר.
כדאי לחשוב איך לבדוק כל חלק באפליקציה בנפרד.
לדוגמה, כשיש ממשק API מוגדר היטב לאחזור נתונים מהרשת, קל יותר לבדוק את המודול ששומר את הנתונים האלה במסד נתונים מקומי. אם במקום זאת תערבבו את הלוגיקה משני המודולים האלה במקום אחד, או תפיצו את קוד הרשתות בבסיס הקוד כולו, יהיה הרבה יותר קשה – אם לא בלתי אפשרי – לבדוק אותו בצורה יעילה.
הסוגים אחראים למדיניות שלהם בנושא בו-זמניות.
אם סוג מסוים מבצע עבודה חוסמת ממושכת, הוא צריך להיות אחראי להעברת החישוב הזה לשרשור הנכון. הסוג הספציפי הזה יודע איזה סוג חישוב הוא מבצע ובאיזה חוט הוא צריך להתבצע. הסוגים צריכים להיות בטוחים לשימוש ב-main, כלומר אפשר להפעיל אותם מה-thread הראשי בלי לחסום אותו.
שומרים כמה שיותר נתונים רלוונטיים ועדכניים.
כך המשתמשים יוכלו ליהנות מהפונקציונליות של האפליקציה גם כשהמכשיר שלהם במצב אופליין. חשוב לזכור שלא כל המשתמשים נהנים מחיבור מהיר וקבוע, וגם אם כן, יכול להיות שהם יקבלו קליטה גרועה במקומות הומי אדם.
היתרונות של ארכיטקטורה
הטמעת ארכיטקטורה טובה באפליקציה מביאה יתרונות רבים לצוות הפרויקט ולצוות ההנדסה:
- כך אפשר לשפר את יכולת התחזוקה, האיכות והעמידות של האפליקציה הכוללת.
- היא מאפשרת לאפליקציה להתאים את עצמה למסך. יותר אנשים וצוותים יכולים לתרום לאותו קוד בסיס עם מינימום של התנגשויות בקוד.
- הוא עוזר בתהליך ההצטרפות. ארכיטקטורה מביאה עקביות לפרויקט, כך שאנשים חדשים בצוות יכולים להיכנס לעניינים במהירות ולהיות יעילים יותר בפחות זמן.
- קל יותר לבדוק אותו. ארכיטקטורה טובה מעודדת שימוש בסוגי נתונים פשוטים יותר, שקל יותר לבדוק אותם בדרך כלל.
- אפשר לחקור באיטרציות באמצעים שיטתיים באמצעות תהליכים מוגדרים היטב.
השקעה בארכיטקטורה משפיעה גם ישירות על המשתמשים. הם נהנים מאפליקציה יציבה יותר ומתכונות נוספות, בזכות צוות מהנדסים פרודוקטיבי יותר. עם זאת, גם הארכיטקטורה דורשת השקעה מראש של זמן. כדי להצדיק את הזמן הזה לשאר החברה, כדאי לעיין בסקירות המקרה האלה שבהן חברות אחרות משתפות את סיפורי ההצלחה שלהן כשהאפליקציה שלהן בנויה עם ארכיטקטורה טובה.
דוגמיות
הדוגמאות הבאות של Google מדגימות ארכיטקטורה טובה של אפליקציות. כדאי לעיין בהם כדי לראות איך ההנחיות האלה עובדות בפועל:
מומלץ עבורך
- הערה: טקסט הקישור מוצג כש-JavaScript מושבת
- שכבת נתונים
- שכבת ממשק המשתמש
- אירועי UI