Cấu hình lớp thực hiện so với cấu hình API


231

Tôi đang cố gắng tìm ra sự khác biệt giữa apiimplementationcấu hình trong khi xây dựng các phụ thuộc của mình.
Trong tài liệu này, nó nói rằng implementationcó thời gian xây dựng tốt hơn, nhưng, khi thấy nhận xét này trong một câu hỏi tương tự tôi đã tự hỏi liệu nó có đúng không.
Vì tôi không phải là chuyên gia trong lớp, tôi hy vọng ai đó có thể giúp đỡ. Tôi đã đọc tài liệu này rồi nhưng tôi băn khoăn về một lời giải thích dễ hiểu.


1
Bạn đã đọc ở đây ?
MatPag

như trong vấn đề thực tế, tôi đã làm, nhưng, như tôi đã nói, nhận xét đó làm cho tự hỏi về nó. vì vậy bây giờ tôi đã mất
reinaldomoreira

Bạn có thể sẽ chuyển đổi phụ thuộc thư viện của bạn từ compilesang api. Các thư viện bạn sử dụng nội bộ có thể sử dụng một số triển khai riêng tư không được hiển thị trong thư viện cuối cùng để chúng minh bạch với bạn. Những phụ thuộc "riêng tư nội bộ" đó có thể được chuyển sang implementationvà khi plugin cấp độ Android sẽ biên dịch ứng dụng của bạn, nó sẽ bỏ qua việc biên dịch các phụ thuộc đó dẫn đến thời gian xây dựng nhỏ hơn (nhưng những phụ thuộc đó sẽ có sẵn trong thời gian chạy). Rõ ràng bạn có thể làm điều tương tự nếu bạn có thư viện mô-đun cục bộ
MatPag

1
Dưới đây là một lời giải thích đồ họa ngắn về 'api' và 'triển khai': jeroenmols.com/blog/2017/06/14/androidstudio3
albert c braun

1
đó là một bài viết tuyệt vời! cảm ơn bạn @albertbraun
reinaldomoreira

Câu trả lời:


418

compileTừ khóa Gradle không được dùng để ủng hộ apiimplementationtừ khóa để cấu hình các phụ thuộc.

Sử dụng apilà tương đương với việc sử dụng không dùng nữa compile, vì vậy nếu bạn thay thế tất cả compilebằng apimọi thứ sẽ hoạt động như mọi khi.

Để hiểu implementationtừ khóa hãy xem xét ví dụ sau.

THÍ DỤ

Giả sử bạn có một thư viện được gọi là MyLibrarybên trong sử dụng một thư viện khác được gọi InternalLibrary. Một cái gì đó như thế này:

    // 'InternalLibrary' module
    public class InternalLibrary {
        public static String giveMeAString(){
            return "hello";
        }
    }
    // 'MyLibrary' module
    public class MyLibrary {
        public String myString(){
            return InternalLibrary.giveMeAString();
        }
    }

Giả sử sử MyLibrary build.gradledụng apicấu hình dependencies{}như thế này:

dependencies {
    api project(':InternalLibrary')
}

Bạn muốn sử dụng MyLibrarymã của mình để trong ứng dụng của build.gradlebạn, bạn thêm phụ thuộc này:

dependencies {
    implementation project(':MyLibrary')
}

Sử dụng apicấu hình (hoặc không dùng nữa compile), bạn có thể truy cập InternalLibraryvào mã ứng dụng của mình:

// Access 'MyLibrary' (granted)
MyLibrary myLib = new MyLibrary();
System.out.println(myLib.myString());

// Can ALSO access the internal library too (and you shouldn't)
System.out.println(InternalLibrary.giveMeAString());

Theo cách này, mô-đun MyLibrarycó khả năng "rò rỉ" việc thực hiện nội bộ của một cái gì đó. Bạn không nên (có thể) sử dụng nó vì nó không được bạn nhập trực tiếp.

Các implementationcấu hình đã được giới thiệu để ngăn chặn điều này. Vì vậy, bây giờ nếu bạn sử dụng implementationthay vì apitrong MyLibrary:

dependencies {
    implementation project(':InternalLibrary')
}

bạn sẽ không thể gọi InternalLibrary.giveMeAString()mã ứng dụng của mình nữa.

Loại này của chiến lược đấm bốc cho phép Android Gradle plugin để biết rằng nếu bạn chỉnh sửa một cái gì đó trong InternalLibrary, nó chỉ phải kích hoạt các biên dịch lại của MyLibrarykhông các biên dịch lại toàn bộ ứng dụng của bạn, bởi vì bạn không có quyền truy cập vào InternalLibrary.

Khi bạn có nhiều phụ thuộc lồng nhau, cơ chế này có thể tăng tốc độ xây dựng lên rất nhiều. (Xem video được liên kết ở cuối để hiểu đầy đủ về điều này)

KẾT LUẬN

  • Khi bạn chuyển sang plugin Android Gradle 3.XX mới, bạn nên thay thế tất cả compilebằng implementationtừ khóa (1 *) . Sau đó thử biên dịch và kiểm tra ứng dụng của bạn. Nếu mọi thứ đều ổn, hãy để nguyên mã, nếu bạn gặp vấn đề, có thể bạn đã có vấn đề gì đó với các phụ thuộc của mình hoặc bạn đã sử dụng một cái gì đó là riêng tư và không thể truy cập nhiều hơn. Đề xuất của kỹ sư plugin Android Gradle Jerome Dochez (1 ) * )

  • Nếu bạn là người quản lý thư viện, bạn nên sử dụng apicho mọi phụ thuộc cần thiết cho API công khai của thư viện, trong khi sử dụng implementationcho các phụ thuộc kiểm tra hoặc phụ thuộc mà người dùng cuối không sử dụng.

Bài viết hữu ích Thể hiện sự khác biệt giữa thực hiệnapi

TÀI LIỆU THAM KHẢO (Đây là cùng một video được chia ra để tiết kiệm thời gian)

Google I / O 2017 - Cách tăng tốc độ xây dựng Gradle (VIDEO đầy đủ)

Google I / O 2017 - Cách tăng tốc độ xây dựng Gradle (CHỈ CÓ PHẦN GRADLE PLUGIN 3.0.0 MỚI)

Google I / O 2017 - Cách tăng tốc độ xây dựng Gradle (tham khảo 1 * )

Tài liệu Android


4
Tôi nhận thấy rằng api dường như không hoạt động tốt trong các mô-đun thư viện. Nếu tôi sử dụng nó, tôi vẫn không thể truy cập các phụ thuộc từ dự án ứng dụng của mình. Tôi chỉ có thể truy cập mã trong thư viện đó.
Allan W

1
Điều này tốt và hoạt động trên các bản dựng gỡ lỗi nhưng khi sử dụng ProGuard (trên các phiên bản phát hành) MyLibrary#myString()sẽ bị sập vì ProGuard sẽ bị InternalLibraryxóa. Cách thực hành tốt nhất cho android-lib được sử dụng trong các ứng dụng của ProGuard là gì?
hardysim

3
Tôi nghĩ rằng câu trả lời là không chính xác, ứng dụng có thể sử dụng bất kỳ phạm vi nào nó muốn cho MyL Library. Nó sẽ thấy hay không InternalL Library tùy thuộc vào việc MyL Library có sử dụng api / hiện thực hay không.
Snicolas

2
cảm ơn người đàn ông lời giải thích tuyệt vời, tốt hơn nhiều so với lời giải thích được đưa ra trong các tài liệu chính thức của Android
Henry

2
đó là một lời giải thích hay Lý thuyết và bê tông trộn lẫn rực rỡ. Làm tốt. Cảm ơn vì điều đó
Peter Kahn

134

Tôi thích nghĩ về một apiphụ thuộc là công khai (được xem bởi các mô-đun khác) trong khi implementationphụ thuộc là riêng tư (chỉ được nhìn thấy bởi mô-đun này).

Lưu ý, không giống như public/ privatebiến và phương thức, api/ implementationphụ thuộc không được thi hành bởi bộ thực thi. Đây chỉ đơn thuần là tối ưu hóa thời gian xây dựng, cho phép Gradlebiết các mô-đun cần biên dịch lại khi một trong các phụ thuộc thay đổi API của nó.


16
Yêu sự đơn giản của câu trả lời này cảm ơn bạn rất nhiều
Kevin Gilles

2
Sự khác biệt thực sự (AFAICT) là tệp pom được tạo đặt các apiphụ thuộc trong phạm vi "biên dịch" (chúng sẽ được bao gồm dưới dạng phụ thuộc trong thư viện của bạn và mọi thứ phụ thuộc vào thư viện của bạn) và implementationphụ thuộc trong phạm vi "thời gian chạy" classpath khi mã của bạn đang chạy, nhưng chúng không cần thiết để biên dịch mã khác sử dụng thư viện của bạn).
Shadow Man

@ShadowMan Đây là một chi tiết triển khai của plugin, chịu trách nhiệm tạo tệp POM, cách nó ánh xạ phạm vi Gradle sang phạm vi Maven .
dev.bmax

1
Bạn nên sử dụng implementationcho bất kỳ sự phụ thuộc nào được yêu cầu để chạy (và để thư viện của bạn biên dịch), nhưng điều đó không nên tự động được kéo vào các dự án sử dụng thư viện của bạn. Một ví dụ sẽ là jax-rs, thư viện của bạn có thể sử dụng RESTeasy, nhưng nó không nên kéo những lib đó vào bất kỳ dự án nào sử dụng thư viện của bạn, vì họ có thể muốn sử dụng Jersey thay thế.
Shadow Man

1
đây là cách bạn biết ai đó có được nội dung của nó: D cảm ơn vì câu trả lời đơn giản và rõ ràng
Elias Fazel

12

Hãy xem xét bạn có appmô-đun sử dụng lib1như một thư viện và lib1sử dụng lib2như một thư viện. Một cái gì đó như thế này : app -> lib1 -> lib2.

Bây giờ khi sử dụng api lib2trong lib1, sau đó app có thể thấy lib2 mã khi sử dụng: api lib1hoặc implementation lib1trong appmô-đun.

NHƯNG khi sử dụng implementation lib2trong lib1, sau đó app không thể nhìn thấy các lib2mã.


5

Câu trả lời từ @matpag@ dev-bmax đủ rõ ràng để khiến mọi người hiểu cách sử dụng khác nhau giữa thực hiện và api. Tôi chỉ muốn đưa ra một lời giải thích thêm từ một góc độ khác, hy vọng sẽ giúp cho những người có cùng câu hỏi.

Tôi đã tạo hai dự án để thử nghiệm:

  • dự án Một dự án thư viện java có tên 'frameworks-web-gradle-plugin' phụ thuộc vào 'org.springframework.boot: spring-boot-gradle-plugin: 1.5.20.RELEASE'
  • dự án B phụ thuộc vào dự án A bằng cách triển khai 'com.example.frameworks.gradle: frameworks-web-gradle-plugin: 0.0.1-SNAPSHOT'

Hệ thống phân cấp phụ thuộc được mô tả ở trên trông giống như:

[project-b] -> [project-a] -> [spring-boot-gradle-plugin]

Sau đó, tôi đã thử nghiệm các kịch bản sau đây:

  1. Tạo dự án A phụ thuộc vào 'org.springframework.boot: spring-boot-gradle-plugin: 1.5.20.RELEASE' bằng cách thực hiện .

    Chạy gradle dependencieslệnh trong một thiết bị đầu cuối trong thư mục gốc B ject với ảnh chụp màn hình đầu ra sau đây, chúng ta có thể thấy rằng 'spring-boot-gradle-plugin' xuất hiện trong cây phụ thuộc runtimeClasspath, nhưng không phải trong compileClasspath, tôi nghĩ đó chính xác là lý do tại sao chúng ta không thể thực hiện sử dụng thư viện đã khai báo bằng cách sử dụng, nó sẽ không được biên dịch.

    nhập mô tả hình ảnh ở đây

  2. Tạo dự án A phụ thuộc vào 'org.springframework.boot: spring-boot-gradle-plugin: 1.5.20.RELEASE' của api

    Chạy gradle dependencieslệnh trong một thiết bị đầu cuối trong poject B root dir một lần nữa. Bây giờ 'spring-boot-gradle-plugin' xuất hiện cả trong cây phụ thuộc compileClasspath và runtimeClasspath.

    nhập mô tả hình ảnh ở đây

Một sự khác biệt đáng kể mà tôi nhận thấy là sự phụ thuộc trong dự án nhà sản xuất / thư viện được khai báo theo cách triển khai sẽ không xuất hiện trong compileClasspath của các dự án tiêu dùng, do đó chúng tôi không thể sử dụng lib tương ứng trong các dự án tiêu dùng.


2

Từ tài liệu lớp :

Chúng ta hãy xem một kịch bản xây dựng rất đơn giản cho một dự án dựa trên JVM.

plugins {
    id 'java-library'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.hibernate:hibernate-core:3.6.7.Final'
    api 'com.google.guava:guava:23.0'
    testImplementation 'junit:junit:4.+'
}

thực hiện

Các phụ thuộc cần thiết để biên dịch nguồn sản xuất của dự án không phải là một phần của API được dự án đưa ra. Ví dụ, dự án sử dụng Hibernate để thực hiện lớp kiên trì nội bộ của nó.

api

Các phụ thuộc cần thiết để biên dịch nguồn sản xuất của dự án là một phần của API được dự án đưa ra. Ví dụ, dự án sử dụng Guava và hiển thị các giao diện công cộng với các lớp Guava trong chữ ký phương thức của chúng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.