Zalecana architektura aplikacji na Androida sprzyja podziałowi kod należy podzielić na klasy, co umożliwia unikanie problemów. Jest to zasada, w których każda klasa ma jedną zdefiniowaną odpowiedzialność. Dzięki temu zwiększa się liczba mniejszych klas, które trzeba połączyć ze sobą. aby wypełniać swoje zależności.

Zależności między klasami można przedstawić w formie wykresu, na którym każda z nich
klasa jest połączona z klasami, od których zależy. Reprezentacja wszystkich
klas i ich zależności składają się na graf aplikacji.
Na ilustracji 1 widać abstrakcyjny wykres aplikacji.
Gdy klasa A (ViewModel
) jest zależna od klasy B (Repository
), istnieje
linia łącząca punkty A do B, która reprezentuje tę zależność.
Wstrzykiwanie zależności pomaga nawiązywać te połączenia i umożliwia ich zamianę
do testowania. Na przykład podczas testowania funkcji ViewModel
w zależności od repozytorium, możesz przekazywać różne implementacje
Repository
z podróbkami, aby przetestować różne przypadki.
Podstawy ręcznego wstrzykiwania zależności
Z tej sekcji dowiesz się, jak zastosować ręczne wstrzykiwanie zależności w prawdziwym Androidzie w przypadku aplikacji. Omawiamy w nim powtarzalne podejście do tego, jak zacząć wstrzykiwanie zależności w aplikacji. Metoda ta będzie się doskonalić aż do osiągnięcia który jest bardzo podobny do tego, co Dagger automatycznie wygeneruje do Ciebie. Więcej informacji o krzyżyku znajdziesz w artykule Dagger – podstawy.
Potraktuj przepływ jako grupę ekranów w aplikacji, które odpowiadają funkcji. Przykładami procesów są logowanie, rejestracja i płatności.
W procesie logowania w typowej aplikacji na Androida LoginActivity
zależy od kolumny LoginViewModel
, która z kolei zależy od UserRepository
.
Następnie UserRepository
zależy od wartości UserLocalDataSource
i
UserRemoteDataSource
, która z kolei zależy od Retrofit
posprzedażna.

LoginActivity
to punkt wejścia do procesu logowania się,
wchodzi w interakcję z aktywnością. Dlatego LoginActivity
musi utworzyć
LoginViewModel
ze wszystkimi zależnościami.
Klasy Repository
i DataSource
procesu wyglądają tak:
class UserRepository(
private val localDataSource: UserLocalDataSource,
private val remoteDataSource: UserRemoteDataSource
) { ... }
class UserLocalDataSource { ... }
class UserRemoteDataSource(
private val loginService: LoginRetrofitService
) { ... }
class UserLocalDataSource {
public UserLocalDataSource() { }
...
}
class UserRemoteDataSource {
private final Retrofit retrofit;
public UserRemoteDataSource(Retrofit retrofit) {
this.retrofit = retrofit;
}
...
}
class UserRepository {
private final UserLocalDataSource userLocalDataSource;
private final UserRemoteDataSource userRemoteDataSource;
public UserRepository(UserLocalDataSource userLocalDataSource, UserRemoteDataSource userRemoteDataSource) {
this.userLocalDataSource = userLocalDataSource;
this.userRemoteDataSource = userRemoteDataSource;
}
...
}
Tak wygląda LoginActivity
:
class LoginActivity: Activity() {
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// In order to satisfy the dependencies of LoginViewModel, you have to also
// satisfy the dependencies of all of its dependencies recursively.
// First, create retrofit which is the dependency of UserRemoteDataSource
val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
// Then, satisfy the dependencies of UserRepository
val remoteDataSource = UserRemoteDataSource(retrofit)
val localDataSource = UserLocalDataSource()
// Now you can create an instance of UserRepository that LoginViewModel needs
val userRepository = UserRepository(localDataSource, remoteDataSource)
// Lastly, create an instance of LoginViewModel with userRepository
loginViewModel = LoginViewModel(userRepository)
}
}
public class MainActivity extends Activity {
private LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// In order to satisfy the dependencies of LoginViewModel, you have to also
// satisfy the dependencies of all of its dependencies recursively.
// First, create retrofit which is the dependency of UserRemoteDataSource
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService.class);
// Then, satisfy the dependencies of UserRepository
UserRemoteDataSource remoteDataSource = new UserRemoteDataSource(retrofit);
UserLocalDataSource localDataSource = new UserLocalDataSource();
// Now you can create an instance of UserRepository that LoginViewModel needs
UserRepository userRepository = new UserRepository(localDataSource, remoteDataSource);
// Lastly, create an instance of LoginViewModel with userRepository
loginViewModel = new LoginViewModel(userRepository);
}
}
W przypadku tego podejścia mogą wystąpić problemy:
Do tego dochodzi dużo sztywnego kodu. Jeśli chcesz utworzyć kolejną instancję
LoginViewModel
w innej części kodu, spowoduje to jego zduplikowanie.Zależności muszą zostać zadeklarowane w odpowiedniej kolejności. Musisz utworzyć instancję
UserRepository
przedLoginViewModel
, aby go utworzyć.Trudno jest je ponownie wykorzystać. Jeśli chcesz ponownie wykorzystać
UserRepository
w wielu funkcjach, musisz dostosować singleton. Wzorzec singletona utrudnia testowanie, ponieważ wszystkie testy z jedną instancją jednostkową.
Zarządzanie zależnościami za pomocą kontenera
Aby rozwiązać problem ponownego wykorzystywania obiektów, możesz utworzyć własne
klasa kontenera zależności, której używasz do pobierania zależności. Wszystkie instancje
udostępniane przez ten kontener mogą być publiczne. W tym przykładzie potrzebujesz tylko
instancji UserRepository
, możesz ustawić jego zależności jako prywatne za pomocą
opcję ich publicznego udostępniania, jeśli zajdzie potrzeba ich udostępnienia:
// Container of objects shared across the whole app
class AppContainer {
// Since you want to expose userRepository out of the container, you need to satisfy
// its dependencies as you did before
private val retrofit = Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService::class.java)
private val remoteDataSource = UserRemoteDataSource(retrofit)
private val localDataSource = UserLocalDataSource()
// userRepository is not private; it'll be exposed
val userRepository = UserRepository(localDataSource, remoteDataSource)
}
// Container of objects shared across the whole app
public class AppContainer {
// Since you want to expose userRepository out of the container, you need to satisfy
// its dependencies as you did before
private Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://example.com")
.build()
.create(LoginService.class);
private UserRemoteDataSource remoteDataSource = new UserRemoteDataSource(retrofit);
private UserLocalDataSource localDataSource = new UserLocalDataSource();
// userRepository is not private; it'll be exposed
public UserRepository userRepository = new UserRepository(localDataSource, remoteDataSource);
}
Te zależności są używane w całej aplikacji, dlatego muszą
umieścić w wspólnym miejscu we wszystkich działaniach, z których mogą korzystać:
Application
. Utwórz niestandardową
Klasa Application
zawierająca instancję AppContainer
.
// Custom Application class that needs to be specified
// in the AndroidManifest.xml file
class MyApplication : Application() {
// Instance of AppContainer that will be used by all the Activities of the app
val appContainer = AppContainer()
}
// Custom Application class that needs to be specified
// in the AndroidManifest.xml file
public class MyApplication extends Application {
// Instance of AppContainer that will be used by all the Activities of the app
public AppContainer appContainer = new AppContainer();
}
Teraz możesz pobrać instancję AppContainer
z aplikacji
uzyskaj udostępniony plik UserRepository
instancji:
class LoginActivity: Activity() {
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Gets userRepository from the instance of AppContainer in Application
val appContainer = (application as MyApplication).appContainer
loginViewModel = LoginViewModel(appContainer.userRepository)
}
}
public class MainActivity extends Activity {
private LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Gets userRepository from the instance of AppContainer in Application
AppContainer appContainer = ((MyApplication) getApplication()).appContainer;
loginViewModel = new LoginViewModel(appContainer.userRepository);
}
}
W ten sposób nie utworzysz ani jednego elementu UserRepository
. Zamiast tego masz
AppContainer
wspólne dla wszystkich aktywności zawierających obiekty z grafu
i tworzy instancje tych obiektów, które mogą być wykorzystywane przez inne klasy.
Jeśli LoginViewModel
jest potrzebny w większej liczbie miejsc w aplikacji,
w jednym miejscu, w którym tworzysz instancje LoginViewModel
,
z całego świata. Możesz przenieść tworzenie obiektu LoginViewModel
do kontenera i podać
tego typu obiekty
z fabryką. Kod dla aplikacji LoginViewModelFactory
wygląda tak:
// Definition of a Factory interface with a function to create objects of a type
interface Factory<T> {
fun create(): T
}
// Factory for LoginViewModel.
// Since LoginViewModel depends on UserRepository, in order to create instances of
// LoginViewModel, you need an instance of UserRepository that you pass as a parameter.
class LoginViewModelFactory(private val userRepository: UserRepository) : Factory
// Definition of a Factory interface with a function to create objects of a type
public interface Factory<T> {
T create();
}
// Factory for LoginViewModel.
// Since LoginViewModel depends on UserRepository, in order to create instances of
// LoginViewModel, you need an instance of UserRepository that you pass as a parameter.
class LoginViewModelFactory implements Factory
Możesz uwzględnić LoginViewModelFactory
w AppContainer
i ustawić w parametrze
LoginActivity
spożywa:
// AppContainer can now provide instances of LoginViewModel with LoginViewModelFactory
class AppContainer {
...
val userRepository = UserRepository(localDataSource, remoteDataSource)
val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
class LoginActivity: Activity() {
private lateinit var loginViewModel: LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Gets LoginViewModelFactory from the application instance of AppContainer
// to create a new LoginViewModel instance
val appContainer = (application as MyApplication).appContainer
loginViewModel = appContainer.loginViewModelFactory.create()
}
}
// AppContainer can now provide instances of LoginViewModel with LoginViewModelFactory
public class AppContainer {
...
public UserRepository userRepository = new UserRepository(localDataSource, remoteDataSource);
public LoginViewModelFactory loginViewModelFactory = new LoginViewModelFactory(userRepository);
}
public class MainActivity extends Activity {
private LoginViewModel loginViewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Gets LoginViewModelFactory from the application instance of AppContainer
// to create a new LoginViewModel instance
AppContainer appContainer = ((MyApplication) getApplication()).appContainer;
loginViewModel = appContainer.loginViewModelFactory.create();
}
}
Ta metoda jest lepsza niż poprzednia, ale nadal będziemy stosować wyzwania, które należy wziąć pod uwagę:
Musisz samodzielnie zarządzać domeną
AppContainer
, tworząc instancje dla wszystkich i innych zależności.To nadal bardzo dużo kodu. Musisz zbudować fabryki ręcznie w zależności od tego, czy chcesz ponownie użyć obiektu.
Zarządzanie zależnościami w przepływach aplikacji
Usługa AppContainer
staje się skomplikowana, gdy chcesz dodać więcej funkcji do
nad projektem. Gdy Twoja aplikacja stanie się większa i zaczniesz wprowadzać różne
pojawia się więcej problemów.
Jeśli stosujesz różne przepływy pracy, możesz chcieć, by obiekty znajdowały się zakresu tego procesu. Na przykład podczas tworzenia obiektu
LoginUserData
(który może być składa się z nazwy użytkownika i hasła używanych tylko podczas logowania), nie chcesz, zachowywania danych ze starego procesu logowania się innego użytkownika. Chcesz mieć nowy dla każdego nowego przepływu. Możesz to osiągnąć, tworzącFlowContainer
w obiekcieAppContainer
, jak pokazano w następnym przykładzie kodu.Optymalizacja wykresu aplikacji i kontenerów przepływu może też być trudna. Musisz pamiętać o usunięciu niepotrzebnych instancji w zależności od w którym żyjesz.
Załóżmy, że masz przepływ logowania, który składa się z jednej aktywności (LoginActivity
)
i kilka fragmentów (LoginUsernameFragment
i LoginPasswordFragment
).
Te widoki chcą:
Uzyskaj dostęp do tej samej instancji
LoginUserData
, która musi być udostępniona do momentu gdy proces logowania się zakończy.Utwórz nową instancję
LoginUserData
, gdy przepływ zacznie się od nowa.
Możesz to osiągnąć za pomocą kontenera procesu logowania. Ten kontener musi być utworzona podczas rozpoczynania procesu logowania i usuwana z pamięci po jego zakończeniu.
Dodajmy do przykładowego kodu LoginContainer
. Chcesz mieć możliwość tworzenia
wiele wystąpień ciągu LoginContainer
w aplikacji, więc zamiast używania go
w singleton, ustaw ją jako klasę z zależnościami, których potrzebuje przepływ logowania
AppContainer
.
class LoginContainer(val userRepository: UserRepository) {
val loginData = LoginUserData()
val loginViewModelFactory = LoginViewModelFactory(userRepository)
}
// AppContainer contains LoginContainer now
class AppContainer {
...
val userRepository = UserRepository(localDataSource, remoteDataSource)
// LoginContainer will be null when the user is NOT in the login flow
var loginContainer: LoginContainer? = null
}
// Container with Login-specific dependencies
class LoginContainer {
private final UserRepository userRepository;
public LoginContainer(UserRepository userRepository) {
this.userRepository = userRepository;
loginViewModelFactory = new LoginViewModelFactory(userRepository);
}
public LoginUserData loginData = new LoginUserData();
public LoginViewModelFactory loginViewModelFactory;
}
// AppContainer contains LoginContainer now
public class AppContainer {
...
public UserRepository userRepository = new UserRepository(localDataSource, remoteDataSource);
// LoginContainer will be null when the user is NOT in the login flow
public LoginContainer loginContainer;
}
Gdy masz już kontener dostosowany do przepływu, musisz zdecydować, kiedy utworzyć
i usuń instancję kontenera. Proces logowania się jest autonomiczny w
działanie (LoginActivity
) to działanie, które zarządza cyklem życia
tego kontenera. LoginActivity
może utworzyć instancję w: onCreate()
oraz
Usuń go w aplikacji onDestroy()
.
class LoginActivity: Activity() {
private lateinit var loginViewModel: LoginViewModel
private lateinit var loginData: LoginUserData
private lateinit var appContainer: AppContainer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
appContainer = (application as MyApplication).appContainer
// Login flow has started. Populate loginContainer in AppContainer
appContainer.loginContainer = LoginContainer(appContainer.userRepository)
loginViewModel = appContainer.loginContainer.loginViewModelFactory.create()
loginData = appContainer.loginContainer.loginData
}
override fun onDestroy() {
// Login flow is finishing
// Removing the instance of loginContainer in the AppContainer
appContainer.loginContainer = null
super.onDestroy()
}
}
public class LoginActivity extends Activity {
private LoginViewModel loginViewModel;
private LoginData loginData;
private AppContainer appContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
appContainer = ((MyApplication) getApplication()).appContainer;
// Login flow has started. Populate loginContainer in AppContainer
appContainer.loginContainer = new LoginContainer(appContainer.userRepository);
loginViewModel = appContainer.loginContainer.loginViewModelFactory.create();
loginData = appContainer.loginContainer.loginData;
}
@Override
protected void onDestroy() {
// Login flow is finishing
// Removing the instance of loginContainer in the AppContainer
appContainer.loginContainer = null;
super.onDestroy();
}
}
Podobnie jak LoginActivity
, fragmenty logowania mogą uzyskać dostęp do LoginContainer
z
AppContainer
i użyj współdzielonej instancji LoginUserData
.
Bo w tym przypadku mamy do czynienia z logiką wyświetlania obserwacja cyklu życia ma sens.
Podsumowanie
Wstrzykiwanie zależności to dobra technika tworzenia skalowalnych i możliwych do testowania Aplikacje na Androida. Używanie kontenerów do udostępniania instancji klas w różnych poszczególnych części aplikacji oraz jako centralne miejsce do tworzenia instancji przy użyciu fabryk.
Gdy Twoja aplikacja się powiększy, zaczniesz pisać dużo (np. fabryki), który jest podatny na błędy. Musisz także samodzielnie zarządzać zakresem i cyklem życia kontenerów, optymalizując odrzucanie kontenerów, które nie są już potrzebne, aby zwolnić pamięć. Jeśli zrobisz to nieprawidłowo, mogą wystąpić drobne błędy i wycieki pamięci w Twojej aplikacji.
W sekcji Dagger dowiedz się, jak za pomocą Daggera zautomatyzować ten proces i wygenerować ten sam kod w innej sytuacji.