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

本文探讨了在Android开发中如何遵循CleanArchitecture的原则,提倡MVP架构,强调避免创建过于复杂的上帝类,并提出了分层、依赖和抽象的设计准则,以提高代码可维护性和测试性。
摘要由CSDN通过智能技术生成

另外,在软件工程领域,始终都有一些值得我们学习和遵守的原则,比如:单一职责原则依赖倒置原则避免副作用等等。 Android Framework 不会强制我们遵守这些原则,或者说它对我们没有任何限制,试想那些耦合紧密的实现类,处理大量业务逻辑的 Activity 或 Fragment ,随处可见的EventBus,难以阅读的数据流传递和混乱的回调地狱等等,它们虽然不会导致系统马上崩溃,但随着项目的发展,它们会变得难以维护,甚至很难添加新的代码,这无疑会成为业务增长的可怕障碍。

所以说,对于开发者们来讲,一个好的架构指导规范,至关重要。

架构的选择

现在网上关于 MVVM、MVP、MVC、AndroidFlux 的选择与分析的文章已经非常多了,这里我就不过多描述了,感兴趣的同学可以看 我的Android重构之旅:架构篇 ,在这里我们最终选择了 MVP 作为我们的开发架构,MVP 的好处有很多,但最终使我们选择它的是因为看中了它对于普通开发者简单容易上手,并同时能将我们的 Activity 的业务边界规划清晰。

Refused God Activity

在这些年的开发过程中,经常能够看到上千行代码的 Activity ,它无所不能:

  • 重新定义的生命周期
  • 处理Intent
  • 数据更新
  • 线程切换
  • 基础业务逻辑
    ……

更有甚者在 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;
    }
    }
    }

/**

  • 处理加载错误的情况
  • @param loadState
  • @param errorInfo
    */
    protected void handleLoadDataError(int loadState, int errorInfo) {
    switch (loadState) {
    case INIT: {
    viewBase.switchReLoadLayout(errorInfo);
    break;
    }
    case REFRESH: {
    ToastUtil.showToast(viewBase.getContext(), viewBase.getContext().getString(errorInfo));
    viewBase.finishRefresh();
    break;
    }
    case LOAD_MORE: {
    pageStart = pageStart - 1;
    ToastUtil.showToast(viewBase.getContext(), viewBase.getContext().getString(errorInfo));
    viewBase.finishRefreshLoadMore();
    break;
    }
    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

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

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

img

img

img

img

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

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

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后如何才能让我们在面试中对答如流呢?

答案当然是平时在工作或者学习中多提升自身实力的啦,那如何才能正确的学习,有方向的学习呢?有没有免费资料可以借鉴?为此我整理了一份Android学习资料路线:

这里是一部分我工作以来以及参与过的大大小小的面试收集总结出来的一套BAT大厂面试资料专题包,主要还是希望大家在如今大环境不好的情况下面试能够顺利一点,希望可以帮助到大家。

好了,今天的分享就到这里,如果你对在面试中遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

遇到的问题,或者刚毕业及工作几年迷茫不知道该如何准备面试并突破现状提升自己,对于自己的未来还不够了解不知道给如何规划。来看看同行们都是如何突破现状,怎么学习的,来吸收他们的面试以及工作经验完善自己的之后的面试计划及职业规划。

最后,祝愿即将跳槽和已经开始求职的大家都能找到一份好的工作!

这些只是整理出来的部分面试题,后续会持续更新,希望通过这些高级面试题能够降低面试Android岗位的门槛,让更多的Android工程师理解Android系统,掌握Android系统。喜欢的话麻烦点击一个喜欢再关注一下

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 10
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值