一篇文章教你读懂UI绘制流程我的Android重构之旅:框架篇

更有甚者在 BaseActivity 中定义了一切能想得到的子类变量等等,它现在确实成为了“上帝”,方便且无所不能的上帝!

随着项目的发展,它已经庞大到无法继续添加代码了,于是你写了很多很多的帮助类来帮助这个上帝瘦下来:

不经意之间,你已经埋下了黑色炸弹

看起来,业务逻辑被帮助类消化解决了,BaseActivity 中的代码减少了,不再那么“胖”了,帮助类缓解了它的压力,但随着项目的成长,业务的扩大,同时这些帮助类也慢慢变多变大,这时候又要按照业务继续拆分它们,维护成本好像又增加了,那些混乱并且难以复用的程序又回来了,我们的努力好像都白费了。

当然,一部分人会根据不同的业务功能分离出不同的抽象类,但相对那种业务场景下,它们仍是万能的。

无论什么理由这种创造“上帝类”的方式都应该尽量避免,我们不应该把重点放在编写那些大而全的类,而是投入精力去编写那些易于维护和测试的低耦合类,如果可以的话,最好不要让业务逻辑进入纯净的Android世界,这也是我一直努力的目标。

Clean architecture and The Clean rule

这种看起来像“地壳”的环形图就是Clean Architecture,不同颜色的“环”代表了不同的系统结构,它们组成了整个系统,箭头则代表了依赖关系。

我们已经选用 MVP 作为框架开发的架构了,这里就不深入的细说 Clean Architecture 架构了,Clean Architecture 的一些优势我们将揉入框架中,我们在框架的设计时应该遵从以下三个原则:

  • 分层原则
  • 依赖原则
  • 抽象原则

接下来我就分别阐述一下,我对这些原则的理解,以及背后的原因。

分层原则

首先,框架应不去限制应用的具体分层,但是从多人协作开发的角度来说,通常我会将 Android 分为三层:

  • 外层:事件引导层(View)
  • 中间层:接口适配层(一般由 Dagger2 生成)
  • 内层:业务逻辑层

看上面的三层我们很容易的就联想到 MVP 结构,下面我就来说一说这三层所包含的内容。

事件引导层

事引导层,它在框架中作为 View 层的另一展现,它主要负责 View 事件上的走向,例如 onClick、onTouch、onRefresh 等,负责将事件传递至业务逻辑层。

接口适配层

接口适配层的目的是连接业务逻辑与框架特定代码,担任外层与内层之间的桥梁,一般我们使用 Dagger2 进行生成。

业务逻辑层

业务逻辑层是框架中最重要的一部分,我们在这里解决所有业务逻辑,这一层不应该包含事件走向的代码,应该能够独立使用 Espresso 进行测试,也就是说我们的业务逻辑能够被独立测试、开发和维护,这是我们框架架构的主要好处。

依赖规则

依赖规则与 Clean Architecture 箭头方向保持一致,外层”依赖“内层,这里所说的“依赖”并不是指你在gradle中编写的那些 Dependency 语句,应该将它理解成“看到”或者“知道”,外层知道内层,相反内层不知道外层,或者说外层知道内层是如何定义抽象的,而内层却不知道外层是如何实现的。如前所述,内层包含业务逻辑,外层包含实现细节,结合依赖规则就是:业务逻辑既看不到也不知道实现细节

对于项目工程来讲,具体的依赖方式完全取决于你。你可以将他们划入不同的包,通过包结构来管理它们,需要注意的是不要在内部包中使用外部包的代码。使用包来进行管理十分的简单,但同时也暴露了致命的问题,一旦有人不知道依赖规则,就可能写出错误的代码,因为这种管理方式不能阻止人们对依赖规则的破坏,所以我更倾向将他们归纳到不同的 Android module 中,调整 Module 间的依赖关系,使内层代码根本无法知道外层的存在。

抽象原则

所谓”抽象原则”,就是指从具体问题中,提取出具有共性的模式,再使用通用的解决方法加以处理。
例如,在我们开发中往往会碰到切换无网络、无数据界面,我们在框架中定义一个 ViewLayoutState`接口,一方面业务逻辑层可以直接使用它来切换界面,另一方面我们也可以在 View 层实现该接口,来重写切换不同界面的样式,业务逻辑层只是通知接口,它不清楚实现细节,也不用知道是如何实现的,甚至不知道面的载体是一个 Activity 或是一个 View。

这很好演示了如何使用抽象原则,当抽象与依赖结合后,就会发现使用抽象通知的业务逻辑看不到也不知道 ViewLayoutState 的具体实现,这就是我们想要的:业务逻辑不会注意到具体的实现细节,更不知道它何时会改变。抽象原则很好的帮我们做到了这一点。

Build this library

上面介绍了这么多设计准则,现在就来介绍下 Library 的设计,Library 只分为以下三个模块:

  • Instance
  • Util
  • Base

这里是关于我自己的Android 学习,面试文档,视频收集大整理,有兴趣的伙伴们可以看看~

Util、Instance

Util、Instance 本质上的定位都为工具、辅助类,一种为“即用即走”的 static 工具类,例如判断文字是否为空等,一种为“长时间使用”的 instance 形式,例如 Activity 管理栈等。

Base

Base 主要工作是赋予了 BaseActivity 与 BaseFragment 很多不同的能力,上面我们提到了要避免创造“上帝”,但是在项目开发过程中很难避免这种情况,在 Library 中我们将 BaseView 所有能力抽取了出来,BaseActivity 与 BaseFragment 将只负责 View 的展示。

BaseActivity

BaseActivity 主要功能被分为:

  • ActivityMvp 提供上下文
  • ViewResult 提供跨界面刷新
  • ActivityToolbarBase 提供顶部栏
  • ViewLayoutState 提供切换界面
  • LifecycleCallbackStrategy 生命周期回调管理

我们这里可以看到 BaseActivity 实现出的全部能力都与 View 相关,可能这会感到奇怪,不是有实现 ViewResult 跨界面刷新这个业务能力吗?我们来看下它是如何实现的。

/**

  • 全局刷新
    */
    @Override
    public void resultAll() {
    presenter.resultAll();
    }

/**

  • 部分刷新
  • @param resultData
    */
    @Override
    public void result(Map<String, String> resultData) {
    presenter.result(resultData);
    }

这里可以看到,我们委托了 presenter 去实现,保证了 BaseActivity 只存在 View 相关的操作。

BaseListActivity

public abstract class ActivityListBase extends ActivityBase implements ActivityRecyclerMvp {
private RecyclerView rvIndexRecycler = null;
private SmartRefreshLayout srlRefresh = null;
private MultiTypeAdapter adapter = null;
private PresenterListBase presenter = null;

@Override
protected final int getLayout() {
return R.layout.activity_recycler_base;
}

@Override
protected final void onBeforeInit(Bundle savedInstanceState, Intent intent) {
presenter = getPresenter();
presenter.onCreate(savedInstanceState);
}

@Override
protected final void onInitComponent() {
rvIndexRecycler = findViewById(R.id.rv_index_recycler);
srlRefresh = findViewById(R.id.srl_index_refresh);
onInitRecycler();
onInitListComponent();
}

@Override
protected final void onInitViewListener() {
onInitRefresh();
}

@Override
protected final void onLoadHttpData() {
presenter.getData(PresenterListBase.INIT);
}

/**

  • 初始化刷新布局
    */
    protected final void onInitRefresh() {
    srlRefresh.setOnLoadMoreListener(new OnLoadMoreListener() {
    @Override
    public void onLoadMore(RefreshLayout refreshLayout) {
    presenter.getData(PresenterListBase.LOAD_MORE);
    }
    });
    srlRefresh.setOnRefreshListener(new OnRefreshListener() {
    @Override
    public void onRefresh(RefreshLayout refreshLayout) {
    srlRefresh.setEnableLoadMore(true);
    srlRefresh.setNoMoreData(false);
    presenter.getData(PresenterListBase.REFRESH);
    }
    });
    }

/**

  • 初始化Recycler
    */
    protected final void onInitRecycler() {
    RecyclerView.LayoutManager layoutManager = getLayoutManager();
    rvIndexRecycler.setLayoutManager(layoutManager);
    rvIndexRecycler.setHasFixedSize(false);
    adapter = new MultiTypeAdapter(presenter.providerData());
    addRecyclerItem(adapter);
    rvIndexRecycler.setAdapter(adapter);
    }
    }

PresenterViewListImpl

public abstract class PresenterViewListImpl implements PresenterListBase {

protected ActivityRecyclerMvp viewBase = null;
// 布局内容
protected List data = null;
// 布局起点
protected int pageStart = 1;
// 加载更多
protected final int pageSize = PAGE_MAX_SIZE;
// 加载数据类型
protected @LoadDataState
int loadState;

public PresenterViewListImpl(ActivityListBase activityListBase) {
viewBase = activityListBase;
data = new ArrayList<>();
}

@Override
public void onCreate(Bundle savedInstanceState) {

}

@Override
public void result(Map<String, String> resultData) {
RunTimeUtil.runTimeException(“未实现result接口”);
}

@Override
public void resultAll() {
RunTimeUtil.runTimeException(“未实现resultAll接口”);
}

@Override
public void getData(int state) {
loadState = state;
switch (loadState) {
case INIT: {
processPreInitData();
break;
}
case REFRESH: {
pageStart = 1;
break;
}
case LOAD_MORE: {
pageStart = pageStart + 1;
break;
}
}
// 加载网络数据
loadData(new OnLoadDataListener() {
@Override
public void loadDataComplete(T t) {
handleLoadData(loadState, t);
}

@Override
public void loadDataError(@StringRes int errorInfo) {
handleLoadDataError(loadState, errorInfo);
}

@Override
public void loadDataEnd() {
handleLoadDataEnd();
}
});
}

/**

  • 开始加载
    */
    protected final void processPreInitData() {
    pageStart = 1;
    viewBase.switchLoadLayout();
    }

/**

  • 处理加载完成的数据
  • @param loadState
  • @param t
    */
    protected void handleLoadData(int loadState, T t) {
    switch (loadState) {
    case INIT: {
    viewBase.switchContentLayout();
    initView(t);
    break;
    }
    case REFRESH: {
    viewBase.finishRefresh();
    initView(t);
    break;
    }
    case LOAD_MORE: {
    viewBase.finishRefreshLoadMore();
    break;
    }
    }
    }

/**

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

最后

**要想成为高级安卓工程师,必须掌握许多基础的知识。**在工作中,这些原理可以极大的帮助我们理解技术,在面试中,更是可以帮助我们应对大厂面试官的刁难。


,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-8VmpT0wm-1712048985383)]

最后

**要想成为高级安卓工程师,必须掌握许多基础的知识。**在工作中,这些原理可以极大的帮助我们理解技术,在面试中,更是可以帮助我们应对大厂面试官的刁难。


[外链图片转存中…(img-jyYuO5Vu-1712048985384)]

[外链图片转存中…(img-zJeRzaj1-1712048985384)]

本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值