Dagger2实战(详细)

提前准备

如果你对Dagger2一点基础都没有,建议你先看看第一篇:Dagger2入门详解

如果想直接看代码,可以 到Github上 Clone一下:源码地址

参考文章

Dependency Injection with Dagger 2

史上最通俗易懂的Android中使用Dagger入门教程

都是套路——Dagger2没有想象的那么难

环境配置

project: build.gradle

dependencies {
        //...
        
        //dagger2
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }

app: build.gradle

//...

//dagger2
apply plugin: 'com.neenbedankt.android-apt'

android {
    //...
}

dependencies {
    //...

    //dagger2
    apt 'com.google.dagger:dagger-compiler:2.7'
    compile 'com.google.dagger:dagger:2.7'
    provided 'javax.annotation:jsr250-api:1.0'
}

开始撸代码

首先我们要不落俗套的借用一张图:

不落俗套的一张图.png

来解释一下这样图,通俗的理解,我们知道 Dagger2 中一个很重要的概念就是 Scope 生命周期,这里的 component(容器) 的框框可以看成一个容器,并且每个component 一般都拥有自己的 Scope, module 可以看成生产物品并放入 component中的工厂(虽然它不叫 factory)。并且框框里面的component 可以使用外层 component 中的产品。

结合图:ApplicationComponent 是一个容器,它的 ApplicationModule 负责生产一些产品。里层的 ActivityComponent 在 ApplicationComponent中,并享有 ApplicationComponent中的所有产品,并且自己也有 ActivityModule 可以生产自己的产品。 然后再里面就是 UserComponent,它拥有ApplicationComponent和ActivityComponent中的所有产品,并自己也有 Module。这里的产品,就是我们可以用@Inject 注入的东西

其中有两个生命周期 @Singleton 和 @PerActivity(自定义),@Singleton并不是我们设计模式中的单例模式,而是Dagger2中为了保持一个产品在一个Scope中只有一个对象的标签。@PerActivity 也一样,表示在 @PerActivity 这个生命周期中,只包含一个这样产品的标签。

Dagger2 本来只是一个依赖注入框架,再简单不过了。但是非要搞出这么复杂的结构是为了什么?其实这是根据Android开发的特点来的。Fragment 依赖 Activity 依赖 Application 其实本来就有这样的结构,如果你按照这种思想来理解,会容易很多。

好了,上代码

首先建立最大的 AppComponent

AppComponent.java

@Singleton
@Component(modules = {AppModule.class})
public interface AppComponent {

    Context getContext();

    ToastUtil getToastUtil();
}

注意这里的 再 Component中给出的 ToastUtil 是提供给依赖于 AppComponent 的所有 Component的。这里表示所有依赖它的结构都可以使用 toast 功能。

AppModule.java

@Module
public class AppModule {

    private Context context;

    public AppModule(Context context) {
        this.context = context;
    }

    @Singleton
    @Provides
    public Context provideContext() {
        return this.context;
    }


    @Singleton
    @Provides
    public ToastUtil provideToastUtil(Context context) {
        return new ToastUtil(context);
    }
}

Module中由 @Provides 修饰的表示我这个Component中能提供的产品(供外界注入)。

ToastUtil.java
很简单

public class ToastUtil {

    private Context context;

    public ToastUtil(Context context) {
        this.context = context;
    }

    public void showToast(String message) {
        Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
    }
}

自定义 App,并将 AppComponent 初始化:
App.java

public class App extends Application {

    private AppComponent appComponent;

    @Override
    public void onCreate() {
        super.onCreate();

        appComponent = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .build();

    }

    public AppComponent getAppComponent() {
        return this.appComponent;
    }
}

别忘了修改 AndroidManifest.xml 文件 <application android:name=".App"> ...</application>

以上就是最大的生命周期 App,而且内容都是 @Singleton 的。然后我们来写 Acitivyt的。

严格按照图上的来写:

写一个抽象的 ActivityComponent

ActivityComponent.java

@PerActivity
@Component(modules = {ActivityModule.class}, dependencies = {AppComponent.class})
public interface ActivityComponent {

    Activity getActivity();

    ToastUtil getToastUtil();
}

ActivityModule.java

@Module
public class ActivityModule {

    private final Activity activity;

    public ActivityModule(Activity activity) {
        this.activity = activity;
    }

    @PerActivity
    @Provides
    public Activity provideActivity() {
        return this.activity;
    }
}

这里注意两点:

  1. ActivityComponent 中 还需要重写一次 ToastUtil getToastUtil();, 上面我们提到,Component中写的是 可提供给依赖自己的Component的东西,并且不能直接继承自 AppComponent。这里只需要提供接口,然后它自己会找到AppComponent中并获得ToastUtil
  2. @PerActivity,如果使用了 dependencies,那么依赖的一方的 Scope 不能和 父级 相同,其实@PerActivity的代码和 @Singleton 是一样的,只是需要我们自己重新定义一下而已。

PerActivity.java

@Scope
@Documented
@Retention(RUNTIME)
public @interface PerActivity {
}

然后写一个 BaseActivity ,它的主要作用其实就是提供 ActivityComponent,因为继承自它的Activity都需要这个容器。

BaseActivity.java

public class BaseActivity extends AppCompatActivity {

    private ActivityComponent activityComponent;

    public ActivityComponent getActivityComponent() {
        return activityComponent;
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        activityComponent = DaggerActivityComponent.builder()
                .appComponent(((App) getApplication()).getAppComponent())
                .activityModule(new ActivityModule(this))
                .build();
    }
}

上面完成了 AppComponent 和 一个抽象的 BaseActivity,为什么叫它抽象,因为你不会用它来实例化一个Activity吧,它只是为了给所有 具体的Activity模块提供内容。当然,还有一种写法,就是不用BaseActivity这个东西,直接让具体的每个Activity和Application发生关系。至于怎么写,我们放在后面吧!

具体的MainComponent

MainComponent.java

@MainActivityScope
@Component(dependencies = {ActivityComponent.class}, modules = {MainModule.class})
public interface MainComponent {

    void inject(MainActivity mainActivity);

    MainFragmentComponent mainFragmentComponent();
}

MainModule.java

@Module
public class MainModule {

    @Provides
    public UserRepository provideUserRepository() {
        return new UserRepository();
    }
}

MainFragment.java

@MainActivityScope
@Subcomponent
public interface MainFragmentComponent {

    void inject(MainFragment mainFragment);
}

这里注意几点:

  1. void inject(MainActivity mainActivity);void inject(MainFragment mainFragment); 因为要和具体的依赖组件发生关联,所以添加了注入接口。
  2. 关于 @Subcomponent 的用法,它的作用和 dependencys 相似,这里表示 FragmentComponent 是一个子组件,那么它的父组件是谁呢? 提供了获取 MainFragmentComponent 的组件,如 MainComponent中的 MainFragmentComponent mainFragmentComponent();,是这样做的关联。
  3. MainModule中提供了 UserRepository,表示要给数据仓库,这里只是模拟数据。

UserRepository .java

public class UserRepository  {

    public UserRepository() {
    }

    public User getUser() {
        User user = new User();
        user.name = "yxm";
        return user;
    }
}

User.java

public class User {
    public String name;
}
  1. 自定义的 Scope,和前面方法一样,直接copy @Singleton 注解中的代码
    MainActivityScope.java
@Scope
@Documented
@Retention(RUNTIME)
public @interface MainActivityScope {
}

MainActivity和MainFragment怎么注入

MainActivity.java

public class MainActivity extends BaseActivity {

    private MainComponent mainComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mainComponent = DaggerMainComponent.builder()
                .activityComponent(getActivityComponent())
                .mainModule(new MainModule())
                .build();
        mainComponent.inject(this);
    }

    public MainComponent getMainComponent() {
        return this.mainComponent;
    }
}

还是最流行的MVP模式:

MainFragmentContact.java

public class MainFragmentContact {
    public interface View {
        void setUserName(String name);

        void showToast(String msg);
    }

    public static class Presenter {
        public UserRepository userRepository;

        @Inject
        public Presenter(UserRepository repository) {
            this.userRepository = repository;
        }

        private View view;

        public void setView(View view) {
            this.view = view;
        }

        public void toastButtonClick() {
            String msg = "hello world";
            view.showToast(msg);
        }

        public void userInfoButtonClick() {
            User userData = this.userRepository.getUser();
            this.view.setUserName((userData.name));
        }
    }
}

然后是Fragment

MainFragment.java

public class MainFragment extends Fragment implements MainFragmentContact.View {

    @Inject
    MainFragmentContact.Presenter mainPresenter;
    @Inject
    ToastUtil toastUtil;

    private MainFragmentComponent mainFragmentComponent;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        if (getActivity() instanceof MainActivity) {
            mainFragmentComponent = ((MainActivity) getActivity()).getMainComponent().mainFragmentComponent();
            mainFragmentComponent.inject(this);

        }
        mainPresenter.setView(this);
    }

    @Nullable
    @Override
    public android.view.View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        android.view.View view = inflater.inflate(R.layout.fragment_main, container, false);
        return view;
    }

    @Override
    public void onViewCreated(android.view.View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Button btnToast = (Button) view.findViewById(R.id.btn_toast);
        btnToast.setOnClickListener(new android.view.View.OnClickListener() {
            @Override
            public void onClick(android.view.View view) {
                mainPresenter.toastButtonClick();
            }
        });

        Button btnUserData = (Button) view.findViewById(R.id.btn_user_info);
        btnUserData.setOnClickListener(new android.view.View.OnClickListener() {
            @Override
            public void onClick(android.view.View view) {
                mainPresenter.userInfoButtonClick();
            }
        });
    }

    @Override
    public void setUserName(String name) {
        ((TextView) getView().findViewById(R.id.et_user)).setText(name);
    }

    @Override
    public void showToast(String msg) {
        toastUtil.showToast(msg);
    }
}

代码有点长,注意几点:

  1. 与MainComponent关联上
if (getActivity() instanceof MainActivity) {
    mainFragmentComponent = ((MainActivity) getActivity()).getMainComponent().mainFragmentComponent();
    mainFragmentComponent.inject(this);
}
  1. 内部类注入时,必须使用 static 的,如上的 MainContact 中的 Presenter

  2. 其他的就是MVP方面的知识了,如果不懂,就自己看看呗。然后还有 layout文件,我不信你不会写,哈哈哈

另一种写法

刚刚我们提到,还可以让Activity直接和Application发生关系,怎么写呢?

  1. ActivityComponent 不依赖 AppComponent,所以也不能再提供ToastUtil

ActivityComponent.java

@PerActivity
@Component(modules = {ActivityModule.class})
public interface ActivityComponent {
    Activity getActivity();
}
  1. MainComponent直接依赖Appcomponent

MainComponent.java

@MainActivityScope
@Component(dependencies = {AppComponent.class}, modules = {MainModule.class, ActivityModule.class})
public interface MainComponent {

    void inject(MainActivity mainActivity);

    MainFragmentComponent mainFragmentComponent();
}
  1. BaseActivity中提供 AppComponent的引用,因为所有要注入AppComponent的Activity都需要这个,所以 写在BaseActivity中,同时 ActivityComponent也不需要了。

AppCompatActivity.java

public class BaseActivity extends AppCompatActivity {

    public AppComponent getAppComponent() {
        return ((App) getApplication()).getAppComponent();
    }
}
  1. 注入的地方,添加 AppComponent,同时添加 ActivityModule和MainModule

BaseActivity.java

public class MainActivity extends BaseActivity {

    private MainComponent mainComponent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mainComponent = DaggerMainComponent.builder()
                .appComponent(getAppComponent())
                .activityModule(new ActivityModule(this))
                .mainModule(new MainModule())
                .build();
        mainComponent.inject(this);
    }

    public MainComponent getMainComponent() {
        return this.mainComponent;
    }
}

好了,其他代码不用动,直接可以运行

总结

如果看完并理解了这篇,你肯定能更加理解Dagger2的设计思想。这种分层的结构,可以让我们的项目结构化更加清晰。不要看着上面的代码很复杂,实现的内容又有限。其实,这个架子搭好了,以后往里面加东西就方便了。

现在就可以在各个层级添加自己想要注入的对象的 provideXXX 方法,然后在Activity或者Fragment中直接注入,十分方便,你可以自己体会体会。

当前我们注入的数据源是实体类,其实完全可以注入一个interface,例如 UserFromNet 和 UserFromLocal 表示来自网络和本地的数据源,通过注解来更换数据源。这也是面相抽象编程的思想。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容