转载

更快!更高效!异步启动框架Alpha完全解析

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

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

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

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

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

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

其中 异步任务 是很重要的一环,这里就涉及到我们今天要讲的内容了, 启动器 。顾名思义就是帮我们优化启动的一个工具,可以 高效合理 的帮我们安排启动过程中的一些任务处理。

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

作为一个启动器,该有什么功能

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

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

这是个啥啊?我晕了。这么多关系,我该怎么处理? 既然文字看着太麻烦,就画个图吧,这里涉及到一个用于 时间管理的图形——Pert图 。 Pert 图是一个有向图,能清晰地描述子任务之间的 依赖关系 。比如我们这个项目的情况,画成Pert 图如下:

更快!更高效!异步启动框架Alpha完全解析
Pert图.jpg

通过 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的内部源码。

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 < MAIN_PROCESS_MODE || 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<Task> tasks) {
        if (tasks.size() <= 1) {
            return;
        }
        Collections.sort(tasks, sTaskComparator);
    }    
    
    private static Comparator<Task> sTaskComparator = new Comparator<Task>() {
        @Override
        public 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的工厂模式。

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

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

更快!更高效!异步启动框架Alpha完全解析
Alpha流程图.jpg

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

你的一个:+1:,就是我分享的动力:heart:。

原文  https://juejin.im/post/5ef018e2f265da02d96ac81b
正文到此结束
Loading...