הגדרת וריאנטים של build

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

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

גרסאות build הן תוצאה של שימוש ב-Gradle בקבוצה ספציפית של כללים לשילוב הגדרות, קוד ומשאבים שהוגדרו בסוגי ה-build ובטעמי המוצר. למרות שאתם לא מגדירים ישירות וריאציות של גרסאות build, אתם מגדירים את סוגי ה-build ואת טעמי המוצרים שמרכיבים אותן.

לדוגמה, גרסת המוצר מסוג 'הדגמה' עשויה לציין תכונות מסוימות ודרישות מכשיר, כמו קוד מקור מותאם אישית, משאבים ורמות API מינימליות, בעוד שסוג build מסוג 'ניפוי באגים' חל על הגדרות שונות של build ואריזה, כמו אפשרויות ניפוי באגים ומפתחות חתימה. וריאנט ה-build שמשלב את שניהם הוא הגרסה demoDebug של האפליקציה, והוא כולל שילוב של ההגדרות והמשאבים שנכללים בטעם המוצר demo, בסוג ה-build debug ובקבוצת המקור main/.

הגדרת סוגי build

אפשר ליצור ולהגדיר סוגי build בתוך הבלוק android בקובץ build.gradle.kts ברמת המודול. כשיוצרים מודול חדש, Android Studio יוצר באופן אוטומטי את סוגי ה-build של ניפוי הבאגים וגרסת build להפצה. למרות שסוג ה-build לניפוי באגים לא מופיע בקובץ הגדרות ה-build, ‏ Android Studio מגדיר אותו עם debuggable true. כך תוכלו לנפות באגים באפליקציה במכשירי Android מאובטחים, ולהגדיר חתימת אפליקציה באמצעות מאגר מפתחות כללי לניפוי באגים.

אפשר להוסיף את סוג ה-build של ניפוי הבאגים להגדרה אם רוצים להוסיף או לשנות הגדרות מסוימות. בדוגמה הבאה מצוין applicationIdSuffix לסוג ה-build של ניפוי הבאגים, ומוגדר סוג build של 'staging' שמופעל באמצעות הגדרות מסוג ה-build של ניפוי הבאגים:

Kotlin

android {
    defaultConfig {
        manifestPlaceholders["hostName"] = "www.example.com"
        ...
    }
    buildTypes {
        getByName("release") {
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
        }

        getByName("debug") {
            applicationIdSuffix = ".debug"
            isDebuggable = true
        }

        /**
         * The `initWith` property lets you copy configurations from other build types,
         * then configure only the settings you want to change. This one copies the debug build
         * type, and then changes the manifest placeholder and application ID.
         */
        create("staging") {
            initWith(getByName("debug"))
            manifestPlaceholders["hostName"] = "internal.example.com"
            applicationIdSuffix = ".debugStaging"
        }
    }
}

מגניב

android {
    defaultConfig {
        manifestPlaceholders = [hostName:"www.example.com"]
        ...
    }
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        debug {
            applicationIdSuffix ".debug"
            debuggable true
        }

        /**
         * The `initWith` property lets you copy configurations from other build types,
         * then configure only the settings you want to change. This one copies the debug build
         * type, and then changes the manifest placeholder and application ID.
         */
        staging {
            initWith debug
            manifestPlaceholders = [hostName:"internal.example.com"]
            applicationIdSuffix ".debugStaging"
        }
    }
}

הערה: כשמבצעים שינויים בקובץ תצורת build, צריך לסנכרן את הפרויקט עם התצורה החדשה ב-Android Studio. כדי לסנכרן את הפרויקט, לוחצים על סנכרון עכשיו בסרגל ההתראות שמופיע כשמבצעים שינוי, או לוחצים על סנכרון הפרויקט בסרגל הכלים. אם Android Studio מזהה שגיאות בהגדרה, חלון Messages מופיע עם תיאור של הבעיה.

מידע נוסף על כל הנכסים שאפשר להגדיר באמצעות סוגי build זמין במאמר BuildType.

הגדרת טעמים של מוצרים

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

הערה: עדיין צריך לציין שם חבילה באמצעות המאפיין package בקובץ המניפסט main/. צריך להשתמש בשם החבילה הזה גם בקוד המקור כדי להתייחס למחלקה R או כדי לפתור בעיות שקשורות לרישום של פעילות או שירות יחסיים. כך תוכלו להשתמש ב-applicationId כדי להקצות לכל גרסת מוצר מזהה ייחודי לאריזה ולהפצה, בלי שתצטרכו לשנות את קוד המקור.

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

  Error: All flavors must now belong to a named flavor dimension.
  The flavor 'flavor_name' is not assigned to a flavor dimension.

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

בדוגמת הקוד הבאה נוצרת קבוצת גרסאות בשם version, ונוספות לה גרסאות המוצר demo ו-full. לכל הטעמים האלה יש applicationIdSuffix משלהם וגם versionNameSuffix:

Kotlin

android {
    ...
    defaultConfig {...}
    buildTypes {
        getByName("debug"){...}
        getByName("release"){...}
    }
    // Specifies one flavor dimension.
    flavorDimensions += "version"
    productFlavors {
        create("demo") {
            // Assigns this product flavor to the "version" flavor dimension.
            // If you are using only one dimension, this property is optional,
            // and the plugin automatically assigns all the module's flavors to
            // that dimension.
            dimension = "version"
            applicationIdSuffix = ".demo"
            versionNameSuffix = "-demo"
        }
        create("full") {
            dimension = "version"
            applicationIdSuffix = ".full"
            versionNameSuffix = "-full"
        }
    }
}

מגניב

android {
    ...
    defaultConfig {...}
    buildTypes {
        debug{...}
        release{...}
    }
    // Specifies one flavor dimension.
    flavorDimensions "version"
    productFlavors {
        demo {
            // Assigns this product flavor to the "version" flavor dimension.
            // If you are using only one dimension, this property is optional,
            // and the plugin automatically assigns all the module's flavors to
            // that dimension.
            dimension "version"
            applicationIdSuffix ".demo"
            versionNameSuffix "-demo"
        }
        full {
            dimension "version"
            applicationIdSuffix ".full"
            versionNameSuffix "-full"
        }
    }
}

הערה: אם יש לכם אפליקציה מדור קודם (שנוצרה לפני אוגוסט 2021) שאתם מפיצים באמצעות קובצי APK ב-Google Play, כדי להפיץ את האפליקציה באמצעות תמיכה בכמה קובצי APK ב-Google Play, צריך להקצות את אותו ערך applicationId לכל הווריאציות ולתת לכל וריאציה versionCode שונה. כדי להפיץ וריאציות שונות של האפליקציה כאפליקציות נפרדות ב-Google Play, צריך להקצות applicationId שונה לכל וריאציה.

אחרי שיוצרים ומגדירים את טעמי המוצרים, לוחצים על סנכרון עכשיו בסרגל ההתראות. אחרי שהסנכרון מסתיים, Gradle יוצר באופן אוטומטי וריאציות של build על סמך סוגי ה-build והטעמים של המוצרים, ונותן להם שמות בהתאם ל-<product-flavor><Build-Type>. לדוגמה, אם יצרתם טעמי מוצר בשם demo ו-full, והשארתם את סוגי ה-build שמוגדרים כברירת מחדל debug ו-release, ‏ Gradle יוצר את וריאציות ה-build הבאות:

  • demoDebug
  • demoRelease
  • fullDebug
  • fullRelease

כדי לבחור איזה וריאנט build ליצור ולהריץ, עוברים אל Build > Select Build Variant ובוחרים וריאנט build מהתפריט. כדי להתחיל להתאים אישית כל וריאציית build עם התכונות והמשאבים שלה, צריך ליצור ולנהל קבוצות של מקורות, כמו שמתואר בדף הזה.

שינוי מזהה האפליקציה עבור וריאציות של גרסאות Build

כשיוצרים APK או AAB לאפליקציה, כלי הבנייה מתייגים את האפליקציה עם מזהה האפליקציה שמוגדר בבלוק defaultConfig מהקובץ build.gradle.kts, כמו בדוגמה הבאה. עם זאת, אם רוצים ליצור גרסאות שונות של האפליקציה שיופיעו כרשימות נפרדות בחנות Google Play, כמו גרסה 'חינמית' וגרסת 'פרו', צריך ליצור וריאציות נפרדות של Build, שלכל אחת מהן יש מזהה אפליקציה שונה.

במקרה כזה, צריך להגדיר כל וריאנט build כגרסת מוצר נפרדת. לכל טעם (flavor) בתוך הבלוק productFlavors, אפשר להגדיר מחדש את המאפיין applicationId, או להוסיף פלח למזהה האפליקציה שמוגדר כברירת מחדל באמצעות applicationIdSuffix, כמו שמוצג כאן:

Kotlin

android {
    defaultConfig {
        applicationId = "com.example.myapp"
    }
    productFlavors {
        create("free") {
            applicationIdSuffix = ".free"
        }
        create("pro") {
            applicationIdSuffix = ".pro"
        }
    }
}

מגניב

android {
    defaultConfig {
        applicationId "com.example.myapp"
    }
    productFlavors {
        free {
            applicationIdSuffix ".free"
        }
        pro {
            applicationIdSuffix ".pro"
        }
    }
}

כך, מזהה האפליקציה של גרסת המוצר 'free' הוא com.example.myapp.free.

אפשר גם להשתמש ב-applicationIdSuffix כדי להוסיף פלח על סמך סוג ה-build, כמו שמוצג כאן:

Kotlin

android {
    ...
    buildTypes {
        getByName("debug") {
            applicationIdSuffix = ".debug"
        }
    }
}

מגניב

android {
    ...
    buildTypes {
        debug {
            applicationIdSuffix ".debug"
        }
    }
}

מכיוון ש-Gradle מחיל את ההגדרה של סוג ה-build אחרי גרסת המוצר, מזהה האפליקציה של וריאנט ה-build 'free debug' הוא 'com.example.myapp.free.debug'. האפשרות הזו שימושית אם רוצים שגם גרסת הניפוי באגים וגם גרסת build להפצה יהיו באותו מכשיר, כי לשתי אפליקציות לא יכול להיות אותו מזהה האפליקציה.

אם יש לכם אפליקציה מדור קודם (שנוצרה לפני אוגוסט 2021) שאתם מפיצים באמצעות קובצי APK ב-Google Play, ואתם רוצים להשתמש באותו דף אפליקציה כדי להפיץ כמה קובצי APK שכל אחד מהם מיועד להגדרת מכשיר שונה, כמו רמת ה-API, אתם צריכים להשתמש באותו מזהה אפליקציה לכל וריאציה של build, אבל לתת לכל קובץ APK מזהה versionCode שונה. מידע נוסף זמין במאמר בנושא תמיכה בכמה קובצי APK. הפרסום באמצעות קובצי AAB לא מושפע, כי הוא משתמש בארטיפקט יחיד שמשתמש בקוד גרסה יחיד ובמזהה אפליקציה כברירת מחדל.

טיפ: אם אתם צריכים להפנות למזהה האפליקציה בקובץ המניפסט, אתם יכולים להשתמש ב-placeholder ‏${applicationId} בכל מאפיין של המניפסט. במהלך בנייה, Gradle מחליף את התג הזה במזהה האפליקציה בפועל. מידע נוסף זמין במאמר בנושא הוספת משתני build למניפסט.

שילוב של כמה טעמים של מוצרים עם מאפייני טעם

במקרים מסוימים, יכול להיות שתרצו לשלב הגדרות מכמה גרסאות של מוצר. לדוגמה, יכול להיות שתרצו ליצור הגדרות שונות לגרסאות המוצר 'מלאה' ו'הדגמה' שמבוססות על רמת ה-API. כדי לעשות את זה, פלאגין של Android Gradle מאפשר ליצור כמה קבוצות של גרסאות מוצר כקבוצות גרסאות.

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

בדוגמת הקוד הבאה נעשה שימוש במאפיין flavorDimensions כדי ליצור מאפיין של טעם 'mode' כדי לקבץ את הטעמים של המוצר 'full' ו-'demo', ומאפיין של טעם 'api' כדי לקבץ את ההגדרות של טעמי המוצר על סמך רמת ה-API:

Kotlin

android {
  ...
  buildTypes {
    getByName("debug") {...}
    getByName("release") {...}
  }

  // Specifies the flavor dimensions you want to use. The order in which you
  // list the dimensions determines their priority, from highest to lowest,
  // when Gradle merges variant sources and configurations. You must assign
  // each product flavor you configure to one of the flavor dimensions.
  flavorDimensions += listOf("api", "mode")

  productFlavors {
    create("demo") {
      // Assigns this product flavor to the "mode" flavor dimension.
      dimension = "mode"
      ...
    }

    create("full") {
      dimension = "mode"
      ...
    }

    // Configurations in the "api" product flavors override those in "mode"
    // flavors and the defaultConfig block. Gradle determines the priority
    // between flavor dimensions based on the order in which they appear next
    // to the flavorDimensions property, with the first dimension having a higher
    // priority than the second, and so on.
    create("minApi24") {
      dimension = "api"
      minSdk = 24
      // To ensure the target device receives the version of the app with
      // the highest compatible API level, assign version codes in increasing
      // value with API level.
      versionCode = 30000 + (android.defaultConfig.versionCode ?: 0)
      versionNameSuffix = "-minApi24"
      ...
    }

    create("minApi23") {
      dimension = "api"
      minSdk = 23
      versionCode = 20000  + (android.defaultConfig.versionCode ?: 0)
      versionNameSuffix = "-minApi23"
      ...
    }

    create("minApi21") {
      dimension = "api"
      minSdk = 21
      versionCode = 10000  + (android.defaultConfig.versionCode ?: 0)
      versionNameSuffix = "-minApi21"
      ...
    }
  }
}
...

מגניב

android {
  ...
  buildTypes {
    debug {...}
    release {...}
  }

  // Specifies the flavor dimensions you want to use. The order in which you
  // list the dimensions determines their priority, from highest to lowest,
  // when Gradle merges variant sources and configurations. You must assign
  // each product flavor you configure to one of the flavor dimensions.
  flavorDimensions "api", "mode"

  productFlavors {
    demo {
      // Assigns this product flavor to the "mode" flavor dimension.
      dimension "mode"
      ...
    }

    full {
      dimension "mode"
      ...
    }

    // Configurations in the "api" product flavors override those in "mode"
    // flavors and the defaultConfig block. Gradle determines the priority
    // between flavor dimensions based on the order in which they appear next
    // to the flavorDimensions property, with the first dimension having a higher
    // priority than the second, and so on.
    minApi24 {
      dimension "api"
      minSdkVersion 24
      // To ensure the target device receives the version of the app with
      // the highest compatible API level, assign version codes in increasing
      // value with API level.

      versionCode 30000 + android.defaultConfig.versionCode
      versionNameSuffix "-minApi24"
      ...
    }

    minApi23 {
      dimension "api"
      minSdkVersion 23
      versionCode 20000  + android.defaultConfig.versionCode
      versionNameSuffix "-minApi23"
      ...
    }

    minApi21 {
      dimension "api"
      minSdkVersion 21
      versionCode 10000  + android.defaultConfig.versionCode
      versionNameSuffix "-minApi21"
      ...
    }
  }
}
...

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

בדוגמה הקודמת של הגדרת build,‏ Gradle יוצר 12 וריאציות build בסך הכול, עם סכמת השמות הבאה:

  • וריאנט build: [minApi24, minApi23, minApi21][Demo, Full][Debug, Release]
  • קובץ ה-APK התואם: app-[minApi24, minApi23, minApi21]-[demo, full]-[debug, release].apk
  • לדוגמה,
    Build variant: minApi24DemoDebug
    Corresponding APK: app-minApi24-demo-debug.apk

בנוסף לספריות של קבוצות מקורות שאפשר ליצור לכל גרסת מוצר ווריאנט build, אפשר גם ליצור ספריות של קבוצות מקורות לכל שילוב של גרסאות מוצר. לדוגמה, אפשר ליצור מקורות Java ולהוסיף אותם לספרייה src/demoMinApi24/java/, ו-Gradle ישתמש במקורות האלה רק כשיוצר גרסה שמשלבת בין שני טעמי המוצר האלה.

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

סינון הווריאציות

‫Gradle יוצר וריאנט build לכל שילוב אפשרי של גרסאות מוצר וסוגי build שהגדרתם. עם זאת, יכול להיות שיש וריאציות מסוימות של build שלא נחוצות לכם או שלא מתאימות להקשר של הפרויקט שלכם. כדי להסיר הגדרות מסוימות של וריאציות build, יוצרים מסנן וריאציות בקובץ build.gradle.kts ברמת המודול.

בדוגמה הבאה נעשה שימוש בהגדרת ה-build מהקטע הקודם. נניח שאתם מתכננים לתמוך רק ברמות API 23 ומעלה בגרסת ההדגמה של האפליקציה. אתם יכולים להשתמש בבלוק variantFilter כדי לסנן את כל ההגדרות של וריאציות ה-build שמשלבות בין minApi21 לבין demo:

Kotlin

android {
  ...
  buildTypes {...}

  flavorDimensions += listOf("api", "mode")
  productFlavors {
    create("demo") {...}
    create("full") {...}
    create("minApi24") {...}
    create("minApi23") {...}
    create("minApi21") {...}
  }
}

androidComponents {
    beforeVariants { variantBuilder ->
        // To check for a certain build type, use variantBuilder.buildType == "<buildType>"
        if (variantBuilder.productFlavors.containsAll(listOf("api" to "minApi21", "mode" to "demo"))) {
            // Gradle ignores any variants that satisfy the conditions above.
            variantBuilder.enable = false
        }
    }
}
...

מגניב

android {
  ...
  buildTypes {...}

  flavorDimensions "api", "mode"
  productFlavors {
    demo {...}
    full {...}
    minApi24 {...}
    minApi23 {...}
    minApi21 {...}
  }

  variantFilter { variant ->
      def names = variant.flavors*.name
      // To check for a certain build type, use variant.buildType.name == "<buildType>"
      if (names.contains("minApi21") && names.contains("demo")) {
          // Gradle ignores any variants that satisfy the conditions above.
          setIgnore(true)
      }
  }
}
...

אחרי שמוסיפים מסנן וריאציות להגדרת ה-build ולוחצים על Sync Now בסרגל ההתראות, מערכת Gradle מתעלמת מכל וריאציות ה-build שעומדות בתנאים שציינתם. הווריאציות של הגרסה לא מופיעות יותר בתפריט כשלוחצים על Build > Select Build Variant בסרגל התפריטים או על Build Variants בסרגל של חלון הכלים.

יצירת קבוצות מקורות

כברירת מחדל, Android Studio יוצר main/ קבוצת מקורות וספריות לכל מה שרוצים לשתף בין כל וריאנטים ה-build. עם זאת, אתם יכולים ליצור קבוצות מקור חדשות כדי לשלוט בדיוק באילו קבצים Gradle מהדרת ואורזת עבור סוגי build ספציפיים, טעמי מוצר, שילובים של טעמי מוצר (כשמשתמשים במאפייני טעם) ווריאציות של build.

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

‫Gradle מצפה שקבצים וספריות של קבוצת מקורות יהיו מאורגנים בצורה מסוימת, בדומה לmain/קבוצת המקורות. לדוגמה, Gradle מצפה שקובצי מחלקה של Kotlin או Java שספציפיים לסוג ה-build 'debug' יהיו בתיקיות src/debug/kotlin/ או src/debug/java/.

פלאגין של Android Gradle מספק משימת Gradle שימושית שמראה לכם איך לארגן את הקבצים לכל אחד מסוגי ה-build, גרסאות המוצר ווריאציות ה-build. לדוגמה, בדוגמה הבאה מתוך פלט המשימה מתואר איפה Gradle מצפה למצוא קבצים מסוימים עבור סוג הבנייה debug:

------------------------------------------------------------
Project :app
------------------------------------------------------------

...

debug
----
Compile configuration: debugCompile
build.gradle name: android.sourceSets.debug
Java sources: [app/src/debug/java]
Kotlin sources: [app/src/debug/kotlin, app/src/debug/java]
Manifest file: app/src/debug/AndroidManifest.xml
Android resources: [app/src/debug/res]
Assets: [app/src/debug/assets]
AIDL sources: [app/src/debug/aidl]
RenderScript sources: [app/src/debug/rs]
JNI sources: [app/src/debug/jni]
JNI libraries: [app/src/debug/jniLibs]
Java-style resources: [app/src/debug/resources]

כדי לראות את הפלט הזה, מבצעים את הפעולות הבאות:

  1. לוחצים על Gradle בסרגל חלון הכלים.
  2. עוברים אל MyApplication > Tasks > android ולוחצים לחיצה כפולה על sourceSets.

    כדי לראות את התיקייה Tasks, צריך לאפשר ל-Gradle ליצור את רשימת המשימות במהלך הסנכרון. לשם כך, בצע את הצעדים הבאים:

    1. לוחצים על File > Settings > Experimental (Android Studio > Settings > Experimental ב-macOS).
    2. מבטלים את הסימון של האפשרות Do not build Gradle task list during Gradle sync.
  3. אחרי ש-Gradle מריץ את המשימה, נפתח החלון Run כדי להציג את הפלט.

הערה: בפלט של המשימה מוצג גם איך לארגן קבוצות של קבצי מקור שרוצים להשתמש בהם כדי להריץ בדיקות לאפליקציה, כמו test/ ו-androidTest/ קבוצות של קבצי מקור לבדיקה.

כשיוצרים וריאנט build חדש, ‏ Android Studio לא יוצר את ספריות קבוצת המקורות בשבילכם, אבל הוא מספק כמה אפשרויות שיעזרו לכם. לדוגמה, כדי ליצור רק את הספרייה java/ לסוג ה-build 'debug':

  1. פותחים את החלונית Project ובוחרים באפשרות Project בתפריט שבראש החלונית.
  2. עוברים אל MyProject/app/src/.
  3. לוחצים לחיצה ימנית על ספריית src ובוחרים באפשרות חדש > ספרייה.
  4. בתפריט שמתחת לGradle Source Sets, בוחרים באפשרות full/java.
  5. מקישים על Enter.

‫Android Studio יוצר ספרייה של קבוצת מקורות לסוג ה-build של ניפוי הבאגים, ואז יוצר את הספרייה java/ בתוכה. לחלופין, Android Studio יכול ליצור את הספריות בשבילכם כשאתם מוסיפים קובץ חדש לפרויקט עבור וריאציית build ספציפית.

לדוגמה, כדי ליצור קובץ XML של ערכים לסוג ה-build 'debug':

  1. בחלונית Project, לוחצים לחיצה ימנית על הספרייה src ובוחרים באפשרות New (חדש) > XML > Values XML File (קובץ XML של ערכים).
  2. מזינים שם לקובץ ה-XML או משאירים את שם ברירת המחדל.
  3. בתפריט שליד Target Source Set (קבוצת מקורות יעד), בוחרים באפשרות debug (ניפוי באגים).
  4. לוחצים על סיום.

מכיוון שסוג ה-build 'debug' צוין כקבוצת המקורות של היעד, Android Studio יוצר באופן אוטומטי את הספריות הנדרשות כשהוא יוצר את קובץ ה-XML. מבנה הספריות שמתקבל נראה כמו באיור 1.

איור 1. ספריות חדשות של קבוצות מקורות לסוג הבנייה debug.

בסמל של קבוצות מקורות פעילות מופיע סימון ירוק שמציין שהן פעילות. ‫debug קבוצת המקורות מסומנת בסיומת [main] כדי לציין שהיא תמוזג עם קבוצת המקורות main.

באותו אופן, אפשר ליצור ספריות של קבוצות מקורות לגרסאות המוצר, כמו src/demo/, ולוריאנטים build, כמו src/demoDebug/. בנוסף, אפשר ליצור קבוצות של מקורות לבדיקה שמטרגטות וריאנטים ספציפיים של גרסאות build, כמו src/androidTestDemoDebug/. מידע נוסף זמין במאמר בנושא בדיקת קבוצות של מקורות.

שינוי ההגדרות של קבוצת מקורות ברירת המחדל

אם יש לכם מקורות שלא מאורגנים במבנה הקובץ של קבוצת המקורות שמוגדר כברירת מחדל, ש-Gradle מצפה לו, כמו שמתואר בקטע הקודם בנושא יצירת קבוצות מקורות, אתם יכולים להשתמש בבלוק sourceSets כדי לשנות את המקום שבו Gradle מחפש קבצים לכל רכיב של קבוצת מקורות.

הבלוק sourceSets חייב להיות בתוך הבלוק android. אין צורך להעביר את קובצי המקור. צריך רק לספק ל-Gradle את הנתיבים, ביחס לקובץ build.gradle.kts ברמת המודול, שבהם Gradle יכול למצוא קבצים לכל רכיב של קבוצת מקור. כדי לדעת אילו רכיבים אפשר להגדיר ואם אפשר למפות אותם לכמה קבצים או ספריות, אפשר לעיין במאמרי העזרה של ה-API של פלאגין של Android Gradle.

בדוגמת הקוד הבאה ממופים מקורות מהספרייה app/other/ לרכיבים מסוימים של קבוצת המקורות main, ומשתנה ספריית הבסיס של קבוצת המקורות androidTest:

Kotlin

android {
  ...
  // Encapsulates configurations for the main source set.
  sourceSets.getByName("main") {
    // Changes the directory for Java sources. The default directory is
    // 'src/main/java'.
    java.setSrcDirs(listOf("other/java"))

    // If you list multiple directories, Gradle uses all of them to collect
    // sources. Because Gradle gives these directories equal priority, if
    // you define the same resource in more than one directory, you receive an
    // error when merging resources. The default directory is 'src/main/res'.
    res.setSrcDirs(listOf("other/res1", "other/res2"))

    // Note: Avoid specifying a directory that is a parent to one
    // or more other directories you specify. For example, avoid the following:
    // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
    // Specify either only the root 'other/res1' directory or only the
    // nested 'other/res1/layouts' and 'other/res1/strings' directories.

    // For each source set, you can specify only one Android manifest.
    // By default, Android Studio creates a manifest for your main source
    // set in the src/main/ directory.
    manifest.srcFile("other/AndroidManifest.xml")
    ...
  }

  // Create additional blocks to configure other source sets.
  sourceSets.getByName("androidTest") {
      // If all the files for a source set are located under a single root
      // directory, you can specify that directory using the setRoot property.
      // When gathering sources for the source set, Gradle looks only in locations
      // relative to the root directory you specify. For example, after applying the
      // configuration below for the androidTest source set, Gradle looks for Java
      // sources only in the src/tests/java/ directory.
      setRoot("src/tests")
      ...
  }
}
...

מגניב

android {
  ...
  sourceSets {
    // Encapsulates configurations for the main source set.
    main {
      // Changes the directory for Java sources. The default directory is
      // 'src/main/java'.
      java.srcDirs = ['other/java']

      // If you list multiple directories, Gradle uses all of them to collect
      // sources. Because Gradle gives these directories equal priority, if
      // you define the same resource in more than one directory, you receive an
      // error when merging resources. The default directory is 'src/main/res'.
      res.srcDirs = ['other/res1', 'other/res2']

      // Note: Avoid specifying a directory that is a parent to one
      // or more other directories you specify. For example, avoid the following:
      // res.srcDirs = ['other/res1', 'other/res1/layouts', 'other/res1/strings']
      // Specify either only the root 'other/res1' directory or only the
      // nested 'other/res1/layouts' and 'other/res1/strings' directories.

      // For each source set, you can specify only one Android manifest.
      // By default, Android Studio creates a manifest for your main source
      // set in the src/main/ directory.
      manifest.srcFile 'other/AndroidManifest.xml'
      ...
    }

    // Create additional blocks to configure other source sets.
    androidTest {

      // If all the files for a source set are located under a single root
      // directory, you can specify that directory using the setRoot property.
      // When gathering sources for the source set, Gradle looks only in locations
      // relative to the root directory you specify. For example, after applying the
      // configuration below for the androidTest source set, Gradle looks for Java
      // sources only in the src/tests/java/ directory.
      setRoot 'src/tests'
      ...
    }
  }
}
...

חשוב לזכור שספריית קובצי המקור יכולה להשתייך רק לקבוצת מקורות אחת. לדוגמה, אי אפשר לשתף את אותם מקורות בדיקה עם קבוצות המקורות test ו-androidTest. הסיבה לכך היא ש-Android Studio יוצר מודולים נפרדים של IntelliJ לכל קבוצת מקורות, ולא יכול לתמוך בשורשי תוכן כפולים בקבוצות מקורות.

איך יוצרים קבוצות מקורות

אפשר להשתמש בספריות של ערכות מקור כדי לאחסן את הקוד והמשאבים שרוצים לארוז רק עם הגדרות מסוימות. לדוגמה, אם אתם יוצרים את וריאנט build‏ demoDebug, שהיא תוצר הצלבה של גרסת המוצר demo וסוג build‏ debug, מערכת Gradle בודקת את הספריות האלה ומקצה להן את העדיפות הבאה:

  1. src/demoDebug/ (וריאנט build קבוצת מקורות)
  2. src/debug/ (סוג build קבוצת מקורות)
  3. src/demo/ (גרסת המוצר קבוצת מקורות)
  4. src/main/ (קבוצת מקורות ראשית)

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

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

סדר העדיפות קובע לאיזו קבוצת מקורות יש עדיפות גבוהה יותר כש-Gradle משלב קוד ומשאבים. הספרייה של קבוצת המקורות demoDebug/ כנראה מכילה קבצים שספציפיים לווריאנט הבנייה הזה. לכן, אם demoDebug/ כולל קובץ שמוגדר גם ב-debug/, ‏ Gradle משתמש בקובץ שבקבוצת המקורות demoDebug/. באופן דומה, Gradle נותן לקבצים ב-build type ול-product flavor עדיפות גבוהה יותר מאשר לאותם קבצים ב-main/. מערכת Gradle מתחשבת בסדר העדיפויות הזה כשהיא מחילה את כללי הבנייה הבאים:

  • כל קוד המקור בספריות kotlin/ או java/ עובר קומפילציה ביחד כדי ליצור פלט יחיד.

    הערה: עבור וריאנט build נתון, ‏ Gradle מחזיר שגיאת build אם הוא נתקל בשתי ספריות או יותר של קבוצות מקור שהוגדרה בהן אותה מחלקה של Kotlin או Java. לדוגמה, כשיוצרים אפליקציה לניפוי באגים, אי אפשר להגדיר גם את src/debug/Utility.kt וגם את src/main/Utility.kt, כי Gradle בודק את שתי הספריות האלה במהלך תהליך build ומציג שגיאה של 'כיתה כפולה'. אם רוצים גרסאות שונות של Utility.kt לסוגים שונים של גרסאות build, צריך להגדיר לכל סוג גרסת build גרסה משלו של הקובץ, ולא לכלול אותו בערכת המקור main/.

  • המניפסטים עוברים מיזוג למניפסט יחיד. העדיפות ניתנת לפי הסדר שמופיע ברשימה בדוגמה הקודמת. לדוגמה, הגדרות המניפסט של סוג build עוקפות את הגדרות המניפסט של טעם מוצר. מידע נוסף זמין במאמר בנושא מיזוג מניפסטים.
  • הקבצים בספריות values/ מוזגו יחד. אם לשני קבצים יש את אותו שם, למשל שני קבצים מסוג strings.xml, העדיפות תינתן לפי הסדר שמופיע ברשימה בדוגמה הקודמת. לדוגמה, ערכים שמוגדרים בקובץ בערכת המקור של סוג ה-build מבטלים את הערכים שמוגדרים באותו קובץ בטעם מוצר.
  • המשאבים בספריות res/ ו-asset/ נארזים יחד. אם יש משאבים עם אותו שם שמוגדרים בשתי קבוצות מקור או יותר, העדיפות ניתנת באותו סדר כמו ברשימה בדוגמה הקודמת.
  • ‫Gradle נותן למשאבים ולמניפסטים שנכללים בהתאם למודול הספרייה את העדיפות הנמוכה ביותר כשמבצעים build לאפליקציה.

הצהרה על יחסי תלות

כדי להגדיר תלות בווריאנט build ספציפי או בקבוצת מקורות לבדיקה, מוסיפים את השם של הווריאנט build או של קבוצת המקורות לבדיקה לפני מילת המפתח Implementation, כמו בדוגמה הבאה:

Kotlin

dependencies {
    // Adds the local "mylibrary" module as a dependency to the "free" flavor.
    "freeImplementation"(project(":mylibrary"))

    // Adds a remote binary dependency only for local tests.
    testImplementation("junit:junit:4.12")

    // Adds a remote binary dependency only for the instrumented test APK.
    androidTestImplementation("com.android.support.test.espresso:espresso-core:3.6.1")
}

מגניב

dependencies {
    // Adds the local "mylibrary" module as a dependency to the "free" flavor.
    freeImplementation project(":mylibrary")

    // Adds a remote binary dependency only for local tests.
    testImplementation 'junit:junit:4.12'

    // Adds a remote binary dependency only for the instrumented test APK.
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.6.1'
}

מידע נוסף על הגדרת יחסי תלות זמין במאמר הוספת יחסי תלות ב-build.

שימוש בניהול תלויות עם מודעות לגרסאות

הפלאגין של Android Gradle מגרסה 3.0.0 ואילך כולל מנגנון חדש של יחסי תלות, שמתאים באופן אוטומטי בין וריאציות כשמשתמשים בספרייה. לדוגמה, debug וריאנט של אפליקציה צורך אוטומטית debug וריאנט של ספרייה. היא פועלת גם כשמשתמשים בטעמים: וריאציה של אפליקציה freeDebug תשתמש בווריאציה של ספרייה freeDebug.

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

לדוגמה, נניח שהאפליקציה שלכם מגדירה סוג build בשם staging, אבל אחת מהתלויות בספרייה שלה לא מגדירה סוג כזה. כשהתוסף ינסה ליצור את גרסת ה-staging של האפליקציה, הוא לא יידע באיזו גרסה של הספרייה להשתמש, ותופיע הודעת שגיאה דומה לזו שבהמשך:

Error:Failed to resolve: Could not resolve project :mylibrary.
Required by:
    project :app

פתרון שגיאות build שקשורות להתאמת וריאנטים

התוסף כולל רכיבי DSL שעוזרים לכם לשלוט באופן שבו Gradle פותר מצבים שבהם לא ניתן להתאים ישירות בין אפליקציה לבין תלות.

בהמשך מופיעה רשימה של בעיות שקשורות להתאמת תלות שמודעת לגרסאות, ומוסבר איך לפתור אותן באמצעות מאפייני DSL:

  • האפליקציה שלך כוללת סוג build שאין לו תלות בספרייה.

    לדוגמה, האפליקציה כוללת סוג build של 'staging', אבל תלות כוללת רק סוגי build של 'debug' ו-'release'.

    שימו לב: אין בעיה אם תלות בספרייה כוללת סוג build שהאפליקציה לא כוללת. הסיבה לכך היא שהתוסף אף פעם לא מבקש את סוג build הזה מהתלות.

    משתמשים ב-matchingFallbacks כדי לציין התאמות חלופיות לסוג build נתון, כמו בדוגמה הבאה:

    Kotlin

    // In the app's build.gradle.kts file.
    android {
        buildTypes {
            getByName("debug") {}
            getByName("release") {}
            create("staging") {
                // Specifies a sorted list of fallback build types that the
                // plugin can try to use when a dependency does not include a
                // "staging" build type. You may specify as many fallbacks as you
                // like, and the plugin selects the first build type that's
                // available in the dependency.
                matchingFallbacks += listOf("debug", "qa", "release")
            }
        }
    }

    מגניב

    // In the app's build.gradle file.
    android {
        buildTypes {
            debug {}
            release {}
            staging {
                // Specifies a sorted list of fallback build types that the
                // plugin can try to use when a dependency does not include a
                // "staging" build type. You may specify as many fallbacks as you
                // like, and the plugin selects the first build type that's
                // available in the dependency.
                matchingFallbacks = ['debug', 'qa', 'release']
            }
        }
    }
  • במקרה של קבוצת גרסאות נתונה שקיימת גם באפליקציה וגם בתלות בספרייה שלה, האפליקציה כוללת גרסאות שלא קיימות בספרייה.

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

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

    משתמשים ב-matchingFallbacks כדי לציין התאמות חלופיות לגרסת המוצר 'החינמית' של האפליקציה, כמו שמוצג כאן:

    Kotlin

    // In the app's build.gradle.kts file.
    android {
        defaultConfig{
        // Don't configure matchingFallbacks in the defaultConfig block.
        // Instead, specify fallbacks for a given product flavor in the
        // productFlavors block, as shown below.
      }
        flavorDimensions += "tier"
        productFlavors {
            create("paid") {
                dimension = "tier"
                // Because the dependency already includes a "paid" flavor in its
                // "tier" dimension, you don't need to provide a list of fallbacks
                // for the "paid" flavor.
            }
            create("free") {
                dimension = "tier"
                // Specifies a sorted list of fallback flavors that the plugin
                // can try to use when a dependency's matching dimension does
                // not include a "free" flavor. Specify as many
                // fallbacks as you like; the plugin selects the first flavor
                // that's available in the dependency's "tier" dimension.
                matchingFallbacks += listOf("demo", "trial")
            }
        }
    }

    מגניב

    // In the app's build.gradle file.
    android {
        defaultConfig{
        // Don't configure matchingFallbacks in the defaultConfig block.
        // Instead, specify fallbacks for a given product flavor in the
        // productFlavors block, as shown below.
      }
        flavorDimensions 'tier'
        productFlavors {
            paid {
                dimension 'tier'
                // Because the dependency already includes a "paid" flavor in its
                // "tier" dimension, you don't need to provide a list of fallbacks
                // for the "paid" flavor.
            }
            free {
                dimension 'tier'
                // Specifies a sorted list of fallback flavors that the plugin
                // can try to use when a dependency's matching dimension does
                // not include a "free" flavor. Specify as many
                // fallbacks as you like; the plugin selects the first flavor
                // that's available in the dependency's "tier" dimension.
                matchingFallbacks = ['demo', 'trial']
            }
        }
    }
  • תלות בספרייה כוללת קבוצת גרסאות שהאפליקציה לא כוללת.

    לדוגמה, יחסי תלות של ספריות כוללים טעמים למאפיין minApi, אבל האפליקציה שלכם כוללת טעמים רק למאפיין tier. כשרוצים ליצור את הגרסה freeDebug של האפליקציה, הפלאגין לא יודע אם להשתמש בגרסה minApi23Debug או בגרסה minApi18Debug של התלות.

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

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

    Kotlin

    // In the app's build.gradle.kts file.
    android {
        defaultConfig{
        // Specifies a sorted list of flavors that the plugin can try to use from
        // a given dimension. This tells the plugin to select the "minApi18" flavor
        // when encountering a dependency that includes a "minApi" dimension.
        // You can include additional flavor names to provide a
        // sorted list of fallbacks for the dimension.
        missingDimensionStrategy("minApi", "minApi18", "minApi23")
        // Specify a missingDimensionStrategy property for each
        // dimension that exists in a local dependency but not in your app.
        missingDimensionStrategy("abi", "x86", "arm64")
        }
        flavorDimensions += "tier"
        productFlavors {
            create("free") {
                dimension = "tier"
                // You can override the default selection at the product flavor
                // level by configuring another missingDimensionStrategy property
                // for the "minApi" dimension.
                missingDimensionStrategy("minApi", "minApi23", "minApi18")
            }
            create("paid") {}
        }
    }

    מגניב

    // In the app's build.gradle file.
    android {
        defaultConfig{
        // Specifies a sorted list of flavors that the plugin can try to use from
        // a given dimension. This tells the plugin to select the "minApi18" flavor
        // when encountering a dependency that includes a "minApi" dimension.
        // You can include additional flavor names to provide a
        // sorted list of fallbacks for the dimension.
        missingDimensionStrategy 'minApi', 'minApi18', 'minApi23'
        // Specify a missingDimensionStrategy property for each
        // dimension that exists in a local dependency but not in your app.
        missingDimensionStrategy 'abi', 'x86', 'arm64'
        }
        flavorDimensions 'tier'
        productFlavors {
            free {
                dimension 'tier'
                // You can override the default selection at the product flavor
                // level by configuring another missingDimensionStrategy property
                // for the 'minApi' dimension.
                missingDimensionStrategy 'minApi', 'minApi23', 'minApi18'
            }
            paid {}
        }
    }

מידע נוסף זמין במאמרים matchingFallbacks וmissingDimensionStrategy בהפניה ל-DSL של פלאגין של Android Gradle.

הגדרת הגדרות החתימה

‫Gradle לא חותם על קובץ ה-APK או ה-AAB של גרסת ה-release, אלא אם מגדירים במפורש הגדרת חתימה לגרסה הזו. אם עדיין אין לכם מפתח חתימה, צריך ליצור מפתח העלאה ומאגר מפתחות באמצעות Android Studio.

כדי להגדיר ידנית את הגדרות החתימה לסוג ה-build של גרסת ה-build להפצה באמצעות תצורות ה-build של Gradle:

  1. יוצרים מאגר מפתחות. מאגר מפתחות הוא קובץ בינארי שמכיל קבוצה של מפתחות פרטיים. חשוב לשמור את מאגר המפתחות במקום בטוח ומאובטח.
  2. יוצרים מפתח פרטי. מפתח פרטי משמש לחתימה על האפליקציה לצורך הפצה, והוא אף פעם לא נכלל באפליקציה או נחשף לצדדים שלישיים לא מורשים.
  3. מוסיפים את הגדרות החתימה לקובץ build.gradle.kts ברמת המודול:

    Kotlin

    ...
    android {
        ...
        defaultConfig {...}
        signingConfigs {
            create("release") {
                storeFile = file("myreleasekey.keystore")
                storePassword = "password"
                keyAlias = "MyReleaseKey"
                keyPassword = "password"
            }
        }
        buildTypes {
            getByName("release") {
                ...
                signingConfig = signingConfigs.getByName("release")
            }
        }
    }

    מגניב

    ...
    android {
        ...
        defaultConfig {...}
        signingConfigs {
            release {
                storeFile file("myreleasekey.keystore")
                storePassword "password"
                keyAlias "MyReleaseKey"
                keyPassword "password"
            }
        }
        buildTypes {
            release {
                ...
                signingConfig signingConfigs.release
            }
        }
    }

הערה: לא מומלץ לכלול את הסיסמאות של מפתח ההפצה ומאגר המפתחות בקובץ ה-build. במקום זאת, צריך להגדיר את קובץ ה-build כך שיקבל את הסיסמאות האלה ממשתני סביבה, או להגדיר את תהליך ה-build כך שהוא יבקש את הסיסמאות האלה.

כדי לקבל את הסיסמאות האלה ממשתני סביבה:

Kotlin

storePassword = System.getenv("KSTOREPWD")
keyPassword = System.getenv("KEYPWD")

מגניב

storePassword System.getenv("KSTOREPWD")
keyPassword System.getenv("KEYPWD")

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

אחרי שתשלימו את התהליך הזה, תוכלו להפיץ את האפליקציה ולפרסם אותה ב-Google Play.

אזהרה: חשוב לשמור את מאגר המפתחות ואת המפתח הפרטי במקום בטוח ומאובטח, ולוודא שיש לכם גיבויים מאובטחים שלהם. אם אתם משתמשים בחתימת אפליקציה ב-Play ואיבדתם את מפתח ההעלאה, אתם יכולים לבקש איפוס דרך Play Console. אם אתם מפרסמים אפליקציה ללא חתימת אפליקציה ב-Play (לאפליקציות שנוצרו לפני אוגוסט 2021) ואתם מאבדים את מפתח חתימת האפליקציה, לא תוכלו לפרסם עדכונים לאפליקציה, כי אתם תמיד צריכים לחתום על כל הגרסאות של האפליקציה עם אותו מפתח.

חתימה על אפליקציות Wear OS

כשמפרסמים אפליקציות ל-Wear OS, חבילת ה-APK של השעון וחבילת ה-APK של הטלפון (אם יש כזו) צריכות להיות חתומות באותו מפתח. מידע נוסף על אריזה וחתימה של אפליקציות ל-Wear OS זמין במאמר בנושא אריזה והפצה של אפליקציות ל-Wear.