android:mvp+dagger2解析

来源:互联网 发布:nginx rewrite by lua 编辑:程序博客网 时间:2024/06/14 12:39
这篇文章我们通过一个小案例,完成MVP+daggger2的结合使用。

1.从MVC到MVP

MVC模式:

View:对应于布局文件 (layout)
Model:业务逻辑和实体模型
Controllor:对应于Activity (fragment)

关于MVC模式:有这么一句话:

Most of the modern Android applications just use View-Model architecture,everything is connected with Activity.

在MVC模式中:Activity中做了很多界面相关的工作,如findViewById,获取界面上的信息,弹一个土司等;同时也做了大量的业务逻辑处理。因此导致,Activity既像View又必须担任Controller。其实Activity比较适合归纳到View层。只做与界面相关的操作。

MVP框架就是从经典的MVC框架演变过来的,基本思路相同。
MVP就是 Model(模型),View(视图),Presenter(控制器)。其中M和V的功能和MVC一样,Presenter相当于MVC的Controler,是M层和V层的桥梁,负责所有的业务逻辑处理。在Android中MVP框架Activity担当View视图层,MVC框架中Activity担任控制器。这就是一个重要的区别。

MVP核心流程(觉得不好理解可以先跳过,直接看图和下面的解释):

在MVP框架模型中,View和Model并不直接交互。而是通过Presenter作为View和Model的桥梁。其中Presenter中同时持有View层和Model层的Interface的引用,而View层持有Presenter层的引用。当View层某个界面需要展示某些数据的时候,首先会调用Presenter层的某个接口,然后Presenter层会调用Model层请求数据,当Model层数据加载成功后会调用Presenter的回调方法通知Presenter层数据加载完毕,最后Presenter层再调用View层的接口将加载的数据展示给用户。这就MVP框架的的整个核心过程。

这里写图片描述

从图中可以看到:MVP框架模式完全将Model和View分离,这两者之间间隔着的是Presenter层,其负责调控 View与Model之间的间接交互。对于这个图理解即可而不必限于其中的条条框框,毕竟在不同的场景下多少会有些出入的。在 Android中很重要的一点就是对UI的操作基本上需要异步进行,也就是在MainThread中才能操作UI,所以对View与Model的切断分离是合理的。此外Presenter与View、Model的交互使用接口定义交互操作可以进一步达到松耦合。

如上,MVP模式中,V层和P层的耦合度却很高。比如,我们处理一个登陆功能,Activity中只负责与界面相关的操作,判断登陆的逻辑放到P层中,登陆结果的处理放在Activity中。那么我们就要在Activity中创建P层某个类的对象来完成登陆判断。当P层返回登陆的结果时,还要在P层拿到Activity,用Activity来完成登陆结果的界面处理。这样会发现,V层和M层的耦合度极高。而且,如果多个界面需要这个逻辑操作,那么多个界面都要去创建P层某个类的对象,更加复杂。那么我们如何解MVP中V层和P层的耦合度呢?

android中提供的解耦方式有很多:
1.利用配置文件,使用反射获取到需要加载的对象。例如:我们AndroidManifest.xml中系统是怎么找到我们的程序入口Activity的,利用的就是这个原理。
2.设计模式:单例,工厂,观察者…..
而我们要推荐使用的是dagger2。

2.mvp+Dagger2小案例

1、什么是Dagger2?

Dagger是为Android和Java平台提供的在编译时进行依赖注入的框架。

这里的编译时很重要,我们知道安卓里面还有一个运行时,反射就是在运行时生效。也就是说,使用dagger2,我们在编辑时生成代码(rebulid),就能完成所需对象的注入。这也是为什么谷歌不建议开发者在开发中使用反射。

2、为什么使用Dagger2?

如上所说,Dagger2解决了基于反射带来的开发和性能上的问题。

3、dagger2的作用?

在MVP框架中,Dagger2主要用于做界面和业务之间的隔离,即解耦!

4、dagger2的引入配置

1、添加dagger2的依赖在module的gradle中添加

    dependencies {        compile 'com.google.dagger:dagger:2.6'    }

2、编译时生成代码的插件配置(android-apt)

a ) project的gradle中添加

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

b ) module的gradle中添加

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

c ) 关联Dagger2在module的gradle中添加

dependencies {    apt 'com.google.dagger:dagger-compiler:2.6'}

我们添加了编译和运行库,还有必不可少的apt插件,没有这个插件,dagger可能不会正常工作,特别是在Android studio中。

5.dagger2注解(都是概念,可以跳过直接看案例,看完再看这个):

要学习Dagger2,就必须要知道下面这些注解和这其中的每一个概念:

@Inject: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。

@Module: Modules类里面的方法用来专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的依赖。

@Provide: 在Modules中可以定义很多方法,只有使用了这个注解,Dagger才知道这个方法是提供依赖类的。

@Component: Components可以说是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。 如果没有以@Components注解的接口,dagger是无法找到相应的@module类的,也就无法实现@Inject对象注入。

@Scope: Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域,即可以限定依赖类实例的生命周期,比如有的跟activity生命周期一样,有的是app生命周期等等。

@Qualifier: 当两个依赖类型一样,无法鉴别他们的时候,我们就可以使用这个注解标示。相当如给依赖打上不同的“tag”,例如:在Android中,我们会需要不同类型的context,所以我们就可以定义 @qualifier注解“@ForApplication”和“@ForActivity”,这样当注入一个context的时候,我们就可以告诉 Dagger我们想要哪种类型的context。

6.案例:在MVP框架中,使用dagger2

在这个小项目中,我们将把常规代码改为MVP模式,并引入dagger2进行解耦!

如图,我在项目中新建一个module,取名为mvp,做一个简单的模拟登录功能,先看看在我们常规项目代码的样子。

MainActivity的布局如下:

    <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/activity_main"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:gravity="center"    android:orientation="vertical">    <EditText        android:background="@null"        android:id="@+id/username"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginLeft="20dp"        android:layout_marginRight="20dp"        android:drawableBottom="@drawable/shape_et_bottom_line"        android:drawableLeft="@mipmap/ic_launcher"        android:hint=":请输入用户名" />    <EditText        android:background="@null"        android:id="@+id/password"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginLeft="20dp"        android:layout_marginRight="20dp"        android:drawableBottom="@drawable/shape_et_bottom_line"        android:drawableLeft="@mipmap/ic_launcher"        android:hint=":请输入密码" />    <Button        android:layout_marginTop="20dp"        android:layout_width="wrap_content"        android:layout_height="40dp"        android:background="#0094ff"        android:onClick="login"        android:text="login"        android:textColor="#fff" /></LinearLayout>

shape :

<shape xmlns:android="http://schemas.android.com/apk/res/android"    android:shape="rectangle">    <solid android:color="#1E7EE3" />    <size        android:width="500dp"        android:height="1dp" /></shape>

界面效果很简单:

这里写图片描述

MainActivity代码如下:

public class MainActivity extends AppCompatActivity {    @InjectView(R.id.username)    EditText userName;    @InjectView(R.id.password)    EditText passWord;    private ProgressDialog dialog;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.inject(this);        dialog = new ProgressDialog(this);    }    public void login(View view) {        String username = userName.getText().toString();        String password = passWord.getText().toString();        boolean checkUserInfo = checkUserInfo(username, password);//检查用户输入是否为空        if (checkUserInfo) {            dialog.show();            final User user = new User();            user.username = username;            user.password = password;            new Thread(new Runnable() {                @Override                public void run() {                    UserLoginNet net = new UserLoginNet();                    if (net.loginSuccessOrNot(user)) {                        //登录成功                        success();                    }else{                        //登录失败                        failed();                    }                }            }).start();        } else {            Toast.makeText(MainActivity.this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show();        }    }    private boolean checkUserInfo(String username, String password) {        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {            return false;        }        return true;    }    public void success() {        runOnUiThread(new Runnable() {            @Override            public void run() {                // 登陆成功                dialog.dismiss();                Toast.makeText(MainActivity.this, "欢迎回来:" + userName.getText().toString(), Toast.LENGTH_SHORT).show();            }        });    }    public void failed() {        runOnUiThread(new Runnable() {            @Override            public void run() {                // 登陆失败                dialog.dismiss();                Toast.makeText(MainActivity.this, "用户名或密码输入有误", Toast.LENGTH_SHORT).show();            }        });    }}

UserLoginNet代码如下:

/** * 模拟登录 */public class UserLoginNet {    public UserLoginNet() {    }    public boolean loginSuccessOrNot(User user) {        SystemClock.sleep(2000);        if ("lmy".equals(user.username) && "lmy".equals(user.password)) {            return true;        } else {            return false;        }    }}

运行,点击登录,账户密码输入“lmy”,会弹出吐司“欢迎回来:lmy”!这些代码很简单。但是有没有发现一些问题,在我们的MainActivity里面除了跟界面相关的,还有很多业务逻辑处理的代码。如:

这里写图片描述

不难看出,这一段代码与MainActivity界面是毫无关系的,而其它代码都与界面控件有直接联系的,根据MVP设计模式的思想,业务逻辑的这些代码应该放到Presenter层去。那我们来改进一下把。新建一个package:presenter。在里面新建一个类:MainActivityPresenter,专门处理MainActivity中的业务逻辑。把上面这段代码放进去,抽取为一个方法。

代码如下:

这里写图片描述

然后在MainActivity中登录的时候,创建MainActivityPresenter对象,调用其login()即可。
但是问题来了,这里发现代码报红了,因为success()和failed()都是与界面紧密相关的方法,放在MainActivity中了。我们常规的处理方法是,通过构造拿到MainActivity,这种暴力方法直接提高了代码的耦合度。如下:

public class MainActivityPresenter {    private MainActivity mainActivity;    public MainActivityPresenter(MainActivity mainActivity) {        this.mainActivity = mainActivity;    }    public void login(String username,String password){        final User user = new User();        user.username = username;        user.password = password;        new Thread(new Runnable() {            @Override            public void run() {                UserLoginNet net = new UserLoginNet();                if (net.loginSuccessOrNot(user)) {                    //登录成功                    mainActivity.success();                }else{                    //登录失败                    mainActivity.failed();                }            }        }).start();    }}

然后在MainActivity中:我们当然会这么写:

 //P层的对象 private MainActivityPresenter presenter; @Override protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);     ···     //创建P层的对象,一旦new出来,两层之间就耦合到一起了     presenter = new MainActivityPresenter(this); }public void login(View view) {    ···    if (checkUserInfo) {        dialog.show();        //调用P层的方法处理业务逻辑        presenter.login(username,password);    } else {       ···    }}

这样,实现了MVP模式的基本原则,MainActivity现在是一个非常标准的View层,业务逻辑在Presenter层。但同时,代码的耦合度很高,如我们很多界面要用到同一种逻辑,那么多个界面都去new这个Presenter,问题会更严重。所以,MVP模式的下一步工作,就是要实现解耦!就要用到我们的dagger2了。

简单说:dagger2能帮助我们实现:目标类中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建,而是通过技术手段可以把其他的类的已经初始化好的实例自动注入到目标类中。

7.使用dagger2解耦!

首先把dagger2的配置做好。

dagger2的操作步骤可划分为五步,这里会用到很多注解。

第一步:在MainActivity中,通过@Inject指定需要注入的目标:MainActivityPresenter
public class MainActivity extends AppCompatActivity {    //第一步:    @Inject    MainActivityPresenter presenter;
第二步:将 new MainActivityPresenter(activity)这个代码放到指定类的指定方法中

我们先创建一个package:dagger2,把所有跟dagger2相关的东西放在这个包里面。然后在这个包里面再创建一个package:module。

在这里面创建一个类:MainActivityModule ;

@Modulepublic class MainActivityModule {    MainActivity mainActivity;    public MainActivityModule(MainActivity mainActivity) {        this.mainActivity = mainActivity;    }    @Provides    MainActivityPresenter provideMainActivityPresenter() {        return new MainActivityPresenter(mainActivity);    }}

其中,@Module这个注解指明了哪个类,@Provides指明了是哪个方法。这里很重要,不要写掉了注解。

以上,第一步让我们持有了MainActivityPresenter 的引用,第二步通过一个module帮我们构建了一个MainActivityPresenter 。现在要做的就是,如何让它们之间划上等号。如果能划上等号,就等于我们成功的拿到了MainActivityPresenter 。

第三步:建立MainActivity与MainActivityModule 之间的关系:in(MainActivity activity)

首先,在dagger2包下面再建一个component包,在里面创建一个interface:MainActivityComponent。

现在我们的项目目录结构如下:

这里写图片描述

MainActivityComponent代码:

    /**     * 通过接口将创建实例的代码和目标关联在一起     * Created by lmy on 2017/5/3 0003.     */    @Component(modules= MainActivityModule.class)    public interface MainActivityComponent {        void in(MainActivity activity);    }

通过@Component(modules= MainActivityModule.class)进行关联。
这个接口里面就一个in()方法。

回头看一下,我们不过是为了这行代码:

 presenter = new MainActivityPresenter(this);

第一步,我们通过@inject拿到=号左边的presenter ,第二步,我们通过@Module 和 @provides两个注解实现了=号右边的对象创建。第三步,通过@Component注解,实现了=号的连接。

第四步:点击Build-ReBuild Project完成编译时注入。

我们会发现,在下面这个目录生成了一些文件:

这里写图片描述

重点就是component包下面的DaggerMainActivityComponent.class。

第五步:在MainActivity中调用如下方法:

MainActivity代码现在是这样了,其实改动的很小,主要还是这五个步骤的使用熟练度问题。

public class MainActivity extends AppCompatActivity {    ···    //第一步:    @Inject    MainActivityPresenter presenter;    @Override    protected void onCreate(Bundle savedInstanceState) {        ···        //再不需要new对象了        //presenter = new MainActivityPresenter(this);        //第五步        DaggerMainActivityComponent component= (DaggerMainActivityComponent) DaggerMainActivityComponent.builder()                .mainActivityModule(new MainActivityModule(this))                .build();        component.in(this);    }    public void login(View view) {        ···        if (checkUserInfo) {            dialog.show();            //现在可以直接使用presenter了            presenter.login(username, password);        } else {           ···        }

···

现在,我们就可以使用第一步中的presenter了。而代码中再也不会出现new对象的代码了。

登录测试一下即可。

在操作中会使用到了@Inject、@Module、@Provides、@Conponent注解,那么他们分别在完成什么工作?

MainActivityPresenter presenter = new MainActivityPresenter(this);
@Inject @Conponent @Module @Provides

这四个注解可以这么理解,@Inject承担了=号左边的工作,@Conponent 承担了=号的工作, @Module @Provides承担了=号右边的工作。

以上,就是android中MVP设计模式结合dagger2进行解耦的一个基本方法了。如有不足,还请留言指正,谢谢!

2 0
原创粉丝点击