בפרויקטים גדולים יותר, או בפרויקטים שמטמיעים הרבה לוגיקה של build בהתאמה אישית, יכול להיות שתצטרכו לבחון לעומק את תהליך ה-build כדי למצוא צווארי בקבוק.
כדי לעשות את זה, אתם יכולים ליצור פרופיל של משך הזמן שלוקח ל-Gradle לבצע כל שלב במחזור החיים של ה-build וכל משימת build. לדוגמה, אם בפרופיל ה-build מוצג ש-Gradle משקיע יותר מדי זמן בהגדרת הפרויקט, יכול להיות שצריך להעביר את לוגיקת ה-build המותאמת אישית מחוץ לשלב ההגדרה.
בנוסף, אם המשימה mergeDevDebugResources צורכת חלק גדול מזמן ה-build, יכול להיות שצריך להמיר את התמונות ל-WebP או להשבית את דחיסת ה-PNG.
אם אתם משתמשים ב-Android Studio בגרסה 4.0 ואילך, הדרך הכי טובה לבדוק בעיות בביצועים של ה-build היא להשתמש ב-Build Analyzer.
בנוסף, יש שתי אפשרויות ליצירת פרופיל של ה-build מחוץ ל-Android Studio:
הכלי העצמאי
gradle-profiler, כלי חזק לניתוח מעמיק של ה-build.האפשרות
--profileשל Gradle, כלי נוח שזמין משורת הפקודה של Gradle.
שימוש בכלי gradle-profiler העצמאי
כדי למצוא את הגדרת הפרויקט שמספקת את מהירות ה-build הטובה ביותר, כדאי להשתמש ב-Gradle Profiler, כלי לאיסוף מידע על פרופילים ועל השוואה לשוק (benchmarking) של גרסאות build של Gradle. בעזרת Gradle Profiler אפשר ליצור תרחישי build ולהריץ אותם כמה פעמים, כדי למנוע שונות גבוהה בין התוצאות ולוודא שאפשר לשחזר אותן.
כדאי להשתמש במצב השוואה לשוק כדי לאסוף מידע על גרסאות build נקיות ומצטברות, ובמצב פרופיילינג כדי לאסוף מידע מפורט יותר על ההרצות, כולל תמונות מצב של המעבד (CPU).
הגדרות הפרויקט להשוואה לשוק כוללות:
- גרסאות הפלאגין
- גרסאות Gradle
- הגדרות JVM (גודל ערימה, גודל permgen, איסוף זבל וכו')
- מספר ה-Gradle workers (
org.gradle.workers.max) - אפשרויות לכל פלאגין לשיפור הביצועים
תחילת העבודה
- מתקינים את gradle-profiler לפי ההוראות האלה.
- הרצה:
gradle-profiler --benchmark --project-dir <root-project> :app:assembleDebug
הפעולה הזו תבצע השוואה של גרסה עדכנית לחלוטין, כי --benchmark מריץ את המשימה כמה פעמים בלי לשנות את הפרויקט בין לבין. לאחר מכן, הוא ייצור דוח HTML בספרייה profile-out/ שבו יוצגו זמני ה-build.
יש תרחישים אחרים שבהם יכול להיות שימושי יותר לבצע השוואה:
- שינויים בקוד בגוף של שיטה במחלקה שבה אתם מבצעים את רוב העבודה.
- שינויים ב-API במודול שמשמש את כל הפרויקט. השינויים האלה פחות תכופים משינויים בקוד שלכם, אבל יש להם השפעה גדולה יותר וכדאי למדוד אותה.
- עריכות של פריסה כדי לדמות חזרה על עבודת ממשק משתמש.
- עריכות של מחרוזות כדי לדמות עבודה על תרגום.
- מנקים גרסאות build כדי לדמות שינויים ב-build עצמו (לדוגמה, עדכון של הפלאגין של Android Gradle, עדכון של Gradle או עריכות של קוד ה-build שלכם ב-
buildSrc).
כדי להשוות בין תרחישי השימוש האלה, אתם יכולים ליצור תרחיש שישמש להפעלת gradle-profiler ויחיל את השינויים המתאימים על המקורות שלכם. בקטעים הבאים מתוארים כמה תרחישים נפוצים.
יצירת פרופילים של הגדרות שונות של זיכרון/מעבד (CPU)
כדי להשוות בין הגדרות שונות של זיכרון ומעבד (CPU), אפשר ליצור כמה תרחישים שמשתמשים בערכים שונים של org.gradle.jvmargs. לדוגמה, אפשר ליצור תרחישים:
# <root-project>/scenarios.txt
clean_build_2gb_4workers {
tasks = [":app:assembleDebug"]
gradle-args = ["--max-workers=4"]
jvm-args = ["-Xmx2048m"]
cleanup-tasks = ["clean"]
}
clean_build_parallelGC {
tasks = [":app:assembleDebug"]
jvm-args = ["-XX:+UseParallelGC"]
cleanup-tasks = ["clean"]
}
clean_build_G1GC_4gb {
tasks = [":app:assembleDebug"]
jvm-args = ["-Xmx4096m", "-XX:+UseG1GC"]
cleanup-tasks = ["clean"]
}
הפעלת gradle-profiler --benchmark --project-dir
<root-project> --scenario-file scenarios.txt תריץ שלושה תרחישים, ותוכלו להשוות כמה זמן לוקח ל-:app:assembleDebug בכל אחת מההגדרות האלה.
יצירת פרופילים של גרסאות שונות של הפלאגין של Gradle
כדי לגלות איך שינוי הגרסה של הפלאגין של Gradle משפיע על זמני ה-build, צריך ליצור תרחיש להשוואה. כדי לעשות את זה, צריך לבצע כמה הכנות כדי שיהיה אפשר להחדיר את גרסת הפלאגין מהתרחיש. משנים את קובץ build.gradle ברמה הבסיסית (root):
# <root-project>/build.gradle
buildscript {
def agpVersion = providers.systemProperty("agpVersion").forUseAtConfigurationTime().orNull ?: '4.1.0'
ext.kotlin = providers.systemProperty('kotlinVersion').forUseAtConfigurationTime().orNull ?: '1.4.0'
dependencies {
classpath "com.android.tools.build:gradle:$agpVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin"
}
}
עכשיו אפשר לציין את הגרסאות של הפלאגין של Android Gradle ושל הפלאגין של Kotlin Gradle מקובץ התרחישים, ולהגדיר שהתרחיש יוסיף שיטה חדשה לקובצי המקור:
# <root-project>/scenarios.txt
non_abi_change_agp4.1.0_kotlin1.4.10 {
tasks = [":app:assembleDebug"]
apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
"app/src/main/java/com/example/your_app/your_code_file.kt"]
System-properties {
"agpVersion" = "4.1.0"
"kotlinVersion" = "1.4.10"
}
non_abi_change_agp4.2.0_kotlin1.4.20 {
tasks = [":app:assembleDebug"]
apply-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
"app/src/main/java/com/example/your_app/your_code_file.kt"]
System-properties {
"agpVersion" = "4.2.0-alpha16"
"kotlinVersion" = "1.4.20"
}
יצירת פרופיל של build מצטבר
רוב ה-builds הם מצטברים, ולכן זה אחד התרחישים הכי חשובים לפרופיילינג. ל-Gradle profiler יש תמיכה נרחבת ביצירת פרופילים של גרסאות build מצטברות. הוא יכול להחיל שינויים על קובץ מקור באופן אוטומטי על ידי שינוי גוף של שיטה, הוספת שיטה חדשה או שינוי של פריסה או משאב מחרוזת. לדוגמה, אפשר ליצור תרחישים מצטברים כאלה:
# <root-project>/scenarios.txt
non_abi_change {
tasks = [":app:assembleDebug"]
apply-non-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
"app/src/main/java/com/example/your_app/your_code_file.kt"]
}
abi_change {
tasks = [":app:assembleDebug"]
apply-abi-change-to = ["app/src/main/java/com/example/your_app/your_code_file.java,
"app/src/main/java/com/example/your_app/your_code_file.kt"]
}
layout_change {
tasks = [":app:assembleDebug"]
apply-android-layout-change-to = "app/src/main/res/your_layout_file.xml"
}
string_resource_change {
tasks = [":app:assembleDebug"]
apply-android-resource-value-change-to = "app/src/main/res/values/strings.xml"
}
הרצת הפקודה gradle-profiler --benchmark --project-dir <root-project>
--scenario-file scenarios.txt תיצור את דוח ה-HTML עם נתוני ההשוואה לשוק.
אפשר לשלב תרחישים מצטברים עם הגדרות אחרות, כמו גודל ה-heap, מספר ה-workers או גרסת Gradle:
# <root-project>/scenarios.txt
non_abi_change_4g {
tasks = [":app:assembleDebug"]
apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
"app/src/main/java/com/example/your_app/your_code_file.kt"]
jvm-args = ["-Xmx4096m"]
}
non_abi_change_4g_8workers {
tasks = [":app:assembleDebug"]
apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
"app/src/main/java/com/example/your_app/your_code_file.kt"]
jvm-args = ["-Xmx4096m"]
gradle-args = ["--max-workers=8"]
}
non_abi_change_3g_gradle67 {
tasks = [":app:assembleDebug"]
apply-non-abi-change-to ["app/src/main/java/com/example/your_app/your_code_file.java,
"app/src/main/java/com/example/your_app/your_code_file.kt"]
jvm-args = ["-Xmx3072m"]
version = ["6.7"]
}
יצירת פרופיל של גרסה נקייה
כדי להשוות בין ביצועים של גרסאות build נקיות, אפשר ליצור תרחיש שישמש להפעלת gradle-profiler:
# <root-project>/scenarios.txt
clean_build {
tasks = [":app:assembleDebug"]
cleanup-tasks = ["clean"]
}
כדי להריץ את התרחיש הזה, משתמשים בפקודה הבאה:
gradle-profiler --benchmark --project-dir <root-project> --scenario-file scenarios.txt
שימוש באפשרות --profile של Gradle
כדי ליצור פרופיל build ולהציג אותו משורת הפקודה של Gradle, מבצעים את השלבים הבאים:
פותחים טרמינל של שורת פקודה בתיקיית הבסיס של הפרויקט.
מזינים את הפקודה הבאה כדי לבצע בנייה נקייה. כשאתם יוצרים פרופיל של ה-build, כדאי לבצע build נקי בין כל build שאתם יוצרים לו פרופיל, כי Gradle מדלג על משימות כשהקלט של משימה (כמו קוד מקור) לא משתנה. לכן, גרסת build שנייה ללא שינויים בקלט תמיד פועלת מהר יותר כי המשימות לא מופעלות מחדש. לכן, הפעלת המשימה
cleanבין ה-builds מבטיחה שאתם יוצרים פרופיל של תהליך ה-build המלא.// On Mac or Linux, run the Gradle wrapper using "./gradlew". gradlew cleanמריצים גרסת build לניפוי באגים של אחת מגרסאות המוצר, למשל גרסת dev, עם הדגלים הבאים:
gradlew --profile --offline --rerun-tasks assemble<var>Flavor</var>Debug
--profile: הפעלת יצירת פרופילים.
--offline: משבית את האפשרות של Gradle לאחזר יחסי תלות אונליין. כך אפשר לוודא שעיכובים שנגרמים כתוצאה מניסיונות של Gradle לעדכן את יחסי התלות לא יפריעו לנתוני הפרופיילינג. כבר הייתם אמורים לבנות את הפרויקט פעם אחת כדי לוודא ש-Gradle כבר הוריד את יחסי התלות ושמר אותם במטמון.
--rerun-tasks: מאלץ את Gradle להריץ מחדש את כל המשימות ולהתעלם מכל אופטימיזציה של משימות.
אחרי שה-build מסתיים, משתמשים בחלון Project כדי לעבור לספרייה
project-root/build/reports/profile/(כמו שמוצג באיור 1).
איור 1. תצוגת פרויקט שמציינת את המיקום של דוחות הפרופילים.
לוחצים לחיצה ימנית על הקובץ
profile-timestamp.htmlובוחרים באפשרות Open in Browser > Default (פתיחה בדפדפן > ברירת מחדל). הדוח אמור להיראות כמו הדוח שמוצג באיור 2. אתם יכולים לבדוק כל כרטיסייה בדוח כדי לקבל מידע על ה-build, למשל הכרטיסייה Task Execution שמראה כמה זמן נדרש ל-Gradle כדי להריץ כל משימת build.
איור 2. צפייה בדוח בדפדפן.
אופציונלי: לפני שמשנים את הפרויקט או את תצורת ה-build, חוזרים על הפקודה שבשלב 3, אבל משמיטים את הדגל
--rerun-tasks. Gradle מנסה לחסוך זמן ולא מפעיל מחדש משימות שהקלט שלהן לא השתנה (המשימות האלה מסומנות כ-UP-TO-DATEבכרטיסייה Task Execution בדוח, כמו שמוצג באיור 3). לכן, אתם יכולים לזהות אילו משימות פועלות כשלא צריך. לדוגמה, אם:app:processDevUniversalDebugManifestלא מסומן כ-UP-TO-DATE, יכול להיות שתצורת ה-build מעדכנת באופן דינמי את המניפסט בכל build. עם זאת, יש משימות שצריך להריץ בכל build, כמו:app:checkDevDebugManifest.
איור 3. צפייה בתוצאות של הרצת המשימה.
אחרי שיצרתם דוח פרופיל build, אתם יכולים להתחיל לחפש הזדמנויות לאופטימיזציה על ידי בדיקת המידע בכל כרטיסייה בדוח. חלק מהגדרות ה-build דורשות ניסויים כי היתרונות עשויים להשתנות בין פרויקטים ותחנות עבודה. לדוגמה, בפרויקטים עם בסיס קוד גדול, כדאי להשתמש בכיווץ קוד כדי להסיר קוד שלא בשימוש ולהקטין את גודל האפליקציה.
עם זאת, בפרויקטים קטנים יותר, יכול להיות שעדיף להשבית לגמרי את כיווץ הקוד. בנוסף, הגדלת ה-heap size של Gradle (באמצעות org.gradle.jvmargs) עלולה לפגוע בביצועים במכונות עם זיכרון מועט.
אחרי שמשנים את תצורת ה-build, חוזרים על השלבים הקודמים ויוצרים פרופיל build חדש כדי לראות את התוצאות של השינויים. לדוגמה, באיור 4 מוצג דוח של אותה אפליקציה לדוגמה אחרי שהוחלו חלק מהאופטימיזציות הבסיסיות שמתוארות בדף הזה.
איור 4. צפייה בדוח חדש אחרי אופטימיזציה של מהירות ה-build.