Dagger2 在 Android 项目的正确使用方式【完整篇】
Dagger2的入門難度極大,我們直奔主題,先使用起來 再去思考原理。網上幾乎都是Java的用法,謹慎參考。
當你看到沒有使用dagger.android這個庫的講解,都是Java用的,Android如果那樣用人都會累死的。。
Java用法是Android的基礎,是最原始的用法,Android所有的庫都是對Java用法做了優化,我們先從最基礎的來講。
很多人開發Android都不看Google的文檔,總用Java那套,還真把Android手機當服務器用了,你的App不卡誰的卡??
官網說明,其實Java這種用法在Android端大有問題!查閱官網的Android用法。?如果熟悉dagger2基礎,可以直接跳到后面。
贈送源碼:https://github.com/Pangu-Immortal: 🔥免root實現 Android改機(一鍵新機)技術解密,微信無限多開等。。
《最完整的Android逆向知識體系》
集成 Dagger2 依賴
Android studio 3.2.1 如下Gradle方式引用,Java工程僅需引用如下兩個庫,Android也可以這么用。
// dagger 2.x implementation 'com.google.dagger:dagger:2.17' annotationProcessor 'com.google.dagger:dagger-compiler:2.17'(推薦) 建議使用谷歌為Android提供的 dagger.android 依賴庫,需要引用完整,如下。可查閱Dagger2 在GitHub官網集成方法
// dagger 2.x // implementation 'com.google.dagger:dagger:2.17'// android部分接口也會需要這個庫annotationProcessor 'com.google.dagger:dagger-compiler:2.17'// dagger2針對Android的庫,如果你使用Android的開發方式,僅依賴這三個庫。implementation 'com.google.dagger:dagger-android:2.17'implementation 'com.google.dagger:dagger-android-support:2.17'annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'// 本文前部分demo中使用了dagger 2.x的基本用法,需要引入dagger 2.x的兩個Java庫 // 后半部分dagger只使用Android方式開發,實際的Android開發中你只需要引用Android庫即可。如果你使用的是2.2以下的低版本gradle,可以使用apt的方式引用。(Dagger2 現在都不用 apt 方式了)
最新的AndroidStudio在使用apt插件的時候已經會報warn了,但是還能用,如果你看到集成方式還是apt的,就沒必要看下去了。
apt插件使用Dagger2方法如下(不建議用,已過時):
Project的 build.gradle中添加如下代碼
dependencies {classpath 'com.android.tools.build:gradle:3.2.1'//添加apt插件classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'}App的 build.gradle中添加如下代碼
apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt'... // 網上這么寫的都是Java用法,Android不要使用這種apt方式,會發警告 dependencies {implementation 'com.google.dagger:dagger:2.17'apt 'com.google.dagger:dagger-compiler:2.17'implementation 'org.glassfish:javax.annotation:10.0-b28' }開始使用 Dagger2
從最簡單方式? @Inject 注解開始,看代碼來體會 創建對象的過程。(基礎 僅此而已)
/*** (核心功能):json解析的Javabean類(注意必須public修飾)** @author qihao* @date on 2018/12/18 17:07*/ public class User {private String name;// 這個 @Inject 表示可以提供User類型的對象實例@Injectpublic User() {this.name = "我的名字…User…";}public String getName() {return name;} } public class LoginActivity extends AppCompatActivity {@InjectUser user; // 必須 public class User@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);ButterKnife.bind(this);/**** 添加依賴關系*///第一種方式DaggerLoginComponent.create().inject(this);//第二種方式DaggerLoginComponent.builder().build().inject(this);}@OnClick(R.id.login_button)public void onViewClicked() {Logger.i(user.getName());} } @Component() public interface LoginComponent {/*** 必須讓Component知道需要往哪個類中注入* 這個方法名可以是其它的,但是推薦用inject* 目標類LoginActivity必須精確,不能用它的父類*/void inject(LoginActivity activity); }需要Make Project之后才會在build目錄下生成 DaggerLoginComponent 類,Dagger開頭的一個編譯類。
LoginActivity 中使用?User 中的方法,需要一個注入--Component類,通過 @Component()把雙方關聯。
LoginActivity 中需要初始化 DaggerTestComponent.builder().build().inject(this);
要理解記憶,要理解記憶,要理解記憶……這三個類之間的調用必須先理解了,@Inject注解和@Component()兩處,僅此而已。
現在就用上?Dagger2 了( 配合MVP的結構使用,接下來講,先理解了這個調用過程。)建議新手停留片刻思考一會再往下看。
接下來我們要考慮該如何調用第三方庫中的方法呢??總不能改源碼去加@Inject吧,稍作修改,如下。
使用@Module?@Provides 標簽,稍稍改一處。
(目前不要去想MVP結構,只是演示我們在Activity中調用一個類,不使用?new 的過程,思路簡單些,僅此而已。)
@Component(modules = ActivityModule.class) public interface LoginComponent {void inject(LoginActivity activity); } @Module public class ActivityModule {// 使用Provider 注解 提供實例化對象@ProvidesUser providerUser() {return new User();} }我們只添加了一個Module。此處User() 只是一個普通類,并沒有使用@Inject。
public class User {private String name;public User() {this.name = "我的名字…User…";}public String getName() {return name;} }最初我們 LoginActivity --> 通過 Component類 --> 得到并使用User對象
現在我們 LoginActivity --> 通過 Component類 --> 查找Module中的Provides提供的類?--> 得到并使用User對象
(好多人入門時的困惑是Dagger2比普通寫法代碼量還要大,這是真的,因為他的目的是方便解耦。丟掉疑慮看下去)
Module其實是一個簡單工廠模式,Module里面的函數基本都是創建類實例的方法。
什么時候使用@Provide,比如你使用的第三方庫,或者你引用的類,它的構造方法沒有@Inject時。
Component首先搜索User類中用Inject注解標注的屬性,沒找到,Component就會去Module中查找Provides注解標注的位置,這樣就可以解決第三方類庫用dagger2實現依賴注入了。。
接下來按照真實項目的使用場景,開始一步一步的優化,優化到最后我們會把Dagger2中的注解標簽都用上。最后封裝成完美的MVP開發框架,源碼會提供GitHub地址,請耐心看完全篇。接下來我們繼續優化……
簡單總結:
- @Inject? 主要是用來標注目標類的依賴和依賴的構造函數。
- @Module? ?Module和Provides是為解決第三方類庫而生的。
- @Component??它是一個橋梁,一端是目標類,另一端是目標類所依賴類的實例。
接下來我們再稍微改一點,用@Named玩一玩。。
public interface BasePresenter {void postHttplogin(); } public class HomePresenter implements BasePresenter {@Overridepublic void postHttplogin(){Logger.i("Activity調用了home P層代碼");} } public class LoginPresenter implements BasePresenter {@Overridepublic void postHttplogin(){Logger.i("Activity調用了login P層代碼");} }隨便寫了幾個類,我們可以通過@Named改一個參數就可以調用不同的類,原理如此,怎么玩都行。
@Module public class ActivityModule {// 使用Provider 注解 實例化對象@ProvidesUser providerUser() {return new User();}@Provides@Named("login")BasePresenter getLoginP(){return new LoginPresenter();}@Provides@Named("home")BasePresenter getHomeP(){return new HomePresenter();} } @Component(modules = ActivityModule.class) public interface LoginComponent {void inject(LoginActivity activity); } public class User {private String name;public User() {this.name = "我的名字…User…";}public String getName() {return name;} } public class LoginActivity extends AppCompatActivity {@InjectUser user;@Inject@Named("home")BasePresenter hoemPresenter;@Inject@Named("login")BasePresenter loginPresenter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);ButterKnife.bind(this);DaggerLoginComponent.create().inject(this);}@OnClick(R.id.login_button)public void onViewClicked() {hoemPresenter.postHttplogin();Logger.i(user.getName());loginPresenter.postHttplogin();} }這次貼的代碼有點多,把完整的所有代碼都貼上了,以免有些地方疑惑。。
相信看一遍代碼心里都清楚這個@Named("login")是什么意思了,用法就是利用Module提供不同的對象。
在這其實我想舉很多例子,其實就是利用Module提供對象的過程。。使用場景和思路千變萬化。
classA中存在對classB的依賴該怎么使用呢??如下場景。
public class User {private String name = "我的名字…User…";@InjectWorkInfo workInfo;public String getName() {DaggerLoginComponent.create().inject(this);return name + " <==> " + workInfo.getInfo();}}我們再user中使用 @Inject?WorkInfo workInfo;? 需要注入,否則是個空對象。
注意這一行:DaggerLoginComponent.create().inject(this);
@Component(modules = ActivityModule.class) public interface LoginComponent {void inject(LoginActivity activity);void inject(User user); } public class WorkInfo {private String job = "財務";private int jobYear = 5;private String jobSite = "北京";public String getInfo(){return "工作:"+job+", 工齡:"+jobYear+", 工作地點:"+jobSite;} }LoginActivity依然是打印user.getName(),代碼和之前一樣……
我們只是加了一個Component,通過這個例子可以進一步理解Component的作用,哪里用就要哪里注入。
一個工程幾千幾萬的類,要是全這么用肯定不爽……于是谷歌又發布了dagger.android 依賴庫。。
現在回到頂部依賴處,看看自己是否集成dagger.android 依賴庫,純用Dagger2多累啊。
單利是我們最常用的設計模式,dagger2構建的對象怎么保證單利呢??
@Singleton注解 輕輕松松解決單例。我們稍微改一下user的引用。加個注解即可。
@Module public class ActivityModule {@Singleton@ProvidesUser providerUser() {return new User();} }注意: 第一個坑!!! 如果 module所依賴的Component 中有被單利的對象,那么Conponnent也必須是單利的
@Singleton @Component(modules = ActivityModule.class) public interface LoginComponent {void inject(LoginActivity activity); }接下來我們創建兩個對象打印一下:
public class LoginActivity extends AppCompatActivity {@InjectUser user;@InjectUser user2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);DaggerLoginComponent.create().inject(this);Logger.i(user.toString());Logger.i(user2.toString());}}注意: 第二個坑,單利對象只能在同一個Activity中有效。不同的Activity 持有的對象不同
沒什么辦法么,我就想全局只要一個實例化對象啊?
自定義Scoped就可以解決,實現全局單例模式。
下面是@Singleton的源碼
可以看到定義一個Scope注解,必須添加以下三部分:
- @Scope :注明是Scope?
- @Documented :標記在文檔?
- @Retention(RUNTIME) :運行時級別
Dagger2 Scope 如何實現全局單例,開始純Android用法。
Dagger2的Android庫支持support-v4包,導包時請記得使用support下的Dagger相關類
舉例:import dagger.android.support.DaggerApplication;? 先講完全局單例我們再玩這個類。一點點感受生命周期。
最基礎的引用對象我們已經講完,接下來就是正題,Android如何使用Dagger2,并綁定生命周期,并且按照mvp的模式拆分。
@Scope @Documented @Retention(RUNTIME) public @interface ActivityScoped { } @ActivityScoped @Component(modules = {ActivityModule.class}, dependencies = AppComponent.class) public interface LoginComponent { // 依賴了AppComponent作父類void inject(LoginActivity activity);void inject(HomeActivity activity); } @Singleton @Component(modules = AppModules.class) public interface AppComponent {// 如果不暴露,外面是調不到的。User providerUser(); } @Module public class AppModules {@Singleton@ProvidesUser providerUser() {return new User();} } public class ComponentHolder {private static AppComponent myAppComponent;public static void setAppComponent(AppComponent component) {myAppComponent = component;}public static AppComponent getAppComponent() {return myAppComponent;} } public class MyApp extends Application {@Overridepublic void onCreate() {super.onCreate();inject();}private void inject() {AppComponent appComponent = DaggerAppComponent.builder().appModules(new AppModules(this)).build();ComponentHolder.setAppComponent(appComponent);} } @Module public class ActivityModule extends AppModules {}我們只是對基礎的代碼做了簡單的修改,講一下再繼續寫:(Activity中的代碼沒多少變動)
- 第一,我們的注入類 LoginComponent 繼承了?AppComponent,這個類是在Application中初始化,并且@Singleton標記了單例,綁定了全局,來保證單例,我們使用?Component時就是依賴它,dependencies = AppComponent.class 。
- 第二,我們使用?Component時無法使用@Singleton標記,因為父類已經用過了,再用會報錯。。所以我們自己實現了一個ActivityScoped,和Singleton源碼一樣的,這就是子類權限低于父類的問題。
Modules和我們上面使用單例一樣@Singleton標記,沒變化。
全局單例的區別就在于Component初始化放在了Application中。
我寫了一個AppModules只是留個父類而已,實際開發中我們會有很多的類,總要提取一個基類出來。
public class LoginActivity extends AppCompatActivity {@InjectUser user;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);ButterKnife.bind(this);DaggerLoginComponent.builder().activityModule(new ActivityModule(MyApp.getInstance())).appComponent(ComponentHolder.getAppComponent()).build().inject(this);}@OnClick(R.id.login_button)public void onViewClicked() {Logger.i("login="+user.toString());startActivity(new Intent(this,HomeActivity.class));} } public class HomeActivity extends AppCompatActivity {@InjectUser user;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);DaggerLoginComponent.builder().activityModule(new ActivityModule(MyApp.getInstance())).appComponent(ComponentHolder.getAppComponent()).build().inject(this);Logger.i("home="+user.toString());} }dagger.android的使用,MVP模式的設計。請保證依賴了Android庫。
DaggerLoginComponent.builder().activityModule(new ActivityModule(MyApp.getInstance())).appComponent(ComponentHolder.getAppComponent()).build().inject(this);我們發現了這段代碼會到處都是……而且這段代碼重復率很高。
官網說明,其實Java這種用法在Android端大有問題!查閱官網的Android用法。
- 第一點,代碼重復度過高:我們要在幾乎每一個需要注入依賴項的 Activity 和 Fragment 里面來一套這樣的代碼。這是因為我們在 Android 編程中要用到的 Activity、Fragment 等類是繼承自系統的,同時生命周期都由系統管理,所以使用 Dagger 2 的時候就不得不進行手動的處理,也就只有在純粹自己寫的 Java 類中使用 Dagger 2 才感覺更舒服一些。
- 第二點,違反了控制反轉原則:原有的用法使得被注入的目標類必須要了解注入管理工具的詳細信息,才能讓注入工作順利進行。即使可以通過接口使得實際代碼中不必書寫太多的實際類名,但這仍然造成了嚴重的目標類和管理類的緊耦合。
下面我們看一下Android的正確使用方式,
這次也不循序漸進了,直接上最終的優化版。官網用法很詳細了,我就不從官網的demo一點點講優化了,太累,寫了太多了……
我們盡量簡潔,整體理解,application下初始化,和activity初始化。然后關聯,全局使用。
@Singleton @Component(modules = {AppModule.class,BuildersModule.class,ConfigModule.class,AndroidSupportInjectionModule.class} ) public interface AppComponent extends AndroidInjector<MyApp> {@Component.Builderinterface Builder {@BindsInstanceBuilder application(Application application);AppComponent build();} } @Module public class AppModule {@Singleton@ProvidesContext provideContext(Application application) {return application;} } public class MyApp extends DaggerApplication {@Overrideprotected AndroidInjector<? extends DaggerApplication> applicationInjector() {return DaggerAppComponent.builder().application(this).build();} } @Module public abstract class BuildersModule {@ActivityScope@ContributesAndroidInjectorabstract LoginActivity loginActivityInject();@ActivityScope@ContributesAndroidInjectorabstract SplashActivity splashActivityInject(); } @Module public abstract class ConfigModule {@Provides@Singletonstatic Student provideStudent() {return new Student();} } @Documented @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } public class LoginActivity extends BaseActivity implements MainContract.View {@InjectStudent student;@InjectLoginPresenter presenter;@BindView(R.id.login_button)Button login;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);ButterKnife.bind(this);}@OnClick(R.id.login_button)public void onViewClicked() {Logger.i("student:"+student.getName());Logger.i("對象:"+student.toString());presenter.requestHttp();startActivity(new Intent(this, SplashActivity.class));}public void showMessage(String message) {Toast.makeText(this, message, Toast.LENGTH_SHORT).show();}} public class SplashActivity extends BaseActivity {@InjectStudent student;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_splash);Logger.i("對象2:"+student.toString());} } public class BaseActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {AndroidInjection.inject(this);super.onCreate(savedInstanceState);} } public interface MainContract {interface View{}interface Presenter {}interface Model {} } public class MainModel implements MainContract.Model {@Injectpublic MainModel() {}public String returnMessage() {return "qi_hao";} } public class LoginPresenter extends BasePresenter implements MainContract.Presenter {private final LoginActivity view;private final MainModel model;@Injectpublic LoginPresenter(LoginActivity view, MainModel model) {this.view = view;this.model = model;}public void requestHttp() {view.showMessage(model.returnMessage());} }App--> AppCompat --->AppModule-->調用類
Activity--> ActCompat --->ActModule-->調用類
拆開看其實就這幾條思路,在看代碼的時候尋找就可以了,我們說一下Android的正確用法的注意點。。
先回顧前面的基礎,現在全局沒有了那么多ActCompat,現在只有一個Compat類,所有的Module都關聯到這個Compat類。
到這一步資料就很多了,看一眼代碼基本上也能理解。
注意點:Activity不可以繼承DaggerActivity,看源碼可知,必須使用源碼中的Fragment才可以繼承此類。
所以老老實實在BaseActivity中寫 AndroidInjection.inject(this); 就一行初始化而已,也不麻煩。。。
Fragment可以繼承DaggerFragment。
Application可以繼承DaggerApplication。
如果使用V4 V7包的Fragment或者Activity,就要導入support包下的類,例如:?dagger.android.support.DaggerApplication;
源碼下載GitHub:已簡單封裝了MVP,會持續不斷的完善為便捷開發框架。
贈送源碼:https://github.com/Pangu-Immortal?!蹲钔暾腁ndroid逆向知識體系》
總結
以上是生活随笔為你收集整理的Dagger2 在 Android 项目的正确使用方式【完整篇】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Xposed是如何为所欲为的?
- 下一篇: Android Studio使用Goog