`
Z_萧晓
  • 浏览: 11142 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Android 代码优化:“这个需求很简单,怎么实现我不管”

 
阅读更多

背景

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:仪表盘(简单易实现)

    解压并导入到Android Studio项目中,根据文件内容进行相应的配置和调整,就可以实现一个简单易用的Android仪表盘功能。 总之,创建一个简单的Android仪表盘并不复杂,关键在于合理地使用Android提供的UI组件和绘图...

    Android Studio实现简单的购物商城界面源码

    在Android应用开发中,创建一个简单的购物商城界面是常见的需求,尤其对于初学者而言,这是一个很好的实践项目。本文将详细讲解如何使用Android Studio和ListView组件来实现这样的界面。首先,我们要理解ListView的...

    android实现登录注册

    Android 实现登录注册 ...总结来说,Android 实现登录注册功能需要考虑多个方面,包括需求、实现步骤、技术实现、安全性和优化等。只有通过详细的设计和实现,才能确保登录注册功能的安全和可靠性。

    Android应用自动更新代码实现

    本文将详细探讨如何在Android应用程序中实现自动检查并下载新版本的代码逻辑,以达到无缝更新的效果。 首先,我们需要一个服务(Service)来执行后台的更新检查。这个服务会在合适的时机(例如应用启动时或网络连接...

    Android代码-优化大师源代码.zip

    - 代码优化:源码中可能会包含如何减少CPU使用率,优化内存分配,避免空指针异常,以及合理使用线程和异步处理的实例。 - 数据库优化:优化大师可能使用SQLite数据库存储数据,源码中会展示如何高效查询、更新和...

    Android中JNI实现AES加密源代码

    综上所述,"Android中JNI实现AES加密源代码"涉及的知识点包括Android JNI的使用、AES加密算法的实现、NDK开发环境的配置、C/C++与Java的交互,以及数据安全和性能优化。通过这样的实现,开发者可以在Android应用中...

    Android自定义ScrollView:实现滑动顶部停靠

    "Android自定义ScrollView:实现滑动顶部停靠"这个主题聚焦于如何创建一个在用户上滑时能够固定在顶部的组件,这通常被称为“sticky header”或“吸顶效果”。这种效果在很多应用中都有应用,比如新闻列表、电商商品...

    Android时间轴(Timeline)效果的实现代码示例

    本示例将详细解释如何在Android应用中实现一个自定义的时间轴效果。 首先,我们需要创建一个自定义的布局来表示时间轴的样式。时间轴通常由三个主要部分组成:时间点、时间线和时间线上的内容。在XML布局文件中,...

    android简单计算器代码

    综上所述,这段代码提供了一个简单的计算器应用实现,对于想要学习Android开发的初学者来说,是一个很好的入门案例。通过理解其基础架构和工作原理,开发者可以在此基础上进行优化和扩展,以构建更复杂、功能更全面...

    Android多线程任务优化2:实现后台预读线程

    在Android应用开发中,性能优化是一项至关重要的任务,特别是在处理数据加载、网络请求以及大量计算时。本篇文章将深入探讨如何通过实现后台预读线程来优化多线程任务,提高用户界面的响应速度和整体应用体验。 ...

    android 经典代码例子

    在这个压缩包中,我们可能找到了多个有关Android编程的示例项目或者代码片段。下面,我将详细讲解一些Android开发中的重要知识点。 1. **Activity**:Activity是Android应用的基本组件,代表用户界面的一个屏幕。它...

    android ui源码:自己设计的一个精美布局

    例如,分析一个开源项目的布局代码,可以学习到如何优化性能,避免过度绘制,以及如何实现动态布局和响应式设计。 4. **布局设计实践**: 实践是检验理论的最好方式。"自己设计的一个精美布局"项目提供了这样的...

    android 一个图片浏览器的实现。

    在Android开发中,创建一个图片浏览器是常见的需求,主要用于展示用户拍摄或选择的照片。这个"GalleryDemo"项目正是为了实现这样一个功能,它提供了一个类似系统自带的图片浏览体验,包括图片的缩放和平滑滑动。 一...

    Android自定义控件:Android L控件点击水波纹的实现(源码 + Demo)

    `MyRevealLayout`这个文件可能是实现了这样一个自定义布局,它可能覆盖了`dispatchTouchEvent()`方法来处理触摸事件,并根据触摸位置启动水波纹动画。 在实际开发中,为了兼容Android L之前的版本,可以使用第三方...

    JavaAndroid优化大师源代码

    这个源代码提供了一个深入理解Java在Android环境下的优化技术的机会,适合开发者进行学习和研究。以下将详细解释其中涉及的关键知识点: 1. **Java语言基础**:作为项目的基础,Java语言是Android开发的主要语言。...

    Android Studio代码高亮插件

    - **Android Drawable Importer**:虽然非代码高亮插件,但能提升Android开发效率,自动导入和优化图片资源。 4. **安装与配置插件**: - Android Studio允许通过内置插件市场安装,`File` -&gt; `Settings` -&gt; `...

    Android优化大师源代码.rar

    《Android优化大师源代码解析》 在Android应用开发领域,性能优化是提升用户体验的关键环节。本文将深入探讨“Android优化大师”源代码,揭示其中蕴含的优化策略和技术手段,帮助开发者提升自己的Android应用性能。...

    Android 性能调优和开源代码知识分享

    * Java(Android)代码优化:优化 Java 代码,减少对象的创建和销毁,使用缓存机制,避免过多的计算操作。 * 图片缓存:使用图片缓存机制,减少图片的加载次数,提高应用程序的响应速度。 2. Android 开源代码知识...

    Android添加系统白名单和电池优化.zip

    1. 开放源代码:Android 是基于 Linux 内核的开源操作系统,开发人员可以自由获取、使用和修改源代码。 2. 多样化的硬件设备支持:Android 支持多种硬件设备和屏幕尺寸,可以运行于手机、平板电脑、电视、手表等多种...

    简单的记事本android应用源代码

    【标题】"简单的记事本android应用源代码"揭示了这是一个基于Android平台的简易记事本应用程序的开发源代码。这个应用可能具有基础的文本编辑、保存和查看功能,是初学者了解Android应用开发的好例子。 【描述】...

Global site tag (gtag.js) - Google Analytics