背景
before:
在我们APP启动过程,我们可能常常有这样的需求:在APP第一次进入的时候根据网络请求的结果弹一个对话框式的广告, ok~ 很简单,那么代码大致就是这样(这边都是伪代码,为了方便理解):
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
checkFirstDialogIfNeed();
}
/**
* step 1
*/
private void checkFirstDialogIfNeed(){
Utils.fakeRequest("http://www.api1.com", new HttpCallBack() {
@Override
public void onOk() {
showADialog();
}
@Override
public void onFailure() {
//do nothing
}
});
}
ok,做完上线
step1
过了一阵子,产品同学又跑过来了说:
“老板的需求,我们要在首页第一次进来的时候加一个注册协议的弹框”
程序员小A: “可是首页已经之前有一个弹框了,直接一起展示吗?”
产品同学: “一起弹出来体验不好,那你放在A弹框之后吧!”
ok,那么程序员小A寻思了一下,那应该就是这么改了:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
checkFirstDialogIfNeed();
}
private void checkFirstDialogIfNeed(){
Utils.fakeRequest("http://www.api1.com", new HttpCallBack() {
@Override
public void onOk() {
showADialog();
}
@Override
public void onFailure() {
//请求失败直接跳过,请求注册协议
checkRegisterAgreement();
}
});
}
private void showADialog() {
new AlertDialog.Builder(this)
.setTitle("这是一条有态度的广告")
.setPositiveButton("我看完了", null)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
//弹框结束后请求注册协议
checkRegisterAgreement();
}
}).create().show();
}
private void checkRegisterAgreement() {
Utils.fakeRequest("http://www.api2.com", new HttpCallBack() {
@Override
public void onOk() {
showBDialog();
}
@Override
public void onFailure() {
//do nothing
}
});
}
这么下来,我们先请求接口APi1,如果成功了弹广告弹框A,弹框结束了调用注册协议的方法,如果APi1 请求失败,直接跳过广告弹框A,继而请求注册协议,当注册协议请求成功了弹出我们的注册协议弹框,这么下来,我们确实可以保证注册协议弹框在广告弹框之后~
step2
过了几天,产品同学又来了:“这次我们首页初次进来再加一个H5页面跳转,这次就加在 广告和注册协议之间吧…”
于是乎,小A继续改了改代码,整个代码就成了这样:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
checkFirstDialogIfNeed();
}
private void checkFirstDialogIfNeed() {
Utils.fakeRequest("http://www.api1.com", new HttpCallBack() {
@Override
public void onOk() {
showADialog();
}
@Override
public void onFailure() {
// //请求失败直接跳过,请求注册协议
// checkRegisterAgreement();
checkNeedShowH5();
}
});
}
private void showADialog() {
new AlertDialog.Builder(this)
.setTitle("这是一条有态度的广告")
.setPositiveButton("我看完了", null)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
// //弹框结束后请求注册协议
// checkRegisterAgreement();
//现在产品要插入一个H5页面优先请求
checkNeedShowH5();
}
}).create().show();
}
private void checkRegisterAgreement() {
Utils.fakeRequest("http://www.api2.com", new HttpCallBack() {
@Override
public void onOk() {
showBDialog();
}
@Override
public void onFailure() {
//do nothing
}
});
}
private void showBDialog() {
new AlertDialog.Builder(this)
.setTitle("这是注册协议")
.setPositiveButton("我看完了", null)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
//do nothing
}
}).create().show();
}
private void checkNeedShowH5() {
Utils.fakeRequest("http://www.api3.com", new HttpCallBack() {
@Override
public void onOk() {
toH5Page();
}
@Override
public void onFailure() {
checkRegisterAgreement();
}
});
}
private void toH5Page() {
startActivityForResult(new Intent(this, TestH5Activity.class), REQUEST_CODE_H5);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_CODE_H5:
checkRegisterAgreement();
break;
default:
break;
}
}
首先将原有step1 结束之后不再调用注册协议,而是调用请求H5的方法.
由于去H5是一个Activity的跳转,所以我们在onActivityResult里我们继续之前的注册协议的调用.
看看大致demo效果:
再后来…几经迭代,首页首次的弹框和页面跳转已经有7、8个了,当每次产品有类似需求的时候,我们又要重复上面的过程了,每次大概梳理个大半天吧.
大家是否有发现什么问题吗?
1.首页次序之间强耦合,每次一旦要在二者之前插入一个其他弹框或者页面,我们每次都要修改在它之前和之后的调用链,至少要修改三处,很容易漏掉,但是实际上它们彼此之间除了次序之外,没有其他关联.
2.每次新需求来的时候都要完整梳理原有逻辑,比较浪费时间,影响效率.
怎么办?
思考:
1.能否在一个链中统一管理要处理的事情,每个事情之间互相不关联,只要简单配置就可以轻松替换它们之间的顺序.
2.后来维护者,很清晰的就能知道调用的次序,无需每次重新梳理整个业务代码.
设计:
1.我们是否可以把每件要做的事情抽象成一个节点,每个节点只关心自己的任务是否完成,它并不知道它是第几个,也不知道它前面或者后面的是谁.
2.每个节点统一由一个流来管理,它作为全局统筹者,可以控制从任意节点开始、控制整个流的开启与结束等,每个节点的顺序由流来管理.
实现:
带着以上设计思路,我对代码做了一下重构,代码变成了这样:
public class AfterActivity extends AppCompatActivity {
private static final int REQUEST_CODE_H5 = 1;
/**
* 初次广告弹框
*/
private static final int NODE_FIRST_AD = 10;
/**
* 初次进入h5页
*/
private static final int NODE_CHECK_H5 = 20;
/**
* 初次进入的注册协议
*/
private static final int NODE_REGISTER_AGREEMENT = 30;
private WorkFlow workFlow;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startWorkFlow();
}
private void startWorkFlow() {
workFlow = new WorkFlow.Builder()
.withNode(getFirstAdNode())
.withNode(getShowRegisterAgreementNode())
.withNode(getShowH5Node())
.create();
workFlow.start();
}
private WorkNode getFirstAdNode() {
return WorkNode.build(NODE_FIRST_AD, new Worker() {
@Override
public void doWork(final Node current) {
Utils.fakeRequest("http://www.api1.com", new HttpCallBack() {
@Override
public void onOk() {
new AlertDialog.Builder(AfterActivity.this)
.setTitle("这是一条有态度的广告")
.setPositiveButton("我看完了", null)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
//仅仅只需关心自己是否完成,下一个节点会自动执行
current.onCompleted();
}
}).create().show();
}
@Override
public void onFailure() {
//仅仅只需关心自己是否完成,下一个节点会自动执行
current.onCompleted();
}
});
}
});
}
private WorkNode getShowRegisterAgreementNode() {
return WorkNode.build(NODE_REGISTER_AGREEMENT, new Worker() {
@Override
public void doWork(final Node current) {
Utils.fakeRequest("http://www.api2.com", new HttpCallBack() {
@Override
public void onOk() {
new AlertDialog.Builder(AfterActivity.this)
.setTitle("这是注册协议")
.setPositiveButton("我看完了", null)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
current.onCompleted();
}
}).create().show();
}
@Override
public void onFailure() {
current.onCompleted();
}
});
}
});
}
private WorkNode getShowH5Node() {
return (WorkNode.build(NODE_CHECK_H5, new Worker() {
@Override
public void doWork(final Node current) {
Utils.fakeRequest("http://www.api3.com", new HttpCallBack() {
@Override
public void onOk() {
startActivityForResult(new Intent(AfterActivity.this, TestH5Activity.class), REQUEST_CODE_H5);
}
@Override
public void onFailure() {
current.onCompleted();
}
});
}
}));
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case REQUEST_CODE_H5:
workFlow.continueWork();
break;
default:
break;
}
}
}
经过上述重构,现在的首页流程:
1. 进入首页要做的几件事之间相互无关联,它们的位置可以任意切换,只需改变id大小便可轻松调整它们的执行顺序.
2. 想要加入或者插入节点无需改动原有逻辑.
实现思路
设计每个工作节点
1.我希望每个任务间彼此独立,只关心自己的事情是否完成,我把它抽象成一个节点,每个节点只有自己的节点id 和 完成的方法:
public interface Node {
/**
* 节点id
*
* @return 当前节点id
*/
int getId();
/**
* 任务完成时触发
*/
void onCompleted();
}
至于为什么要提供id,后面会讲到.
我们再来看看它的实现类WorkNode的核心代码:
public class WorkNode implements Node {
/**
* 节点id
*/
private int nodeId;
/**
* 节点工作者
*/
private Worker worker;
private WorkCallBack callBack;
public static WorkNode build(int nodeId, Worker worker) {
return new WorkNode(nodeId, worker);
}
/**
* @param worker 调用者传入,即真正执行要做的事情
*/
public WorkNode(int nodeId, Worker worker) {
this.nodeId = nodeId;
this.worker = worker;
}
/**
* 由workFlow来决定调用
*
* @param callBack 当调用onCompleted 之后回调给WorkFlow
*/
void doWork(WorkCallBack callBack) {
this.callBack = callBack;
worker.doWork(this);
}
@Override
public int getId() {
return nodeId;
}
@Override
public void onCompleted() {
if (null != callBack) {
callBack.onWorkCompleted();
}
}
interface WorkCallBack {
/**
* 当前任务完成
*/
void onWorkCompleted();
}
}
构造方法中传入了节点id,和Worker, 这个Worker的doWork方法的实现就是我们这个节点真正要做的事情:
public interface Worker {
/**
* 执行任务
*
* @param current 当前节点
*/
void doWork(Node current);
}
至此我们回看下demo中对WorkNode的构建:
private WorkNode getFirstAdNode() {
return WorkNode.build(NODE_FIRST_AD, new Worker() {
@Override
public void doWork(final Node current) {
Utils.fakeRequest("http://www.api1.com", new HttpCallBack() {
@Override
public void onOk() {
new AlertDialog.Builder(AfterActivity.this)
.setTitle("这是一条有态度的广告")
.setPositiveButton("我看完了", null)
.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialogInterface) {
//仅仅只需关心自己是否完成,下一个节点会自动执行
current.onCompleted();
}
}).create().show();
}
@Override
public void onFailure() {
//仅仅只需关心自己是否完成,下一个节点会自动执行
current.onCompleted();
}
});
}
});
}
是不是很清晰?
节点只需要关心自己是否做完了,调用完onCompleted之后,一切与我无关了,后续做什么就交给WorkFlow去处理了.
那节点的doWork方法是什么时候被调用的呢? 它内部是怎么安排工作的呢?,我们来设计WorkFlow.
设计工作流:
首先作为所有节点的管理者,当然要把它们存下来,用什么数据结构来存呢?回顾一下我的需求: 可以灵活控制节点的执行顺序, so…经过反复筛选,我最终选择了SparseArray来存放我们所有的节点,因为我们为每个节点提供id作为key:
一来可以提高代码可读性。
二来,SparseArray内部是数组实现的,而且是按照key的大小升序排列的,基于这个特性,我们只需要改变定义Key值的大小关系就可以改变它们在数组中的顺序。
我们再来看看用SparseArray来实现的WorkFlow:
public class WorkFlow {
private SparseArray<WorkNode> flowNodes;
public WorkFlow(SparseArray<WorkNode> flowNodes) {
this.flowNodes = flowNodes;
}
/**
* 开始工作,默认从第一个节点
*/
public void start() {
startWithNode(flowNodes.keyAt(0));
}
/**
* 基于某个节点Id 开始工作
*
* @param startNodeId 节点id
*/
public void startWithNode(int startNodeId) {
final int startIndex = flowNodes.indexOfKey(startNodeId);
WorkNode startNode = flowNodes.valueAt(startIndex);
startNode.doWork(new WorkNode.WorkCallBack() {
@Override
public void onWorkCompleted() {
findAndExecuteNextNodeIfExist(startIndex);
}
});
}
private void findAndExecuteNextNodeIfExist(int startIndex) {
final int nextIndex = startIndex + 1;
final WorkNode nextNode = flowNodes.valueAt(nextIndex);
if (null != nextNode) {
nextNode.doWork(new WorkNode.WorkCallBack() {
@Override
public void onWorkCompleted() {
findAndExecuteNextNodeIfExist(nextIndex);
}
});
}
}
}
我们在demo中调用的start()的方法,其实内部调用的是 startWithNode 方法,这个方法接收一个参数 startNodeId ,也就是我们构建节点的id,我们在 SparseArray 中找到节点,直接开始执行。
当我们在节点内部调用完 onCompleted 方法之后, 会直接回调 onWorkCompleted 方法,此时我们再看看是否有下一个节点,当有下一个节点之后,一直递归到下一个没有节点为止。
至此,我们原理基本分析完成,通过内部 SparseArray的设计,我们可以灵活从任意节点开始执行,外部的WorkFlow就是一个全局的管理者,我们可以拓展很多其他功能,比如 continueWork() :当我们在节点以外希望让流程继续执行下去的时候(参考Demo的 onActivityResult), revert():回退到上一节点 等等。
总结
- WorkFlow,很好的接耦了各个次序任务直接的相互依赖,提高了开发效率。
- 它也很好的提高了代码的可维护性和可读性,非常便于后来新次序任务的拓展。
最后对于程序员来说,要学习的知识内容、技术有太多太多,要想不被环境淘汰就只有不断提升自己,从来都是我们去适应环境,而不是环境来适应我们!
这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。
相信它会给大家带来很多收获:
上述【高清技术脑图】以及【配套的架构技术PDF】可以 加我WX:X1524478394 免费获取
程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。
相关推荐
解压并导入到Android Studio项目中,根据文件内容进行相应的配置和调整,就可以实现一个简单易用的Android仪表盘功能。 总之,创建一个简单的Android仪表盘并不复杂,关键在于合理地使用Android提供的UI组件和绘图...
在Android开发中,RecyclerView是一个非常重要的组件,它用于展示可滚动的列表,具有高效的数据管理和灵活的布局管理器。RecyclerView是对...在学习过程中,不断实践和优化,才能更好地掌握这个强大的列表组件。
这个"android简单的画图板实现代码"就是一个适合初学者的示例项目,旨在帮助开发者了解如何在Android环境中实现画图功能。我们将深入探讨这个项目中的关键知识点。 首先,我们需要了解Android图形绘制的基础。在...
本文将详细探讨如何在Android应用程序中实现自动检查并下载新版本的代码逻辑,以达到无缝更新的效果。 首先,我们需要一个服务(Service)来执行后台的更新检查。这个服务会在合适的时机(例如应用启动时或网络连接...
- 代码优化:源码中可能会包含如何减少CPU使用率,优化内存分配,避免空指针异常,以及合理使用线程和异步处理的实例。 - 数据库优化:优化大师可能使用SQLite数据库存储数据,源码中会展示如何高效查询、更新和...
综上所述,"Android中JNI实现AES加密源代码"涉及的知识点包括Android JNI的使用、AES加密算法的实现、NDK开发环境的配置、C/C++与Java的交互,以及数据安全和性能优化。通过这样的实现,开发者可以在Android应用中...
"Android自定义ScrollView:实现滑动顶部停靠"这个主题聚焦于如何创建一个在用户上滑时能够固定在顶部的组件,这通常被称为“sticky header”或“吸顶效果”。这种效果在很多应用中都有应用,比如新闻列表、电商商品...
本示例将详细解释如何在Android应用中实现一个自定义的时间轴效果。 首先,我们需要创建一个自定义的布局来表示时间轴的样式。时间轴通常由三个主要部分组成:时间点、时间线和时间线上的内容。在XML布局文件中,...
基于Android Studio实现的学生...以下是一个简单的数据库操作示例代码: 5 功能实现 通过界面设计和数据库操作,可以实现学生信息的录入、展示和编辑功能。根据具体需求,可以添加其他功能,如学生信息搜索、删除等。
综上所述,这段代码提供了一个简单的计算器应用实现,对于想要学习Android开发的初学者来说,是一个很好的入门案例。通过理解其基础架构和工作原理,开发者可以在此基础上进行优化和扩展,以构建更复杂、功能更全面...
在Android应用开发中,性能优化是一项至关重要的任务,特别是在处理数据加载、网络请求以及大量计算时。本篇文章将深入探讨如何通过实现后台预读线程来优化多线程任务,提高用户界面的响应速度和整体应用体验。 ...
在这个压缩包中,我们可能找到了多个有关Android编程的示例项目或者代码片段。下面,我将详细讲解一些Android开发中的重要知识点。 1. **Activity**:Activity是Android应用的基本组件,代表用户界面的一个屏幕。它...
在Android开发中,创建一个图片浏览器是常见的需求,主要用于展示用户拍摄或选择的照片。这个"GalleryDemo"项目正是为了实现这样一个功能,它提供了一个类似系统自带的图片浏览体验,包括图片的缩放和平滑滑动。 一...
`MyRevealLayout`这个文件可能是实现了这样一个自定义布局,它可能覆盖了`dispatchTouchEvent()`方法来处理触摸事件,并根据触摸位置启动水波纹动画。 在实际开发中,为了兼容Android L之前的版本,可以使用第三方...
这个源代码提供了一个深入理解Java在Android环境下的优化技术的机会,适合开发者进行学习和研究。以下将详细解释其中涉及的关键知识点: 1. **Java语言基础**:作为项目的基础,Java语言是Android开发的主要语言。...
Android+SQlite 简单的《学生信息管理系统》(实现基本增删改查) 此代码中还有与其相对应的apk文件(在SIMS/bin目录中),大家可先行放手机上看一下效果。 希望对初学者有一定的帮助。(本人自己编写)
- **Android Drawable Importer**:虽然非代码高亮插件,但能提升Android开发效率,自动导入和优化图片资源。 4. **安装与配置插件**: - Android Studio允许通过内置插件市场安装,`File` -> `Settings` -> `...
《Android优化大师源代码解析》 在Android应用开发领域,性能优化是提升用户体验的关键环节。本文将深入探讨“Android优化大师”源代码,揭示其中蕴含的优化策略和技术手段,帮助开发者提升自己的Android应用性能。...
* Java(Android)代码优化:优化 Java 代码,减少对象的创建和销毁,使用缓存机制,避免过多的计算操作。 * 图片缓存:使用图片缓存机制,减少图片的加载次数,提高应用程序的响应速度。 2. Android 开源代码知识...
1. 开放源代码:Android 是基于 Linux 内核的开源操作系统,开发人员可以自由获取、使用和修改源代码。 2. 多样化的硬件设备支持:Android 支持多种硬件设备和屏幕尺寸,可以运行于手机、平板电脑、电视、手表等多种...