Android基于MVP的原子性架构

先说下背景,市面上大部分公司在用的MVP MVVM等,google基于 MVP MVVM做了很多扩展架构,在每个项目实际使用中都会有不同的扩展,接下来我会介绍一下基于这些思想的理解并设计的我们目前项目中使用的架构

项目地址:android-mvp-architecture

项目架构图

Picture

实现思路

  • 架构主题服从mvp的思想,在model层做了一些更加细化的划分,model层由各种原子化的usecase组合而成,通过组合的方式使用,以适应复杂的ui需求,在个别耦合性比较强的业务之中,甚至可以考虑将这些usecase进行封装,达到模块化的目的。

  • 整体架构按照mvp和分层的思想设计,四层结构,自顶向下分别是:view视图层、presenter视图控制层、usecase业务层和data repository数据层。下层不依赖上层,以接口的方式为上层提供服务。

分层介绍

  • View视图层

负责用户图形界面的展示,根据下层的数据,驱动界面的显示,比如按钮的状态,控件的显示和隐藏等等。

View是显示数据(model)并且将用户指令(events)传送到presenter以便作用于那些数据的一个接口,通常将Activity或者Fragment作为View层。

  • Presenter 视图控制层

负责将下层拿到的数据结构转化为ui上面要使用的数据,同时根据数据去控制上层ui界面的显示。

Model层也是数据层,负责数据的读取,在我们这里就是一系列的usecase,之所以进行一个细化,是为了满足各种复杂的ui设计,达到代码复用的目的
Presenter 是连接View层与Model层的桥梁并对业务逻辑进行处理,起到一个承上启下的作用。负责从Model层获得所需要的数据,进行一些适当的处理后交由View层进行显示。通过Presenter能很好的将View与Model进行隔离,使得View和Model之间不存在耦合,同时也将业务逻辑从View中抽离,以及测试用例的编写。

  • Usecase 业务层

负责从下层读取数据,根据不同的业务选择相关策略,缓存读取以及远端读取。在原则上

按照最小粒度设计,以便上层能够更好的复用和组合多个业务。
原子性就体现到了这一层上,不同的业务Task可以随意组合,高复用、扩展特性,可以在presenter随意组合使用

  • Data repository 数据层

负责对接网络和数据库的相关逻辑,从远端取数据以及更新数据库缓存。

代码简析

一、 首先看下几个Base类
  • BaseActivity
public class BaseActivity<P extends IRxPresenter> extends AppCompatActivity {
private P mPresenter;

 @Override
     public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPresenter = createPresenter();
     }
protected P createPresenter() {
         return null;
   }
protected P getPresenter() {
    return mPresenter;
 }

@Override
public void onDestroy() {
  super.onDestroy();
  if (mPresenter != null) { 
      mPresenter.destroy();
      }
   }
 }

通过使用泛型,传入一个IRxPresenter 接口,在使用view的时候直接指定对应的Presenter进行强绑定,一个View只会对应一个Presenter的原则,createPresenter()返回对应的presenter,在整个view里面使用的是时候通过getPresenter()来获取对象进行调度,在Activity生命周期走到onDestroy()时候会调用 mPresenter.destroy(),这时候可以在presenter里面的destroy()去cancel Task,这样的好处就是退出activity时候会自动清理掉正在执行还没有回调的任务。

  • BasePresenter:
public class BasePresenter<V extends IRxView> implements IRxPresenter<V> {
private Map<String, Subscription> mSubscriptions = new HashMap<>();
private V mView;

public BasePresenter(V view) {
    mView = view;
}

@Override
public V getView() {
    return mView;
}

@Override
public void addSubscription(Subscription subscription) {
    addSubscription(String.valueOf(subscription.hashCode()), subscription);
}

@Override
public void addSubscription(String tag, Subscription subscription) {
    mSubscriptions.put(tag, subscription);
}

@Override
public void cancelSubscription(String tag) {
    if (mSubscriptions.containsKey(tag)) {
        mSubscriptions.get(tag).unsubscribe();
    }
}

@Override
public void cancelAll() {
    for (Subscription subscription : mSubscriptions.values()) {
        subscription.unsubscribe();
    }
}

@Override
public void destroy() {
    cancelAll();
    mSubscriptions = null;
}

}
在创建presenter的时候会通过泛型指定一个对应的IRxView,这就是上面说的View和Presenter的强绑定,其他几个方法就是简单的封装,方便presenter进行add和cancel的操作。

  • UseCase
public abstract class UseCase<Q extends UseCase.RequestValues, P extends IDataProtocol> {

private boolean mStopAllWithFuture;

private Q mRequestValues;

private UseCaseCallback<P> mUseCaseCallback;

private CompositeSubscription mCompositeSubscription = new CompositeSubscription();

public void setRequestValues(Q requestValues) {
    mRequestValues = requestValues;
}

public Q getRequestValues() {
    return mRequestValues;
}

public UseCaseCallback<P> getUseCaseCallback() {
    return mUseCaseCallback;
}

public void setUseCaseCallback(UseCaseCallback<P> useCaseCallback) {
    mUseCaseCallback = useCaseCallback;
}

void run() {
    Observable<P> task = buildUseCase(mRequestValues);
    if (task == null) {
        return;
    }
    mCompositeSubscription.add(task.compose(new ApplySchedulers<P>())
                   .subscribe(new EntitySubscriber<>(mUseCaseCallback)));
}

protected abstract Observable<P> buildUseCase(Q requestValues);

public final void execute(Q values, UseCase.UseCaseCallback<P> callback) {
    if (mStopAllWithFuture) {
        return;
    }
    setRequestValues(values);
    setUseCaseCallback(callback);
    run();
}

/**
 * Unsubscribes from current {@link Subscription}.
 */
public void cancel() {
    cancel(true);
}

/**
 * Unsubscribes from current {@link Subscription}.
 *
 * @param stopAllWithFuture stop the future tasks.
 */
public void cancel(boolean stopAllWithFuture) {
    mStopAllWithFuture = stopAllWithFuture;
    mCompositeSubscription.clear();
}

/**
 * empty request.
 */
public static class EmptyRequestValues extends BaseRequestValues {

}

/**
 * base request.
 */
public abstract static class BaseRequestValues implements RequestValues {

}

/**
 * Data passed to a request.
 */
public interface RequestValues {

}

public interface UseCaseCallback<R> {

    void onSuccess(R response);

    void onError(int code, String error);
}

}

UseCase这一层的核心类,主要功能是在创建Task的时候将请求实体和返回实体直接通过泛型定义好,当Task在执行execute(Q values, UseCase.UseCaseCallback<P> callback) 方法时就自动进行观察者和订阅者进行绑定并传入回调。

  • RepositoryProvider
public class RepositoryProvider {
public static BankCardRepository getTasksRepository() {
    return BankCardRepository.getInstance(BankCardRemoteDataSource.getInstance(),
                   BankCardLocalDataSource.getInstance());
    }
}

RepositoryProvider这里可以将usecase进行业务层的划分,每一个单例里面包含了这个业务对应的接口,可以看到
BankCardRepository.getInstance(BankCardRemoteDataSource.getInstance(),
BankCardLocalDataSource.getInstance()初始化的时候需要传入local和reomte两个实例

public interface BankCardDataSource {

Observable<BaseListEntity<BankCardEntity>> addBankCard(BankCardEntity task);

Observable<BaseListEntity<BankCardEntity>> deleteBankCard(String taskId);

Observable<BaseListEntity<BankCardEntity>> getBankCardList();

Observable<BaseListEntity<BankCardEntity>> chageBankCardStatus(String taskId);

Observable<BaseListEntity<BankCardEntity>> delteAllBankCard();

Observable<BankCardEntity> getBankCard(String taskId);

}

可以看到BankCardLocalDataSource和BankCardRemoteDataSource分别实现了BankCardDataSource这个接口

public class BankCardRepository implements BankCardDataSource {
private static BankCardRepository INSTANCE;

private BankCardDataSource mRemoteDataSource;
private BankCardDataSource mLocalDataSource;

public BankCardRepository(BankCardDataSource remoteDataSource, BankCardDataSource localDataSource) {
    this.mRemoteDataSource = remoteDataSource;
    this.mLocalDataSource = localDataSource;
}

public static BankCardRepository getInstance(BankCardDataSource remoteDataSource, BankCardDataSource localDataSource) {
    if (INSTANCE == null) {
        BankCardRepository source = new BankCardRepository(remoteDataSource, localDataSource);
        INSTANCE = source;
    }
    return INSTANCE;
}

@Override
public Observable<BaseListEntity<BankCardEntity>> addBankCard(BankCardEntity task) {
    mRemoteDataSource.addBankCard(task).doOnNext(new Action1<BaseListEntity<BankCardEntity>>() {
        @Override
        public void call(BaseListEntity<BankCardEntity> taskEntityBaseListEntity) {
            //TODO: save data to local
            for (BankCardEntity entity : taskEntityBaseListEntity.list()) {
                mLocalDataSource.addBankCard(entity);
            }
        }
    });
    return mLocalDataSource.addBankCard(task);
}

@Override
public Observable<BaseListEntity<BankCardEntity>> deleteBankCard(String taskId) {

    mRemoteDataSource.deleteBankCard(taskId).doOnNext(new Action1<BaseListEntity<BankCardEntity>>() {
        @Override
        public void call(BaseListEntity<BankCardEntity> taskEntityBaseListEntity) {
            //TODO: save data to local
            for (BankCardEntity entity : taskEntityBaseListEntity.list()) {
                mLocalDataSource.addBankCard(entity);
            }
        }
    });
    return mLocalDataSource.deleteBankCard(taskId);
}

@Override
public Observable<BaseListEntity<BankCardEntity>> getBankCardList() {
    mRemoteDataSource.getBankCardList().doOnNext(new Action1<BaseListEntity<BankCardEntity>>() {
        @Override
        public void call(BaseListEntity<BankCardEntity> taskEntityBaseListEntity) {
            //TODO: save data to local
            for (BankCardEntity entity : taskEntityBaseListEntity.list()) {
                mLocalDataSource.addBankCard(entity);
            }
        }
    });
    return mLocalDataSource.getBankCardList();
}

@Override
public Observable<BankCardEntity> getBankCard(String cardId) {
    Observable<BankCardEntity> remote = mRemoteDataSource.getBankCard(cardId).doOnNext(new Action1<BankCardEntity>() {
        @Override
        public void call(BankCardEntity entity) {
            mLocalDataSource.addBankCard(entity);
        }
    });
    Observable<BankCardEntity> local = mLocalDataSource.getBankCard(cardId);
    return Observable.concat(remote, local).first();
}

@Override
public Observable<BaseListEntity<BankCardEntity>> delteAllBankCard() {
           mRemoteDataSource.delteAllBankCard();
    return mLocalDataSource.delteAllBankCard();
}

@Override
public Observable<BaseListEntity<BankCardEntity>> chageBankCardStatus(String cardId) {
          mRemoteDataSource.chageBankCardStatus(cardId);
    return mLocalDataSource.chageBankCardStatus(cardId);
}

}

这样就很清晰了,最终的调用者是在BankCardRepository这个类里面进行处理的,这样一来我们就很容易扩展新的数据源(获取数据的方式),整个架构涉及到的核心类基本介绍完了。
四层架构里面每一次都是面向接口编程,这为以后的扩展带来了极大的好处。

总结

  • 无论是MVP还是MVVM都只是形,不要去被其外表所固定思维,真正用到具体项目要通过自己对整体的理解去扩展属于自己的架构,架构都是需要根据业务形态去演进,目前这种架构应用在我们线上的项目里面。

  • 为了更好的适应业务的发展,在之后的计划之中,app的设计会慢慢倾向于组件化的开发模式,各个模块之间进行解耦,使得代码架构更加清晰,降低项目的维护难度,也便于业务的扩展。


    image
image

+qq群:853967238。获取以上高清技术思维图,以及相关技术的免费视频学习资料

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

推荐阅读更多精彩内容