Android开发经验谈Android技术知识Android开发

Android MVP 多个 Presenter 依赖注入

2019-08-12  本文已影响109人  Android架构师丨小熊

基于上篇(Android MVP 架构 MVP 泛型 Model 的配置)我们讲诉的是一对一的 Presenter 和 Model 的关系,利用反射来实例化我们的 Model 层,从而解放了我们每次创建 Model 时都要去 new 的麻烦事儿。明显,代码的逼格更高了一个档次,但是还不够,我们这篇文章中要解决的是 View 与 Presener 层的一对多的关系,这个很正常,当业务相对比较多的时候,我们的 Presenter 层并不是只有唯一的一个,这种情况就是我们的一个 View 层对应多个 Presenter 的现象。

从我们的前几次版本封装的代码来看,我们在 BaseActivity 基类中是提供了一个抽象方法给它的子类调用,它的返回类型是一个泛型的 Presenter 实现类,而我们 Activity 继承 BaseActivity 的时候,都是直接 new 一个 Presenter 实例的,先来看看代码吧,这样更能够体现这个问题。

BaseActivity 基类,这里我只抽了部分代码

public abstract class BaseActivity<P extends IBasePresenter> extends AppCompatActivity implements IBaseView {
 
    private P mPresenter;
 
    protected abstract void initLayout(@Nullable Bundle savedInstanceState);
 
    protected abstract P setPresenter();
 
    protected abstract void initViews();
 
    protected abstract void initData();
 
 
    @SuppressWarnings("SameParameterValue")
    protected <T extends View> T $(@IdRes int viewId) {
        return findViewById(viewId);
    }

看代码中的 setPresenter() 方法,子类必须传入一个泛型的实现类。所以,子类代码如下:

MainActivity 部分代码:

public class MainActivity extends BaseActivity<MainContract.IMainPresenter> implements MainContract.IMainView {
 
    private TextView tv;
 
    @Override
    protected void initLayout(@Nullable Bundle savedInstanceState) {
        setContentView(R.layout.activity_main);
    }
 
    @Override
    protected void initViews() {
        tv = $(R.id.tv);
    }
 
    @Override
    protected void initData() {
        getPresenter().handlerData();
    }
 
    @Override
    protected MainContract.IMainPresenter setPresenter() {
        return new MainPresenter();
    }

这里返回的是一个实现了 IMainPresenter 接口的实现类,这里为什么能够 new 实现类呢?其实就是 Java 三大特性中的多态。当然,重点不在这,而是我们这个 MainActivity 需要多个 Presenter 怎么办?

第一种,最简单的就是 new 一个你需要的不同的 Presenter 实现类,只要关联上 View 层,都可以使用。这种办法也是在没有其他方法的情况下,勉为其难的接受。

第二种,是使用 Dagger 框架进行依赖注入,那你又没学过怎么办?除了去学习,当然,我们还可以自己写一个依赖注入嘛,依赖注入的方式有很多种,比如:使用接口的方式、构造函数、方法等,还有就是我们的反射的方式。

所以,由于这里的 Presenter 是泛型的类,而不是具体实现类,我们也就只有考虑用反射来获取对象并实例化了。从上篇文章种的动态创建 Model 就是利用的反射的方式,既然学过了,这里应该也不难吧。来吧,直接上代码:

修改 BaseActivity 代码:

package com.test.mvp.mvpdemo.mvp.v5.basemvp;
 
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.IdRes;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
 
import com.test.mvp.mvpdemo.mvp.v5.inject.InjectPresenter;
 
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
 
public abstract class BaseActivity<P extends IBasePresenter> extends AppCompatActivity implements IBaseView {
 
    private P mPresenter;
 
    /**
     * 保存使用注解的 Presenter ,用于解绑
     */
    private List<BasePresenter> mInjectPresenters;
 
    protected abstract void initLayout(@Nullable Bundle savedInstanceState);
 
    protected abstract P setPresenter();
 
    protected abstract void initViews();
 
    protected abstract void initData();
 
 
    @SuppressWarnings("SameParameterValue")
    protected <T extends View> T $(@IdRes int viewId) {
        return findViewById(viewId);
    }
 
    @SuppressWarnings({"unchecked", "TryWithIdenticalCatches"})
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        initLayout(savedInstanceState);
 
        /**
         * 实例化和绑定 P 层
         */
        this.mPresenter = setPresenter();
        if (mPresenter != null) {
            this.mPresenter.attach(this);
        }
 
        mInjectPresenters = new ArrayList<>();
 
        //获得已经申明的变量,包括私有的
        Field[] fields = this.getClass().getDeclaredFields();
        for (Field field : fields) {
            //获取变量上面的注解类型
            InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
            if (injectPresenter != null) {
                try {
                    Class<? extends BasePresenter> type = (Class<? extends BasePresenter>) field.getType();
                    BasePresenter mInjectPresenter = type.newInstance();
                    mInjectPresenter.attach(this);
                    field.setAccessible(true);
                    field.set(this, mInjectPresenter);
                    mInjectPresenters.add(mInjectPresenter);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                }catch (ClassCastException e){
                    e.printStackTrace();
                    throw new RuntimeException("SubClass must extends Class:BasePresenter");
                }
            }
        }
 
        initViews();
        initData();
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        /**
         * 解绑,避免内存泄漏
         */
        this.mPresenter.detach();
        this.mPresenter = null;
        for (BasePresenter presenter : mInjectPresenters) {
            presenter.detach();
        }
        mInjectPresenters.clear();
        mInjectPresenters = null;
    }
 
    @Override
    public Context getContext() {
        return this;
    }
 
    public P getPresenter() {
        return mPresenter;
    }
}

因为 Presenter 的实例化是在 Activity 中进行的,所以我们得从 BaseActivity 中入手。我们只有一个目的:就是实例化不同的 Presenter 实现类。在泛型的类型面前,我们只能做的也就是通过反射来实例化。看看我们的反射部分关键代码:

    //获得已经申明的变量,包括私有的
    Field[] fields = this.getClass().getDeclaredFields();
    for (Field field : fields) {
        //获取变量上面的注解类型
        InjectPresenter injectPresenter = field.getAnnotation(InjectPresenter.class);
        if (injectPresenter != null) {
            try {
                Class<? extends BasePresenter> type = (Class<? extends BasePresenter>) field.getType();
                BasePresenter mInjectPresenter = type.newInstance();
                mInjectPresenter.attach(this);
                field.setAccessible(true);
                field.set(this, mInjectPresenter);
                mInjectPresenters.add(mInjectPresenter);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }catch (ClassCastException e){
                e.printStackTrace();
                throw new RuntimeException("SubClass must extends Class:BasePresenter");
            }
        }
    }

首先,这里的 this 指的就是继承了 BaseActivity 的子类,也就是我们的 MainActivity 类,我们得先获取到 MainActivity 中所有的成员变量,这里要注意的是,必须使用 getDeclaredFields() 方法进行获取,因为有可能某些成员变量会被定义为私有的。

我们先跳过这个注解,下面来说明。接着,我们在循环遍历被这个注解过的变量,然后进行一个类型的强制转换。通过 newInstance 实例话 BasePresenter 对象。因为,这里我们进行了实例化,所以在这必须要和我们的 View 关联起来,保证可以持有 View 的引用。最后的 set 方法,要将 this 和我们反射创建的实例化对象进行赋值。这里有必要解释一下,this 代表当前 MainActivity 的对象,它意味这去调用 setter() 方法将反射创建的 mInjectPresenter 对象赋值给 MainActivity 通过注解的 MainPresenter 对象,所以呢,这里很清楚的解释了,为什么一个注解符号就可以实例化对象的原因,其实它的背后已经做了大量你看不到的工作。

最后,我们去获取那些被 InjectPresenter 注解的变量,这里的 InjectPresenter 是我们自己定义的一个注解,它必须是一个接口类型,看代码:

新建 InjectPresenter 注解类

package com.test.mvp.mvpdemo.mvp.v5.inject;
 
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectPresenter {
}

这里我们使用 @Target 指定它的注解类型为变量,指定它被保留在运行时期。这里的代码呢,没什么难度。然后,我们在 View 层需要引用的 Presenter 实现类变量头上加个 @InjectPresenter 注解就可以了。当然,你也可以添加多个不同 Presenter,这就解决了我们的 View 与 Presenter 一对多的问题,下面来看看代码吧:

修改 MainActivity 类

package com.test.mvp.mvpdemo.mvp.v5.view;
 
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.widget.TextView;
import android.widget.Toast;
 
import com.test.mvp.mvpdemo.R;
import com.test.mvp.mvpdemo.mvp.v5.MainContract;
import com.test.mvp.mvpdemo.mvp.v5.basemvp.BaseActivity;
import com.test.mvp.mvpdemo.mvp.v5.inject.InjectPresenter;
import com.test.mvp.mvpdemo.mvp.v5.presenter.MainPresenter;
 
/**
 * MVP 的写法,Version 5: 依赖注入,解决多个 Presenter 的问题
 *
 * @author 神探丶威威猫
 * @blog https://blog.csdn.net/smile_running
 * @warning 点个赞哦,评个论哦
 */
public class MainActivity extends BaseActivity<MainContract.IMainPresenter> implements MainContract.IMainView {
 
    private TextView tv;
 
    @InjectPresenter
    private MainPresenter mPresenter;
 
    @Override
    protected void initLayout(@Nullable Bundle savedInstanceState) {
        setContentView(R.layout.activity_main);
    }
 
    @Override
    protected void initViews() {
        tv = $(R.id.tv);
    }
 
    @Override
    protected void initData() {
//        getPresenter().handlerData();
        mPresenter.handlerData();
    }
 
    @Override
    protected MainContract.IMainPresenter setPresenter() {
        return null;
    }
 
    @Override
    public void showDialog() {
        ProgressDialog dialog = new ProgressDialog(getContext());
        dialog.show();
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                dialog.dismiss();
            }
        }, 1500);
    }
 
    @Override
    public void succes(String content) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(getContext(), "" + content, Toast.LENGTH_SHORT).show();
                tv.setText(content);
            }
        });
    }
}

从上面的代码中,我们的 MainPresenter 头上加了个 @InjectPresenter 注解,这就意味这它实例化成功了,我们可以尽情的去调它的方法。为了测试正确性,我们把 getPresenter() 给注释了,并且 setPresenter() 方法都没有返回值,说明我们当前用的就是注解的对象。运行一下,可以成功!

这里还存在一个问题放到最后来讲,因为我们在反射的时候要进行关联 View 层,所以在那个时候已经绑定了。既然已经绑定了,那必须要有解绑的操作,否则就会出现问题。因为,这里可能有多个 Presenter 要进行绑定,也有多个 Presenter 进行解绑。所以,就来一个集合存放不同的 Presenter,在反射时绑定并它添加到集合中去,在 View 的 onDestory() 方法中,摧毁视图时进行解绑,这样就不会出现问题了。代码非常简单,就是集合添加和遍历操作。代码已经在上面了,所以这里就不再贴出来了。

解决这多个 Presenter 的问题情况,那么这个框架也变得越来越具有封装性,代码也更加的难以理解了。

最后

最后我准备了一些面试的知识汇总,数据结构,计算机网络等等都有。自己整理和分类的,还请尊重知识产出。
分享给大家的资料包括高级架构技术进阶脑图、Android开发面试专题资料,还有高级进阶架构资料包括但不限于【高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术】希望能帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也是可以分享给身边好友一起学习的!
资料免费领取方式:私信回复“架构资料”即可获取

image
上一篇下一篇

猜你喜欢

热点阅读