ความแตกต่างระหว่างการนำไปใช้และคอมไพล์ใน Gradle คืออะไร


1027

หลังจากการปรับปรุงเพื่อ Android 3.0 และสตูดิโอสร้างโครงการใหม่ผมสังเกตเห็นว่าในbuild.gradleมีวิธีใหม่ที่จะเพิ่มการอ้างอิงใหม่แทนการcompileมีimplementationและแทนที่จะมีtestCompiletestImplementation

ตัวอย่าง:

 implementation 'com.android.support:appcompat-v7:25.0.0'
 testImplementation 'junit:junit:4.12'

แทน

 compile 'com.android.support:appcompat-v7:25.0.0'
 testCompile 'junit:junit:4.12'

อะไรคือความแตกต่างระหว่างพวกเขากับสิ่งที่ฉันควรใช้

คำตอบ:


1280

TL; DR

เพียงแทนที่:

  • compileด้วยimplementation(หากคุณไม่ต้องการความไว) หรือapi(หากคุณต้องการความไวแสง)
  • testCompile กับ testImplementation
  • debugCompile กับ debugImplementation
  • androidTestCompile กับ androidTestImplementation
  • compileOnlyยังคงใช้ได้ มันถูกเพิ่มเข้ามาใน 3.0 เพื่อแทนที่ให้และไม่ได้รวบรวม ( providedแนะนำเมื่อ Gradle ไม่มีชื่อการกำหนดค่าสำหรับ use-case และตั้งชื่อตามขอบเขตที่ Maven กำหนดไว้)

มันเป็นหนึ่งในการเปลี่ยนแปลงแตกหักมากับ Gradle 3.0 ว่า Google ประกาศที่ IO17

การcompileกำหนดค่าเลิกใช้แล้วและควรถูกแทนที่ด้วยimplementationหรือapi

จากเอกสาร Gradle :

dependencies {
    api 'commons-httpclient:commons-httpclient:3.1'
    implementation 'org.apache.commons:commons-lang3:3.5'
}

การอ้างอิงที่ปรากฏในการapiกำหนดค่าจะถูกนำไปใช้กับผู้บริโภคของไลบรารีและจะปรากฏในคอมไพล์คลาสพา ธ ของผู้บริโภค

การพึ่งพาที่พบในการimplementationกำหนดค่าจะตรงกันข้ามไม่ได้สัมผัสกับผู้บริโภคและดังนั้นจึงไม่รั่วไหลใน classpath รวบรวมของผู้บริโภค สิ่งนี้มีประโยชน์หลายประการ:

  • การพึ่งพาจะไม่รั่วไหลไปสู่การรวบรวม classpath ของผู้บริโภคอีกต่อไปดังนั้นคุณจะไม่เคยขึ้นอยู่กับการพึ่งพาสกรรมกริยา
  • การรวบรวมที่เร็วขึ้นด้วยขนาดพา ธ ที่ลดลง
  • recompilations น้อยกว่าเมื่อการพึ่งพาการใช้งานเปลี่ยนแปลง: ผู้บริโภคไม่จำเป็นต้องทำการคอมไพล์ใหม่
  • การประกาศที่สะอาดยิ่งขึ้น: เมื่อใช้ร่วมกับปลั๊กอิน maven-publish ใหม่ไลบรารี Java จะสร้างไฟล์ POM ที่แยกแยะระหว่างสิ่งที่จำเป็นในการคอมไพล์กับไลบรารีและสิ่งที่จำเป็นในการใช้ไลบรารีที่รันไทม์ (ในคำอื่น ๆ ) ผสมสิ่งที่จำเป็นในการรวบรวมไลบรารีของตัวเองและสิ่งที่จำเป็นในการรวบรวมกับห้องสมุด)

การกำหนดค่าการคอมไพล์ยังคงมีอยู่ แต่ไม่ควรใช้เนื่องจากจะไม่มีการรับประกันว่าจะมีการกำหนดค่าapiและimplementation


หมายเหตุ:หากคุณใช้ไลบรารีในโมดูลแอพของคุณเท่านั้น - กรณีทั่วไป - คุณจะไม่สังเกตเห็นความแตกต่าง
คุณจะเห็นความแตกต่างเฉพาะเมื่อคุณมีโครงการที่ซับซ้อนพร้อมโมดูลที่ขึ้นอยู่กับแต่ละอื่น ๆ หรือคุณกำลังสร้างห้องสมุด


137
ใครคือ "ผู้บริโภค"
Suragch

34
consumer คือโมดูลที่ใช้ไลบรารี ในกรณีของ Android เป็นแอปพลิเคชัน Android ฉันคิดว่านี่ชัดเจนและฉันไม่แน่ใจว่านี่คือสิ่งที่คุณต้องการหรือไม่
humour

21
นั่นคือสิ่งที่ฟังดูเหมือนกับฉันเช่นกัน แต่ถ้าฉันสร้างห้องสมุดแน่นอนว่าฉันต้องการให้ API ของมันถูกเปิดเผยกับแอพ มิฉะนั้นนักพัฒนาแอปจะใช้ห้องสมุดของฉันอย่างไร นั่นเป็นเหตุผลที่ฉันไม่ได้รับความหมายของการimplementationซ่อนการพึ่งพา คำถามของฉันเหมาะสมหรือไม่
Suragch

235
ใช่มันสมเหตุสมผลแล้วหากแอปของคุณขึ้นอยู่กับไลบรารี่ x ซึ่งขึ้นอยู่กับ y, z หากคุณใช้implementationเฉพาะ x api เท่านั้นที่จะได้รับการสัมผัส แต่ถ้าคุณใช้apiy, z ก็จะถูกเปิดเผยเช่นกัน
humour

36
เข้าใจแล้ว! ที่เหมาะสมมากขึ้นในขณะนี้ คุณสามารถเพิ่มคำอธิบายนี้ลงในคำตอบของคุณ ชัดเจนกว่าเอกสารที่ยกมา
Suragch

378

คำตอบนี้จะแสดงให้เห็นถึงความแตกต่างระหว่างimplementation, apiและcompileในโครงการ


สมมติว่าฉันมีโครงการที่มีโมดูล Gradle สามโมดูล:

  • แอพ (แอปพลิเคชัน Android)
  • myandroidlibrary (ห้องสมุด Android)
  • myjavalibrary (ไลบรารี Java)

appมีการmyandroidlibraryพึ่งพา myandroidlibraryมีการmyjavalibrary พึ่งพา

Dependency1

myjavalibraryมีMySecretชั้นเรียน

public class MySecret {

    public static String getSecret() {
        return "Money";
    }
}

myandroidlibraryมีMyAndroidComponentคลาสที่จัดการค่าจากMySecretคลาส

public class MyAndroidComponent {

    private static String component = MySecret.getSecret();

    public static String getComponent() {
        return "My component: " + component;
    }    
}

สุดท้ายappนี้สนใจเพียงคุณค่าจากmyandroidlibrary

TextView tvHelloWorld = findViewById(R.id.tv_hello_world);
tvHelloWorld.setText(MyAndroidComponent.getComponent());

ทีนี้มาพูดถึงการพึ่งพา ...

appจำเป็นที่จะต้องใช้:myandroidlibraryดังนั้นในการใช้งานapp build.gradleimplementation

( หมายเหตุ : คุณสามารถใช้ API / คอมไพล์ได้เช่นกัน แต่ถือความคิดชั่วครู่หนึ่ง)

dependencies {
    implementation project(':myandroidlibrary')      
}

Dependency2

คุณคิดว่าmyandroidlibrarybuild.gradle ควรมีลักษณะอย่างไร เราควรใช้ขอบเขตใด

เรามีสามตัวเลือก:

dependencies {
    // Option #1
    implementation project(':myjavalibrary') 
    // Option #2
    compile project(':myjavalibrary')      
    // Option #3
    api project(':myjavalibrary')           
}

Dependency3

อะไรคือความแตกต่างระหว่างพวกเขากับสิ่งที่ฉันควรใช้

รวบรวมหรือ Api (ตัวเลือก # 2 หรือ # 3) Dependency4

หากคุณกำลังใช้หรือcompile apiแอปพลิเคชัน Android ของเราสามารถเข้าถึงการmyandroidcomponentพึ่งพาซึ่งเป็นMySecretชั้นเรียน

TextView textView = findViewById(R.id.text_view);
textView.setText(MyAndroidComponent.getComponent());
// You can access MySecret
textView.setText(MySecret.getSecret());

การใช้งาน (ตัวเลือก # 1)

Dependency5

หากคุณกำลังใช้implementationการกำหนดค่าMySecretจะไม่ถูกเปิดเผย

TextView textView = findViewById(R.id.text_view);
textView.setText(MyAndroidComponent.getComponent());
// You can NOT access MySecret
textView.setText(MySecret.getSecret()); // Won't even compile

ดังนั้นการกำหนดค่าที่คุณควรเลือก? ขึ้นอยู่กับความต้องการของคุณ

หากคุณต้องการที่จะเปิดเผยการพึ่งพาการใช้งานหรือapicompile

หากคุณไม่ต้องการที่จะเปิดเผยการอ้างอิง (หลบซ่อนตัวอยู่ภายในโมดูลของคุณ) implementationแล้วใช้

บันทึก:

นี่เป็นเพียงส่วนสำคัญของการกำหนดค่า Gradle โปรดดูตารางที่ 49.1 ปลั๊กอิน Java Library - การกำหนดค่าที่ใช้เพื่อประกาศการขึ้นต่อกันสำหรับคำอธิบายโดยละเอียดเพิ่มเติม

ตัวอย่างโครงการสำหรับคำตอบนี้มีอยู่ที่https://github.com/aldoKelvianto/ImplementationVsCompile


1
ฉันได้เพิ่มการอ้างอิงไปยังไฟล์ jar หนึ่งไฟล์โดยใช้การนำไปใช้หากยังไม่เปิดเผยการเข้าถึงไฟล์ทำไมฉันถึงยังสามารถรับและรหัสของฉันทำงานได้ดี
smkrn110

การติดตั้ง @ smkrn110 จะเปิดเผยไลบรารี jar ของคุณ แต่ไม่ใช่การอ้างอิงไลบรารี jar ของคุณ
aldok

2
@WijaySharma สถานะคำตอบที่ยอมรับซึ่งcompileไม่รับประกันสิ่งเดียวกับที่apiรับประกัน
ย่อย 6 ทรัพยากร

9
ฉันคิดว่านี่ควรเป็นคำตอบที่ยอมรับได้ อธิบายได้ดี!
Shashank Kapsime

9
@ StevenW.Klassen เป็น downvote ที่ไม่สมควรที่สุดที่ฉันเคยได้ยิน หากคุณคิดว่าคำสั่งของข้อมูลไม่เหมาะสมแนะนำให้แก้ไขแทนที่จะบ่นเกี่ยวกับมัน
Tim

65

Compileการกำหนดค่าถูกเลิกใช้แล้วและควรถูกแทนที่ด้วยหรือimplementationapi

คุณสามารถอ่านเอกสารที่https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_separation

ส่วนสั้น ๆ เป็น -

ความแตกต่างที่สำคัญระหว่างปลั๊กอิน Java มาตรฐานและปลั๊กอิน Java Library คือสิ่งหลังแนะนำแนวคิดของ API ที่เปิดเผยต่อผู้บริโภค ไลบรารีเป็นส่วนประกอบของ Java ที่ส่วนประกอบอื่นใช้ มันเป็นกรณีการใช้งานทั่วไปในการสร้างหลายโครงการ แต่เมื่อคุณมีการอ้างอิงภายนอก

ปลั๊กอินแสดงการกำหนดค่าสองแบบที่สามารถใช้ในการประกาศการพึ่งพา: api และการใช้งาน ควรใช้การกำหนดค่า api เพื่อประกาศการขึ้นต่อกันซึ่งถูกส่งออกโดยไลบรารี API ในขณะที่การกำหนดค่าการใช้งานควรจะใช้เพื่อประกาศการขึ้นต่อกันที่อยู่ภายในส่วนประกอบ

สำหรับคำอธิบายเพิ่มเติมดูที่ภาพนี้ คำอธิบายสั้น ๆ


46

วิธีแก้ปัญหาโดยย่อ:

วิธีที่ดีกว่าคือการแทนที่การcompileพึ่งพาทั้งหมดด้วยการimplementationอ้างอิง apiและที่เดียวที่คุณรั่วอินเตอร์เฟซของโมดูลที่คุณควรใช้ ที่ควรทำให้การคอมไพล์ซ้ำน้อยลงมาก

 dependencies {
         implementation fileTree(dir: 'libs', include: ['*.jar'])

         implementation 'com.android.support:appcompat-v7:25.4.0'
         implementation 'com.android.support.constraint:constraint-layout:1.0.2'
         // …

         testImplementation 'junit:junit:4.12'
         androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
             exclude group: 'com.android.support', module: 'support-annotations'
         })
 }

อธิบายเพิ่มเติม:

ก่อนที่ Android Gradle plugin 3.0 : เรามีปัญหาใหญ่ซึ่งการเปลี่ยนรหัสหนึ่งทำให้โมดูลทั้งหมดถูกคอมไพล์ใหม่ สาเหตุของเรื่องนี้คือ Gradle ไม่รู้ว่าคุณรั่วส่วนต่อประสานของโมดูลผ่านสิ่งอื่นหรือไม่

หลังจาก Android Gradle plugin 3.0 : ปลั๊กอิน Android Gradleล่าสุดต้องการให้คุณกำหนดอย่างชัดเจนหากคุณรั่วอินเทอร์เฟซของโมดูล ขึ้นอยู่กับว่ามันสามารถสร้างทางเลือกที่เหมาะสมกับสิ่งที่มันควรจะคอมไพล์ซ้ำ

ดังนั้นการcompileพึ่งพาได้ถูกเลิกใช้แล้วและถูกแทนที่ด้วยสองสิ่งใหม่:

  • api: คุณรั่วอินเทอร์เฟซของโมดูลนี้ผ่านอินเทอร์เฟซของคุณเองซึ่งหมายความว่าเหมือนกับการcompileพึ่งพาเก่า

  • implementation: คุณใช้โมดูลนี้ภายในเท่านั้นและไม่รั่วไหลผ่านส่วนต่อประสานของคุณ

ดังนั้นตอนนี้คุณสามารถบอก Gradle ให้คอมไพล์โมดูลอีกครั้งได้อย่างชัดเจนว่าอินเตอร์เฟสของโมดูลที่ใช้นั้นมีการเปลี่ยนแปลงหรือไม่

บล็อกของJeroen Mols


2
คำอธิบายที่สะอาดและรัดกุม ขอบคุณ!
LeOn - Han Li

20
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| Name               | Role                 | Consumable? | Resolveable? | Description                             |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| api                | Declaring            |      no     |      no      | This is where you should declare        |
|                    | API                  |             |              | dependencies which are transitively     |
|                    | dependencies         |             |              | exported to consumers, for compile.     |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| implementation     | Declaring            |      no     |      no      | This is where you should                |
|                    | implementation       |             |              | declare dependencies which are          |
|                    | dependencies         |             |              | purely internal and not                 |
|                    |                      |             |              | meant to be exposed to consumers.       |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| compileOnly        | Declaring compile    |     yes     |      yes     | This is where you should                |
|                    | only                 |             |              | declare dependencies                    |
|                    | dependencies         |             |              | which are only required                 |
|                    |                      |             |              | at compile time, but should             |
|                    |                      |             |              | not leak into the runtime.              |
|                    |                      |             |              | This typically includes dependencies    |
|                    |                      |             |              | which are shaded when found at runtime. |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| runtimeOnly        | Declaring            |      no     |      no      | This is where you should                |
|                    | runtime              |             |              | declare dependencies which              |
|                    | dependencies         |             |              | are only required at runtime,           |
|                    |                      |             |              | and not at compile time.                |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| testImplementation | Test dependencies    |      no     |      no      | This is where you                       |
|                    |                      |             |              | should declare dependencies             |
|                    |                      |             |              | which are used to compile tests.        |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| testCompileOnly    | Declaring test       |     yes     |      yes     | This is where you should                |
|                    | compile only         |             |              | declare dependencies                    |
|                    | dependencies         |             |              | which are only required                 |
|                    |                      |             |              | at test compile time,                   |
|                    |                      |             |              | but should not leak into the runtime.   |
|                    |                      |             |              | This typically includes dependencies    |
|                    |                      |             |              | which are shaded when found at runtime. |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| testRuntimeOnly    | Declaring test       |      no     |      no      | This is where you should                |
|                    | runtime dependencies |             |              | declare dependencies which              |
|                    |                      |             |              | are only required at test               |
|                    |                      |             |              | runtime, and not at test compile time.  |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+

ไม่ตอบคำถามโดยตรง
skryvets

1
นอกจากนี้ยังมีการพัฒนาเท่านั้น
Hohenheimsenberg

ฉันควรใช้อะไรถ้าฉันต้องการทั้งเวลารันไทม์และเวลาคอมไพล์? ขณะนี้ผมได้ตามมาด้วยimplementation runtime
Maroun

8

ความแตกต่างสั้น ๆ ในระยะของคนธรรมดาคือ:

  • หากคุณกำลังทำงานกับอินเทอร์เฟซหรือโมดูลที่ให้การสนับสนุนกับโมดูลอื่นโดยการเปิดเผยสมาชิกของการพึ่งพาที่ระบุไว้คุณควรใช้ 'api'
  • หากคุณกำลังสร้างแอปพลิเคชันหรือโมดูลที่จะนำไปใช้หรือใช้การอ้างอิงที่ระบุไว้ภายในให้ใช้ 'การนำไปใช้'
  • 'คอมไพล์' ทำงานเหมือนกับ 'api' อย่างไรก็ตามหากคุณใช้งานหรือใช้ไลบรารีใด ๆ เท่านั้น 'การใช้งาน' จะทำงานได้ดีขึ้นและประหยัดทรัพยากร

อ่านคำตอบโดย @aldok สำหรับตัวอย่างที่ครอบคลุม


แต่สิ่งที่เกิดขึ้นคือถ้ามีคนจงใจมาที่นี่เพื่อค้นหาคำตอบสำหรับคำถามเหล่านี้แสดงว่าเขาไม่ใช่คนธรรมดา
Rishav

6

ตั้งแต่เวอร์ชัน 5.6.3 เอกสาร Gradleมีกฎง่ายๆที่ระบุว่าควรใช้การcompileอ้างอิงแบบเก่า(หรืออันใหม่) แทนด้วยimplementationการapiอ้างอิงหรือ:

  • ชอบการimplementationกำหนดค่ามากกว่าapiเมื่อเป็นไปได้

สิ่งนี้ทำให้การอ้างอิงไม่อยู่ใน classpath การรวบรวมของผู้บริโภค นอกจากนี้ผู้บริโภคจะไม่สามารถรวบรวมได้ทันทีหากการใช้งานประเภทใดก็ตามรั่วไหลลงสู่ API สาธารณะโดยไม่ตั้งใจ

ดังนั้นเมื่อใดที่คุณควรใช้การapiกำหนดค่า การพึ่งพา API เป็นประเภทที่มีอย่างน้อยหนึ่งประเภทที่เปิดเผยในไลบรารีไบเทอร์เฟซแบบไบนารี่มักเรียกว่า ABI (Application Binary Interface) ซึ่งรวมถึง แต่ไม่ จำกัด เฉพาะ:

  • ชนิดที่ใช้ในคลาสหรืออินเตอร์เฟสระดับสูง
  • ชนิดที่ใช้ในพารามิเตอร์เมธอดพับลิกรวมถึงชนิดพารามิเตอร์ทั่วไป (โดยพับลิกเป็นสิ่งที่มองเห็นได้สำหรับคอมไพเลอร์ Ie พับลิกป้องกันและสมาชิกไพรเวตแพ็กเกจในโลก Java)
  • ประเภทที่ใช้ในเขตข้อมูลสาธารณะ
  • ประเภทคำอธิบายประกอบแบบสาธารณะ

ในทางตรงกันข้ามประเภทใด ๆ ที่ใช้ในรายการต่อไปนี้ไม่เกี่ยวข้องกับ ABI และดังนั้นจึงควรประกาศว่าเป็นการimplementationพึ่งพา:

  • ประเภทที่ใช้เฉพาะในเนื้อความของเมธอด
  • ประเภทที่ใช้เฉพาะในสมาชิกส่วนตัว
  • ประเภทที่พบได้เฉพาะในชั้นเรียนภายใน (Gradle รุ่นอนาคตจะแจ้งให้คุณทราบว่าแพ็คเกจใดเป็นของ API สาธารณะ)

6

Gradle 3.0 แนะนำการเปลี่ยนแปลงต่อไป:

  • compile -> api

    api คำหลักเหมือนกับเลิกใช้แล้ว compile

  • compile -> implementation

    เป็นวิธีที่ดีกว่าเพราะมีข้อดี implementationแสดงการพึ่งพาเพียงหนึ่งระดับขึ้นไปณ เวลาบิลด์ (การพึ่งพามีให้ที่รันไทม์) เป็นผลให้คุณมีบิลด์ที่เร็วขึ้น (ไม่จำเป็นต้องคอมไพล์ผู้บริโภคซึ่งสูงกว่า 1 เลเวลขึ้นไป)

  • provided -> compileOnly

    การพึ่งพานี้มีเฉพาะในเวลารวบรวมเท่านั้น(การอ้างอิงไม่พร้อมใช้งานในขณะทำงาน) .aarพึ่งพานี้ไม่สามารถสกรรมกริยาและ สามารถใช้กับตัวประมวลผลคำอธิบายประกอบเวลาคอมไพล์และช่วยให้คุณลดไฟล์เอาต์พุตสุดท้าย

  • compile -> annotationProcessor

    คล้ายกันมากกับcompileOnlyแต่ยังรับประกันได้ว่าผู้บริโภคจะไม่ปรากฏการพึ่งพาสกรรมกริยา

  • apk -> runtimeOnly

    การพึ่งพาไม่สามารถใช้งานได้ในเวลาคอมไพล์ แต่สามารถใช้งานได้ในขณะทำงาน


ดังนั้นในคำอื่น ๆapi = public, implementation = internalและcompileOnly = private- ฉันต้องสร้างนามแฝงดังกล่าวสำหรับฟังก์ชั่นเหล่านี้ที่พวกเขาเป็นซุปเปอร์ทำให้เกิดความสับสน
t3chb0t
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.