Dagger 2 trong Android - giảm số lượng method
Bài đăng này đã không được cập nhật trong 3 năm
Dagger 2 - là một dependency injection framework thuộc dạng compile-time và fully static. Tách biệt khởi tạo/sử dụng, dễ dàng test hơn(test đơn vị và chức năng), khả năng mở rộng tốt hơn - đây chỉ là một vài lợi ích từ việc sử dụng depedency injection framework như Dagger 2.
Sau một vài bài viết trước trình bày làm thế nào để làm việc với DI. Bài viết này sẽ chia sẻ kinh nghiệm làm việc với Dagger 2 trong phát triển ứng dụng production.
@Component với @Subcomponent
Không được trình bày rõ ràng trong các tutorial đơn giản, nhưng tuỳ chỉnh phạm vi (custom scope) là một trong những tính năng mạnh mẽ nhất trong Dagger 2. Trong các application phức tạp, chỉ sử dụng phạm vi @Singleton
là không đủ để giải quyết vấn đề.
Hãy xem xét ví dụ đơn giản: chúng ta có các phụ thuộc (depedencies) được kết nối chặt chẽ với người dùng (user) hiện đang đăng nhập (ví dụ: class UserProfilePresenter
chịu trách nhiệm về logic trong màn hình UserProfile). Thay vì gọi tới bộ lưu trữ dữ liệu mỗi khi cần thực thể user, chúng ta có thể tạo ra phạm vi @User
và giữ tất cả các phụ thuộc liên quan như là một single instance trong UserComponent
mà tồn tại (live) trong suốt quá trình user đang còn đăng nhập.
Trong Dagger 2, chúng ta có 2 cách để thực hiện custom scope là kế thừa hoặc mở rộng đồ thị phụ thuộc (object graph):
- Chúng ta có thể xây dựng một
@Component
khác để chỉ rõ Component nào được mở rộng (trong trường hợp này là AppComponent):
@UserScope
@Component(
modules = UserModule.class,
dependencies = AppComponent.class
)
public interface UserComponent {
UserProfileActivity inject(UserProfileActivity activity);
}
- Hoặc, chúng ta có thể định nghĩa một
@Subcomponent
được tạo ra từ Component cơ sở với abstract factory method:
@UserScope
@Subcomponent(
modules = UserModule.class
)
public interface UserComponent {
UserProfileActivity inject(UserProfileActivity activity);
}
//===== AppComponent.java =====
@Singleton
@Component(modules = {...})
public interface AppComponent {
// Factory method to create subcomponent
UserComponent plus(UserModule module);
}
Sự khác biệt giữa 2 phương pháp này là gì?
@Subcomponent
có quyền truy cập vào tất cả các phụ thuộc (depedencies) từ Component cha mẹ. Trong khi @Component
chỉ được truy cập vào những depedencies được công khai (public) trong interface của Component cơ sở.
Khi số lượng method trở thành vấn đề
Tuy nhiên, có một sự khác biệt quan trọng nữa giữa @Subcomponent
và @Component
. Chúng ta hãy cùng nhìn vào phần generated code được Dagger 2 tự động tạo ra. Phần implement của interface Subcomponent chỉ là một inner class bên trong Component.
Cụ thể, UserProfileActivityComponent mà phụ thuộc vào AppComponent sẽ tạo ra code như thế này:
public final class DaggerAppComponent implements AppComponent {
//...AppComponent code...
private final class UserProfileActivityComponentImpl implements UserProfileActivityComponent {
private final UserProfileActivityComponent.UserProfileActivityModule userProfileActivityModule;
private Provider<UserProfileActivity> provideActivityProvider;
private Provider<UserProfileActivityPresenter> userProfileActivityPresenterProvider;
private MembersInjector<UserProfileActivity> userProfileActivityMembersInjector;
private UserProfileActivityComponentImpl(
UserProfileActivityComponent.UserProfileActivityModule userProfileActivityModule) {
this.userProfileActivityModule = Preconditions.checkNotNull(userProfileActivityModule);
initialize();
}
private void initialize() {
this.provideActivityProvider = DoubleCheck.provider(BaseActivityModule_ProvideActivityFactory.create(userProfileActivityModule));
this.userProfileActivityPresenterProvider = DoubleCheck.provider(
UserProfileActivityPresenter_Factory.create(
MembersInjectors.<UserProfileActivityPresenter>noOp(),
provideActivityProvider,
DaggerAppComponent.this.logoutManagerProvider,
DaggerAppComponent.this.userManagerProvider)
);
this.userProfileActivityMembersInjector = UserProfileActivity_MembersInjector.create(
DaggerAppComponent.this.logoutManagerProvider,
DaggerAppComponent.this.userManagerProvider
userProfileActivityPresenterProvider)
);
}
@Override
public UserProfileActivity inject(UserProfileActivity activity) {
userProfileActivityMembersInjector.injectMembers(activity);
return activity;
}
}
}
Dòng 20-31 chỉ cho chúng ta thấy cách Dagger cung cấp các depedencies cần thiết từ AppComponent tới UserProfileActivityComponent. Subcomponent có quyền truy cập vào các fields được cung cấp của Component cơ sở để chúng có thể được sử dụng trực tiếp.
Mọi thứ là khác nhau với phụ thuộc @Component
. Cấu trúc tương tự (UserProfileActivityComponent phụ thuộc vào AppComponent ) sẽ được tạo ra code như sau:
public final class DaggerUserProfileActivityComponent implements UserProfileActivityComponent {
private Provider<LogoutManager> logoutManagerProvider;
private Provider<UserManager> userManagerProvider;
//...
private DaggerUserProfileActivityComponent(Builder builder) {
assert builder != null;
initialize(builder);
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.logoutManagerProvider = new Factory<LogoutManager>() {
private final AppComponent appComponent = builder.appComponent;
@Override
public LogoutManager get() {
return Preconditions.checkNotNull(
appComponent.getLogoutManager(),
"Cannot return null from a non-@Nullable component method");
}
};
this.userManagerProvider = new Factory<UserManager>() {
private final AppComponent appComponent = builder.appComponent;
@Override
public UserManager get() {
return Preconditions.checkNotNull(
appComponent.getUserManager(),
"Cannot return null from a non-@Nullable component method");
}
};
//... more providers ....
}
@Override
public UserProfileActivity inject(UserProfileActivity activity) {
userProfileActivityMembersInjector.injectMembers(activity);
return activity;
}
//...
}
Dòng 14-34 cho thấy rằng, mỗi dependency yêu cầu từ AppComponent cần phải có method riêng mà cung cấp đối tượng Provider<> cho nó.
Tại sao? Bởi vì Component
phụ thuộc chỉ có thể truy cập vào những dependencies trong Component
cơ sở được exposed với public interface của nó.
Điều này có ý nghĩa gì với chúng ta - những người phát triển ứng dụng Android? Mỗi dependency trong componet phụ thuộc được lấy từ component cơ sở sẽ khiến số lượng method ngày càng gần số lượng giới hạn là 65k mothod.
Phân tích APK một cách nhanh chóng
Bắt đầu từ Android Studio 2.2, có một tính năng mới cho phép chúng ta phân tích file APK. Chúng ta có thể truy cập từ Build -> Analyze APK….
Kết luận
Bởi vì vậy, chúng ta nên quyết định sử dụng @Subcomponent
thay vì @Component
phụ thuộc. Làm như vậy chúng ta có thể giảm được rất nhiều số lượng method (trong trường hợp của tôi là ~5000 methods).
All rights reserved