本文已受權 微信公衆號 玉剛說 (@任玉剛)獨家發佈。java
過去一年多的時間裏,我一直在致力於打造一個最簡單,並能讓普通Android開發者都能快速上手的框架,並陸續發表了多篇開發心得,最終彙總爲了《使用Kotlin構建MVVM應用程序》系列文章。其中就涉及到Dagger2和ViewModel的使用,這二者之間的碰撞令我想到了另外一種十分簡單的去進行依賴注入的可能,並引起了一系列的化學反應,能夠說是天做之合。android
能夠在Github上查看相關代碼:github.com/ditclear/Pa…git
本文的寫法不區分MVP仍是MVVM結構,只是提供了一種不那麼循序漸進的注入方式。github
開始以前,咱們先來了解一下Dagger2和ViewModel。緩存
Dagger2是由Google提供的一個適用於Android和Java的快速的依賴注入工具,是現今衆多Android開發者進行依賴注入的首選。微信
但因爲其曲折的學習路線和較高的使用門檻,因而出現了一批又一批從入門到放棄的開發者,固然也包括我。app
而ViewModel是Google的Jetpack組件中的一個。它是用來存儲和管理UI相關的數據,將一個Activity或Fragment組件相關的數據邏輯抽象出來,並能適配組件的生命週期,如當屏幕旋轉Activity重建後,ViewModel中的數據依然有效。它還能夠幫助開發者輕易實現 Fragment 與 Fragment 之間, Activity 與 Fragment 之間的通信以及共享數據。框架
咱們能夠經過如下的代碼來獲取ViewModel實例ide
mViewModel=ViewModelProviders.of(this,factory).get(PaoViewModel::class.java)
複製代碼
其中要提供一個ViewModelProvider.Factory的實例來幫助構建你的ViewModel工具
public interface Factory {
/** * Creates a new instance of the given {@code Class}. * <p> * * @param modelClass a {@code Class} whose instance is requested * @param <T> The type parameter for the ViewModel. * @return a newly created ViewModel */
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}
複製代碼
PS:若是你使用的是MVP結構,那麼只須要讓其繼承自ViewModel,也應該能達到相同的效果
首先,咱們先來看看Dagger2一般的依賴注入的方式
public class FrombulationActivity extends Activity {
@Inject Frombulator frombulator;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// DO THIS FIRST. Otherwise frombulator might be null!
((SomeApplicationBaseType) getContext().getApplicationContext())
.getApplicationComponent()
.newActivityComponentBuilder()
.activity(this)
.build()
.inject(this);
// ... now you can write the exciting code
}
}
複製代碼
這是Dagger-Android用來吐槽Dagger2不行的示例,並給出了緣由,這裏咱們也拿來用一次。
Dagger-Android給出了兩點理由:
也就是說你就算是在基類(BaseActivity/BaseFragment)中將其封裝一下,也無可避免的須要寫getComponent.inject(this)
這樣的代碼,並且還必須在對應的Component中添加相應的inject方法,因而便有了如下的代碼:
@ActivityScope
@Subcomponent
interface ActivityComponent {
fun inject(activity: ArticleDetailActivity)
fun inject(activity: CodeDetailActivity)
fun inject(activity: MainActivity)
fun inject(activity: LoginActivity)
fun supplyFragmentComponentBuilder():FragmentComponent.Builder
}
@FragmentScope
@Subcomponent
interface FragmentComponent {
fun inject(fragment: ArticleListFragment)
fun inject(fragment: CodeListFragment)
fun inject(fragment: CollectionListFragment)
fun inject(fragment: MyCollectFragment)
fun inject(fragment: HomeFragment)
fun inject(fragment: RecentFragment)
fun inject(fragment: SearchResultFragment)
fun inject(fragment: RecentSearchFragment)
fun inject(fragment: MyArticleFragment)
@Subcomponent.Builder
interface Builder {
fun build(): FragmentComponent
}
}
複製代碼
而目的也許就只是爲了自動注入你的ViewModel或者Presenter對象,而後你的目錄結構可能就會下圖通常
而build以後生成的文件將會是這樣的
而後就要用Dagger-Android來解決這些問題?
是也不是,可能Dagger-Android解決了這些問題,可是它自己就比Dagger2更復雜,解決了這些問題,卻引入了其它的問題,Android開發者並不是都是Google開發者,不可能都具有這樣強的邏輯和素質,實踐以後我以爲還不如轉向其它依賴注入的框架。
我只是想注入一下個人ViewModel或Presenter,簡簡單單的開發,有必要這麼麻煩嗎?
固然不是,也許咱們並不須要Dagger-Android,Dagger2自己就能作到。
配合ViewModel組件,咱們根本不須要這麼麻煩,並且也根本不須要再考慮注入到哪裏去,在Component/Activity/Fragment中添加亂七八糟的inject()
方法和@Inject
。
咱們只須要幾個文件就好
經過@Binds
和@IntoMap
@Binds 和 @Provider的做用相差不大,區別在於@Provider須要寫明具體的實現,而@Binds只是告訴Dagger2誰是誰實現的,好比
@Provides
fun provideUserService(retrofit: Retrofit) :UserService =retrofit.create(UserService::class.java)
@Binds
abstract fun bindCodeDetailViewModel(viewModel: CodeDetailViewModel):ViewModel
複製代碼
而@IntoMap則可讓Dagger2將多個元素依賴注入到Map之中。
/** * 頁面描述:ViewModelModule * * Created by ditclear on 2018/8/17. */
@Module
abstract class ViewModelModule{
// ...
@Binds
@IntoMap
@ViewModelKey(CodeDetailViewModel::class)
abstract fun bindCodeDetailViewModel(viewModel: CodeDetailViewModel):ViewModel
@Binds
@IntoMap
@ViewModelKey(MainViewModel::class) //key
abstract fun bindMainViewModel(viewModel: MainViewModel):ViewModel
//...
//提供ViewModel的工廠類
@Binds
abstract fun bindViewModelFactory(factory:APPViewModelFactory): ViewModelProvider.Factory
}
複製代碼
經過這些,Dagger2會根據這些信息自動生成一個關鍵的Map。key爲ViewModel的Class,value則爲提供ViewModel實例的Provider對象,經過provider.get()
方法就能夠獲取到相應的ViewModel對象。
private Map<Class<? extends ViewModel>, Provider<ViewModel>>
getMapOfClassOfAndProviderOfViewModel() {
return MapBuilder.<Class<? extends ViewModel>, Provider<ViewModel>>newMapBuilder(7)
.put(ArticleDetailViewModel.class, (Provider) articleDetailViewModelProvider)
.put(CodeDetailViewModel.class, (Provider) codeDetailViewModelProvider)
.put(MainViewModel.class, (Provider) mainViewModelProvider)
.put(RecentViewModel.class, (Provider) recentViewModelProvider)
.put(LoginViewModel.class, (Provider) loginViewModelProvider)
.put(ArticleListViewModel.class, (Provider) articleListViewModelProvider)
.put(CodeListViewModel.class, (Provider) codeListViewModelProvider)
.build();
}
複製代碼
而這些對象也是由Dagger2幫咱們自動組裝的。
有了這些,咱們就能夠很方便的去構造ViewModel的工廠類APPViewModelFactory
,並構造到所需的ViewModel。
/** * 頁面描述:APPViewModelFactory 提供ViewModel 緩存的實例 * 經過Dagger2將Map直接注入,經過key直接獲取到相應的ViewModel的工廠類,進而new 出所需的ViewModel實例 * Created by ditclear on 2018/8/17. */
class APPViewModelFactory @Inject constructor(private val creators:Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>): ViewModelProvider.Factory{
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
//經過class找到相應ViewModel的Provider
val creator = creators[modelClass]?:creators.entries.firstOrNull{
modelClass.isAssignableFrom(it.key)
}?.value?:throw IllegalArgumentException("unknown model class $modelClass")
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T //經過get()方法獲取到ViewModel
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
複製代碼
當creator.get()時則會去構造新的ViewModel實例
public ArticleDetailViewModel get() {
return provideInstance(repoProvider, userRepoProvider);
}
public static ArticleDetailViewModel provideInstance( Provider<PaoRepository> repoProvider, Provider<UserRepository> userRepoProvider) {
return new ArticleDetailViewModel(repoProvider.get(), userRepoProvider.get());
}
複製代碼
到這裏,ViewModel與Dagger2已經緊密聯繫起來,那如何不去寫那麼多惱人的inject()
呢?
答案就是讓你的Application
持有你的ViewModelProvider.Factory
實例,Talk is Cheap~
class PaoApp : Application() {
@Inject
lateinit var factory: APPViewModelFactory
val appModule by lazy { AppModule(this) }
override fun onCreate() {
super.onCreate()
//...
DaggerAppComponent.builder().appModule(appModule).build().inject(this)
}
}
複製代碼
//基類BaseActivity
abstract class BaseActivity : AppCompatActivity(), Presenter {
//...
val factory:ViewModelProvider.Factory by lazy {
if (application is PaoApp) {
val mainApplication = application as PaoApp
return@lazy mainApplication.factory
}else{
throw IllegalStateException("application is not PaoApp")
}
}
fun <T :ViewModel> getInjectViewModel (c:Class<T>)= ViewModelProviders.of(this,factory).get(c)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//....
initView()
}
abstract fun initView()
//....
}
複製代碼
須要進行注入的Activity,好比ArticleDetailActivity就不須要再寫@inject之類的註解
class ArticleDetailActivity : BaseActivity() {
//很方便就可獲取到ViewModel
private val mViewModel: ArticleDetailViewModel by lazy { getInjectViewModel(ArticleDetailViewModel::class.java) }
override fun initView() {
//調用方法
mViewModel.dosth()
}
}
複製代碼
Fragment相同的道理,具體能夠查看【PaoNet : Master分支】相應的代碼。
咱們能夠和一般的Dagger二、Dagger-Android的原理比較一下
viewmodel = ViewModel(Repo(remote,local,prefrence))
複製代碼
inject(activity)
方法,會生成不少java類,有些繁瑣instance.viewmodel = component.viewmodel
複製代碼
app.map = Map<Class<? extends Activity>, Provider<AndroidInjector.Factory<? extends Activity>>>
activity.viewmodel = app.map.get(activity.class).getComponent().viewmodel
複製代碼
XX_MemberInjectors
的java類,修改時改動最少,純粹的一個依賴檢索容器。app.factory = component.AppViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>>)
viewmodel = ViewModelProviders.of(this,app.factory).get(viewmodel.class)
複製代碼
對比Dagger-Android和Dagger2-ViewModel,二者都是間接經過Map來進行注入,不過一個的key是Class<Activity>
,一個是Class<ViewModel>
,並且都是在Application中inject一下。而Dagger2-ViewModel不須要向Dagger-Android那樣添加AndroidInjection.inject(this)
代碼,更像是一個用來構造ViewModel的依賴管理容器,但對於我或者我但願打造的MVVM結構來講,這便已經足夠了。
《使用Kotlin構建MVVM應用程序系列》 :www.jianshu.com/c/50336d57e…