c++builder启动了怎么停止_更快!更高效!异步启动框架Alpha完全解析

本文作者

作者:积木zz

链接:

https://juejin.im/post/6844904201143713806

本文由作者授权发布。

之前推送过一篇:

推荐一个更贴近 android 场景的启动框架 | Anchors

文中提到 alpha,因为 alpha 是阿里开源的,想了解的同学应该更多一些,那么就开看吧。

1 背景

‍‍

‍‍启动优化,其实就是优化从点击icon到主页面展示这个过程的速度,让主界面尽量快的展现在用户面前。所以我们要做的就是找到那些耗时操作,并将其优化。

耗时操作怎么找到?
一般分成两个场景:

1、线下(debug)场景 在应用的开发阶段,我们一般通过AOP进行函数的耗时统计,通过aspectj库可以很方便的将代码插入到函数内部,从而统计到每个方法的耗时时间。或者直接通过Android Studio 自带的Profiler CPU工具,查看每个方法的耗时,CPU信息。

2、线上场景 当应用已经发布到线上,统计就变得不是那么容易了。所以我们一般就通过函数插桩的方式,自己写一个统计耗时的工具类,部署到需要统计的地方,比如Application和Activity的生命周期,数据库的初始化,第三方库的初始化,然后最后上传数据到服务器即可。

找到耗时的地方后该怎么优化解决呢?

一般就是通过分析这些要执行的任务,进行异步,懒加载,预加载等操作。

其中「异步任务」是很重要的一环,这里就涉及到我们今天要讲的内容了,启动器。

顾名思义就是帮我们优化启动的一个工具,可以高效合理的帮我们安排启动过程中的一些任务处理。

接下来就带大家从源码开始分析,一起看看阿里的启动器——Alpha。

https://github.com/alibaba/alpha
2 作为一个启动器,该有什么功能

有人可能要问了,不就是异步任务吗,我整几个线程,把任务往里面一丢不就行了。

事情可没那么简单,比如现在有6个任务需要在Application里面执行,其中「Task1,Tas6」需要在主线程执行,「Task2,Task3」需要在「Task1」执行完才能执行,「Task4,Task5」需要「Task2和Task3」执行完才能执行,「Task6」需要「Task4和Task5」执行完才能执行,「Task4」的耗时要大于「Task5」。

这是个啥啊?我晕了。

这么多关系,我该怎么处理?

既然文字看着太麻烦,就画个图吧,这里涉及到一个用于时间管理的图形——Pert图。Pert 图是一个有向图,能清晰地描述子任务之间的「依赖关系」。

比如我们这个项目的情况,画成Pert 图如下:

35e1a64eba1ae2400c2051dea3bdc764.png

通过Pert图还是可以很直观的看到每个Task的关系,其中当执行完Task2和Task3之后,我们有两个选择,先执行Task4或者先执行Task5,由于Task4的耗时要大于Task5,所以我们就选择先执行Task4了。

其实「制定任务执行」的计划在我们生活中也随处可见,比如我们早起后也有很多事情要处理,比如烧水(5分钟),刷牙(3分钟),洗脸(2分钟),上厕所(8分钟)。

怎么选一条「最优路线」能让我们最快完成这些事情呢?

肯定是能一起并行的事情就安排到一起,然后并行的同时让耗时久的事情先发生。比如先烧水,然后上厕所的同时刷牙洗脸?

扯远了扯远了,哈哈哈,收!

好了,看看我们如果用Alpha框架实现该怎么写呢?

//构造方法,true为主线程执行
Task1=new Task("task1",true);
Task2=new Task("task2",false);
Task3=new Task("task3",false);
Task4=new Task("task4",true);
Task5=new Task("task5",false);
Task6=new Task("task6",true);

//设置优先级,耗时操作优先级较高
Task4.setExecutePriority(1);
Task5.setExecutePriority(2);

Project.Builder builder = new Project.Builder().withTaskCreator(new MyTaskCreator());
builder.add(Task1);
builder.add(Task2).after(Task1);
builder.add(Task3).after(Task1);
builder.add(Task4).after(Task2,Task3);
builder.add(Task5).after(Task2,Task3);
builder.add(Task6).after(Task4,Task5);
builder.setProjectName("innerGroup");

AlphaManager.getInstance(mContext).addProject(builder.create());

AlphaManager.getInstance(mContext).start();
搞定!还不错吧。那就来一起分析下它吧!

首先,我们自己如果好好想想,如果让我们来做一个异步启动框架,需要考虑哪些问题?

  • 多线程管理
  • 任务的优先级
  • 任务之间的先后关系
  • 任务是否需要在主线程执行
  • 多进程处理

就让我们带着这些问题去看看Alpha的内部源码。

3 Alpha源码解析

从上面的代码可以看到,任务的开启是由AlphaManager.getInstance(mContext).start()方法开始调用,所以我们就从这个start方法开始研究:

public void start() {
    Project project = null;

    do {
        //1.是否有为当前进程单独配置的Project,此为最高优先级
        if (mProjectForCurrentProcess != null) {
            project = (Project) mProjectForCurrentProcess;
            break;
        }

        //2.如果当前是主进程,是否有配置主进程Project
        if (AlphaUtils.isInMainProcess(mContext)
                && mProjectArray.indexOfKey(MAIN_PROCESS_MODE) >= 0) {
            project = (Project) mProjectArray.get(MAIN_PROCESS_MODE);
            break;
        }

        //3.如果是非主进程,是否有配置非主进程的Project
        if (!AlphaUtils.isInMainProcess(mContext)
                && mProjectArray.indexOfKey(SECONDARY_PROCESS_MODE) >= 0) {
            project = (Project) mProjectArray.get(SECONDARY_PROCESS_MODE);
            break;
        }

        //4.是否有配置适用所有进程的Project
        if (mProjectArray.indexOfKey(ALL_PROCESS_MODE) >= 0) {
            project = (Project) mProjectArray.get(ALL_PROCESS_MODE);
            break;
        }
    } while (false);

    if (project != null) {
        addListeners(project);
        project.start();
    } else {
        AlphaLog.e(AlphaLog.GLOBAL_TAG, "No startup project for current process.");
    }
}

哇,一开始就把我们多进程的疑惑给解决了。start方法首先就判断了当前的进程以及是否能匹配到相关进程的任务。可以看到一共有三种进程配置变量:

  • MAIN_PROCESS_MODE :主进程任务
  • SECONDARY_PROCESS_MODE :非主进程任务
  • ALL_PROCESS_MODE:适用于所有进程的任务

那么在哪里配置这些进程选项呢?

addProject方法

public void addProject(Task project, int mode) {
    if (project == null) {
        throw new IllegalArgumentException("project is null");
    }

    if (mode  ALL_PROCESS_MODE) {
        throw new IllegalArgumentException("No such mode: " + mode);
    }

    if (AlphaUtils.isMatchMode(mContext, mode)) {
        mProjectArray.put(mode, project);
    }
}

ok,够简单吧。继续往下看start方法。跳转到project的start方法:

@Override
public void start() {
    mStartTask.start();
}

这么简单吗,就开启了一个mStartTask?这个mStartTask是之前设置的那些任务中第一个任务吗?接着看:

    //Project.java
    private void init() {
    ...
        mProject = new Project();
        mFinishTask = new AnchorTask(false, "==AlphaDefaultFinishTask==");
        mFinishTask.setProjectLifecycleCallbacks(mProject);
        mStartTask = new AnchorTask(true, "==AlphaDefaultStartTask==");
        mStartTask.setProjectLifecycleCallbacks(mProject);
        mProject.setStartTask(mStartTask);
        mProject.setFinishTask(mFinishTask);
   ...
    }


private static class AnchorTask extends Task {
    private boolean mIsStartTask = true;
    private OnProjectExecuteListener mExecuteListener;

    public AnchorTask(boolean isStartTask, String name) {
        super(name);
        mIsStartTask = isStartTask;
    }

    public void setProjectLifecycleCallbacks(OnProjectExecuteListener callbacks) {
        mExecuteListener = callbacks;
    }

    @Override
    public void run() {
        if (mExecuteListener != null) {

            if (mIsStartTask) {
                mExecuteListener.onProjectStart();
            } else {
                mExecuteListener.onProjectFinish();
            }
        }
    }

}        

可以看到,在Project类的初始化方法中,定义了一个开始任务和一个结束任务。这是因为从执行角度看,一个任务序列必须有一个开始节点和一个结束节点。但是实际情况中,可能会有多个任务可以同时开始,而且有多个任务可以同时作为结束点。所以就设置了这两个节点方便控制整个流程,标记流程的开始和结束,也方便了任务的监听。

说回上面,开始任务的start方法走到哪里去了呢?自然是AnchorTask的父类Task,看看源码:

public synchronized void start() {
    ...
    switchState(STATE_WAIT);

    if (mInternalRunnable == null) {
        mInternalRunnable = new Runnable() {
            @Override
            public void run() {
                android.os.Process.setThreadPriority(mThreadPriority);
                long startTime = System.currentTimeMillis();

                switchState(STATE_RUNNING);
                Task.this.run();
                switchState(STATE_FINISHED);

                long finishTime = System.currentTimeMillis();
                recordTime((finishTime - startTime));

                notifyFinished();
                recycle();
            }
        };
    }

    if (mIsInUiThread) {
        sHandler.post(mInternalRunnable);
    } else {
        sExecutor.execute(mInternalRunnable);
    }
}

源码还是挺简单的哈,定义了一个Runnable,然后判断是否主线程,并执行这个Runnable。其中还穿插了一些状态的改变,在Runnable内部主要是执行了Task.this.run(),也就是执行了任务本身。

其中setThreadPriority方法主要是设置了线程的优先级,比如THREAD_PRIORITY_DEFAULT等,这里的优先级是较线程而言的,主要是针对CPU资源的竞争,跟我们需要的Task之间的优先级关系不大。如果是需要在主线程执行的任务,就会通过Handler(sHandler)将事件传递给主线程执行。

如果是需要在非主线程执行的任务,就会通过线程池(sExecutor)去执行线程任务。

诶,好像没了?开始任务执行了就没了吗?

再回头看看,还有一个notifyFinished方法。按这个名字应该就是通知任务结束的一个方法,看看源码:

void notifyFinished() {
    if (!mSuccessorList.isEmpty()) {
        AlphaUtils.sort(mSuccessorList);

        for (Task task : mSuccessorList) {
            task.onPredecessorFinished(this);
        }
    }

    if (!mTaskFinishListeners.isEmpty()) {
        for (OnTaskFinishListener listener : mTaskFinishListeners) {
            listener.onTaskFinish(mName);
        }

        mTaskFinishListeners.clear();
    }
}

这个方法主要做了三件事:

  • mSuccessorList 排序
  • 遍历mSuccessorList列表,执行onPredecessorFinished方法
  • 监听回调onTaskFinish方法

mSuccessorList是什么呢?我们叫它「紧后任务列表」,也就是接下来要执行的任务列表。

所以流程就是先把当前任务之后的任务列表进行一个排序,根据优先级排序。然后按顺序执行onPredecessorFinished方法。

如果紧后任务列表为空,也就代表没有后续任务了,那么就会走onTaskFinish回调方法,告知当前Project已经执行完毕。

接下来就看看紧后任务是怎么加进来的呢?又该怎么排序?onPredecessorFinished方法又执行了些什么东西?

//1、紧后任务添加
public Builder after(Task task) {
    task.addSuccessor(mCacheTask);
    mFinishTask.removePredecessor(task);
    mIsSetPosition = true;
    return Builder.this;
}

void addSuccessor(Task task) {
    task.addPredecessor(this);
    mSuccessorList.add(task);
}

//2、紧后任务列表排序 
public static void sort(List tasks) {
    if (tasks.size() <= 1) {
        return;
    }
    Collections.sort(tasks, sTaskComparator);
}    

private static Comparator sTaskComparator = new Comparator() {
    @Overridepublic int compare(Task lhs, Task rhs) {return lhs.getExecutePriority() - rhs.getExecutePriority();
    }
};    //3、紧后任务执行synchronized void onPredecessorFinished(Task beforeTask) {if (mPredecessorSet.isEmpty()) {return;
    }
    mPredecessorSet.remove(beforeTask);if (mPredecessorSet.isEmpty()) {
        start();
    }
}    

ok,源码写的很清楚了,这里逐步分析下:

由源码得知,紧后任务列表主要是通过after方法,还记得之前配置任务的时候吗?builder.add(Task2).after(Task1),所以这个after就代表Task2要在Task1后面执行,也就是Task2成了Task1的紧后任务。同理,Task1也就成了Task2的紧前任务。也就是代码中的addPredecessor方法,在添加紧后任务的同时也添加了紧前任务。

可能有人会问了,紧前任务添加了有什么用呢?难不成还倒退回去执行?

试想一下,如果有多个任务的紧后任务都是一个呢?比如这种情况:

builder.add(Task4).after(Task2,Task3)。Task4是Task2和Task3的紧后任务,所以在Task2执行完之后,还要判断Task3是否执行成功,然后才能执行Task4,这就是紧前任务列表的作用。这也就对应到上述代码中onPredecessorFinished方法的逻辑了。

然后这个紧后任务列表的排序是怎么排的呢?其实就是通过getExecutePriority方法获取task的执行优先级数字,按照正序排列,越小的任务执行时机越早。还记得之前配置的时候我设置了setExecutePriority方法吗,就是这里设置了优先级的。

至此主要逻辑就差不多了。好像还挺简单的是不是。还有一些细节我也简单的提下:

  • 各种回调:包括一些task的回调,project的回调。
  • 日志记录:比如耗时时间的记录,刚才执行任务时候的recordTime方法,就是记录了每个task的耗时。
  • 多种Task配置方法:除了上面用Java代码配置,还可以通过xml文件来配置Project和里面的Task,这个就主要是XmlPullParser类来解析xml数据,然后生成Prject。
  • 各种设计模式:比如构建Project的建造者模式,还有通过传入task名称就可以创建Task的工厂模式。

诸如此类的一些细节感兴趣朋友的可以自己下源码看看。

https://github.com/alibaba/alpha

最后用一张流程图总结下吧:

e6e7d715bfce2604a43da9fab6205862.png

总结

分析下来,这个异步启动框架应该算比较简单的,但是能解决问题啊!其实我们平时工作中也可以做一些积累,然后写成工具或者框架,如果能开源出来大家一起使用还真是一件不错的事情呢!


最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!

推荐阅读:

实际生产中的 Android SDK开发总结 拖不得了,Android11真的要来了,最全适配实践指南奉上 吹爆系列作者, 学习秘诀全公开!

f5cd31c0675e29ad01fd0722fbf16366.png

扫一扫 关注我的公众号

如果你想要跟大家分享你的文章,欢迎投稿~

┏(^0^)┛明天见!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值