Hiểu được sự khác nhau cơ bản của tất cả các configurations (implementation và api) sẽ giúp chúng ta kiểm soát được compile classpath và runtime classpath trong sự phụ thuộc (dependency) của một module với consumers của nó.
Compile classpath
Ta sẽ có thể truy cập vào các classes mà có trong compile classpath của nó. Nếu chúng ta sử dụng một module mà không có sẵn trong classpath thì tại thời điểm biên dịch sẽ có lỗi xảy ra.
Runtime classpath
Các classes mà ta sẽ có thể truy cập tại thời điểm runtime. Điều này có nghĩa là chỉ có thể sử dụng các classes này khi mà việc build đã hoàn thành.
Đầu tiên chúng ta sẽ cùng tìm hiểu từng khái niệm để có cái nhìn tổng quan về hoạt động của chúng trong Gradle Dependency
Implementation
Là configuration được sử dụng chủ yếu trong gradle dependency, khi chúng ta implementation một module thì sẽ tránh được việc gọi trực tiếp vào các module được khai báo bên trong nó. Việc này sẽ làm giảm thời gian biên dịch và hơn thế nữa là hạn chế việc phụ thuộc chéo giữa các module với nhau.
api
Ngược lại với implementation khi ta dùng api thì ta có thể gọi trực tiếp vào các modules mà phụ thuộc vào module ta khai báo api. Việc sử dụng api ta phải vô cùng cẩn thận để tránh trường hợp các modules bị phụ thuộc chồng chéo vào nhau.
Cùng xem một ví dụ để có thể hiểu hơn về các khái niệm trên
Giả sử chúng ta có một thư viện là LibraryA và bên trong LibraryA ta sử dụng một thư viện khác là LibraryB.
Trong thư viện LibraryB có một class như sau:
public class LibraryBAction { public static String doAction() { return “Do action in library B”; } }
Trong thư viện LibraryA ta cũng có một class như sau:
public class LibraryA { public String getAction() { return LibraryBAction.doAction(); } }
Để sử dụng LibraryB trong LibraryA như trên ta thực hiện một khái báo dependency với api như sau:
dependencies { api project(‘:LibraryB’) }
Và cuối dùng để dụng được LibraryA bên trong app của ta thì trong build.gradle của app ta thực hiện khai báo như sau:
dependencies { implementation project(‘:LibraryA’) }
Vì đã dùng api (hoặc là dùng compile một configuration đã bị khai tử) để tạo phụ thuộc cho LibraryB vào LibraryA nên tại tầng app tại có thể truy cập trực tiếp vào LibraryB.
//Khai báo sử dụng hàm getAction trong class LibraryA của module LibraryA LibraryA liba = new LibraryA(); System.out.println(liba.getAction()); //Ta cũng có thể truy cập trực tiếp vào LibraryB System.out.println(LibraryBAction.doAction());
Việc truy cập trực tiếp vào module con nằm bên trong một module, mà tầng app tạo phụ thuộc trực tiếp như trên là việc làm không nên. Vì như ta thấy nó có thể sẽ là một khai báo phụ thuộc mà không phải trực tiếp do ta thêm trong app gây rối cấu trúc của cả project dẫn đến chồng chéo các phụ thuộc.
Trong trường hợp trên sử dụng implementation thay cho api sẽ ngăn chặn tình trạng truy cập trực tiếp vào các module con thành phần. Sau khi thay implementation cho api khi tạo phụ thuộc cho module LibraryB trong module LibraryA ta sẽ không thể gọi trực tiếp hàm doAction() ở bên trong module LibraryB từ tầng app nữa.
Ngoài việc làm cho việc khai báo các phụ thuộc được tường minh thì việc sử dụng implementation sẽ làm giảm đáng kể thời gian biên dịch (compilation). Như trong ví dụ trên khi ta có thay đổi bên trong module LibraryB thì Android Gradle plugin sẽ chỉ thực hiện biên dịch lại module LibraryA thay vì biện dịch lại cả app bởi vì lúc này app đã không thể truy cập trực tiếp vào LibraryB.
Ta cùng xem lại các configurations thông qua các sơ đồ dưới đây:
implementation

api

Leave a Reply