一线大厂大型APP性能优化系列-自定义启动器(三)

1.为何要用启动器

为何要作启动器?直接写它不香吗?来先回顾下恶心的代码结构java

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        // 一堆耗时方法,严重影响启动
        initBugly();
        initBaiduMap();
        initJPushInterface();
        initShareSDK();
    }
}
复制代码

面对这些比较恶心的启动方法,为了加快启动,咱们通常会采用线程池的方式启动,一线大厂资深APP性能优化系列-异步优化与拓扑排序(二)算法

可是若是有的方法本身须要依赖的方法执行完毕才能执行,好比 initJPushInterface() 可能须要先执行完毕 GetDeviceID() 执行完毕才能进行再执行,那么把它们都放入线程池里面并行执行就会产生问题,另外有的方法好比initBugly(); 必须先执行完它以后,主线程才能执行完毕,再跳转页面。那么由于这些问题,若是只是用线程池来并行,就会致使代码写起来过于复杂。性能优化

这也就是为何要推出启动器的缘由,固然阿里作的仍是不错的,可是狗东用阿里作的启动器感受怪怪的,因此跟着做者一块儿从零搭建一个启动器吧。bash


1.定义task接口

首先,咱们要定义本身的一些task, 就是用来执行耗时方法的。先定义个接口吧。并发

/** * @author: lybj * @date: 2020/5/14 * @Description: */
public interface ITask {

    void run();

    /** * Task执行时所在的线程池,能够指定,通常使用默认 */
    Executor runOn();

    /** * 存放须要先执行的task任务集合 */
    List<Class<? extends Task>> dependsOn();

    /** * 该task若是是异步任务,是否须要先等待本身完成后, * 主线程在继续,也就是是否须要在主线程调用await的时候等待 */
    boolean needWait();

    /** * 是否在主线程执行 */
    boolean runOnMainThread();

    /** * 只能在主进程执行 */
    boolean onlyOnMainProcess();

    /** * Task主任务执行完成以后须要执行的任务 */
    Runnable getTailRunnable();

    /** * Task执行过程当中的回调 */
    void setTaskCallBack(TaskCallBack callBack);
}
复制代码

好了,这些基本够用了。异步


2.实现task接口

public abstract class Task implements ITask {

    private volatile boolean mIsWaiting; // 是否正在等待
    private volatile boolean mIsRunning; // 是否正在执行
    private volatile boolean mIsFinished; // Task是否执行完成
    private volatile boolean mIsSend; // Task是否已经被分发

    // 当前Task依赖的Task数量(须要等待被依赖的Task执行完毕才能执行本身),默认没有依赖
    private CountDownLatch mDepends = new CountDownLatch(dependsOn() == null ? 0 : dependsOn().size());

    /** * 依赖的Task执行完一个 */
    public void satisfy() {
        mDepends.countDown();
    }
    
     /** * 当前Task等待,让依赖的Task先执行 */
    public void waitToSatisfy() {
        try {
            mDepends.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    /** * 异步线程执行的Task是否须要在被调用await的时候等待(也就是是否须要主线程等你执行完再执行),默认不须要 * * @return */
    @Override
    public boolean needWait() {
        return false;
    }

    /** * 当前Task依赖的Task集合(须要等待被依赖的Task执行完毕才能执行本身),默认没有依赖 * * @return */
    @Override
    public List<Class<? extends Task>> dependsOn() {
        return null;
    }
}
复制代码

很简单,主要作的是:
1.根据dependsOn() 定义一个栅栏ide

很好理解,传入的task(咱们的耗时任务),由于须要依赖,好比TaskA,必须得等TaskB,TaskC加载完毕才能加载TaskA,那么dependsOn()返回的就是TaskB,TaskC,也就是在TaskA中加了几个同步锁(锁的数量就是TaskA所须要依赖的Task数量),每次执行satisfy()就减小一把锁。oop


3.实现启动器

外部调用post

TaskDispatcher instance = TaskDispatcher.createInstance();
        instance.addTask(new InitBuglyTask()) // 默认添加,并发处理
                .addTask(new InitBaiduMapTask())  // 在这里须要先处理了另一个耗时任务initShareSDK,才能再处理它
                .addTask(new InitJPushTask())  // 等待主线程处理完毕,再进行执行
                .start();
        instance.await();
复制代码

构建启动器性能

public class TaskDispatcher {

    private static Context mContext;
    private static boolean sHasInit;
    private static boolean sIsMainProcess;
   
    // 存放依赖
    private HashMap<Class<? extends Task>, ArrayList<Task>> mDependedHashMap = new HashMap<>();
    
    // 存放全部的task
    private List<Task> mAllTasks = new ArrayList<>();
    private List<Class<? extends Task>> mClsAllTasks = new ArrayList<>();
    
    // 调用了await的时候还没结束的且须要等待的Task队列
    private List<Task> mNeedWaitTasks = new ArrayList<>();
    
    // 已经结束了的Task队列
    private volatile List<Class<? extends Task>> mFinishedTasks = new ArrayList<>(100);

    // 须要在主线程中执行的Task队列
    private volatile List<Task> mMainThreadTasks = new ArrayList<>();
    
    // 保存须要Wait的Task的数量
    private AtomicInteger mNeedWaitCount = new AtomicInteger();
    private CountDownLatch mCountDownLatch;
   
   
    /** * 注意:每次获取的都是新对象 */
    public static TaskDispatcher getInstance(Context context) {

        if (context != null) {
            mContext = context;
            sHasInit = true;
            sIsMainProcess = Utils.isMainProcess(mContext);
        }
        return new TaskDispatcher();
    }

    /** * 添加任务 */
    public TaskDispatcher addTask(Task task) {

        if (task != null) {
        
            // ->> 1
            collectDepends(task);
            
            // ->> 2
            mAllTasks.add(task);
            mClsAllTasks.add(task.getClass());
            // ->> 3
            if (ifNeedWait(task)) {
                mNeedWaitTasks.add(task);
                mNeedWaitCount.getAndIncrement();
            }
        }
        return this;
    }

    /** * 存放相关依赖信息 * */
    private void collectDepends(Task task) {

        // 若是存在依赖
        if (task.dependsOn() != null && task.dependsOn().size() > 0) {

            // 获取依赖
            for (Class<? extends Task> cls : task.dependsOn()) {
                if (mDependedHashMap.get(cls) == null) {
                    mDependedHashMap.put(cls, new ArrayList<Task>());
                }
                mDependedHashMap.get(cls).add(task);
                if (mFinishedTasks.contains(cls)) {
                    task.satisfy();
                }
            }
        }
    }
    
    /** * task 是否须要主线程等其完成再执行 * */
    private boolean ifNeedWait(Task task) {
        return !task.runOnMainThread() && task.needWait();
    }
    
    @UiThread
    public void start() {
    
        if (Looper.getMainLooper() != Looper.myLooper()) {
            throw new RuntimeException("小子,启动器必需要在主线程启动");
        }
        if (mAllTasks.size() > 0) {
        
            // 4.->> 查看被依赖的信息
            printDependedMsg();
            
            // 5.->> 拓扑排序并返回
            mAllTasks = TaskSortUtil.getSortResult(mAllTasks, mClsAllTasks);
            
            // 6.->> 构建同步锁
            mCountDownLatch = new CountDownLatch(mNeedWaitCount.get());

            // 7.->> 分发task
            dispatchTasks();
            executeTaskMain();
        }
    }
    
    /** * 查看被依赖的信息 */
    private void printDependedMsg() {
        DispatcherLog.i("needWait size : " + (mNeedWaitCount.get()));
        if (false) {
            for (Class<? extends Task> cls : mDependedHashMap.keySet()) {
                DispatcherLog.i("cls " + cls.getSimpleName() + " " + mDependedHashMap.get(cls).size());
                for (Task task : mDependedHashMap.get(cls)) {
                    DispatcherLog.i("cls " + task.getClass().getSimpleName());
                }
            }
        }
    }
    
    /** * task分发,根据设定的不一样规则,分发到不一样的线程 */
    private void dispatchTasks() {
        for (Task task : mAllTasks) {
        
            if (task.runOnMainThread()) {
                mMainThreadTasks.add(task);

                if (task.needCall()) {
                    task.setTaskCallBack(new TaskCallBack() {
                        @Override
                        public void call() {

                            TaskStat.markTaskDone();
                            task.setFinished(true);
                            satisfyChildren(task);
                            markTaskDone(task);
                    }
                });
            }
        } else {
            // 异步线程中执行,是否执行取决于具体线程池
            Future future = task.runOn().submit(new DispatchRunnable(task,this));
            mFutures.add(future);
        }
    }
    
    /** * 从等待队列中移除,添加进结束队列 */
    public void markTaskDone(Task task) {
    
        // 8 ->>
        if (ifNeedWait(task)) {
            mFinishedTasks.add(task.getClass());
            mNeedWaitTasks.remove(task);
            mCountDownLatch.countDown();
            mNeedWaitCount.getAndDecrement();
        }
    }
    
    private void executeTaskMain() {
        mStartTime = System.currentTimeMillis();
        for (Task task : mMainThreadTasks) {
            long time = System.currentTimeMillis();
            new DispatchRunnable(task,this).run();
        }
    }
复制代码

首先是经过getInstance()构造了一个实例对象,而后经过addTask() 添加咱们的Task, 若是它不为空的话

根据上面的角标,逐一介绍

  1. 调用collectDepends(),遍历该task所依赖的所有task,而且以它所依赖的task为Key, 自己为Value中集合元素的一员添加进去,而后判断,该task中的依赖是否已经加载过了,若是加载过了,调用该task的satisfy()方法减该task的一把锁。
  2. 而后将这个task和它的class文件添加到2个集合中,方便后面使用。
  3. 若是该Task须要主线程等其完成再执行的话,则添加到等待队列中,等待队列计数器+1
  4. 打印该task所依赖的信息
  5. 拓扑排序,经典的算法,用于描述依赖关系的排序,在上一章节有过介绍也给出过源码,这里就再也不赘述
  6. 这里实际上就是构建一把锁,这个锁注意并不在Task里面,Task里面的锁,注意是为了先执行依赖的Task,执行完毕,再执行本身,而这里的锁是在启动器上,其做用是让主线程等待,优先执行那些必需要先执行完毕才能让主线程继续执行完毕,再跳转页面的task
  7. 根据须要分发不一样的线程去执行,若是是须要在主线程中执行,那就先存储起来,若是是须要在一部现场中执行,那就直接调用task.runOn()方法来异步执行耗时task,runOn()可复写,不写为默认线程池
  8. 若是该线程须要在主线程中执行,将它从等待队列中移除,添加进结束队列,若是该task须要主线程等待的话,主线程的同步锁-1,等待队列数-1

4.DispatchRunnable的实现

好了如何执行任务尼?

public class DispatchRunnable implements Runnable {
    private Task mTask;
    private TaskDispatcher mTaskDispatcher;

    public DispatchRunnable(Task task) {
        this.mTask = task;
    }
    public DispatchRunnable(Task task,TaskDispatcher dispatcher) {
        this.mTask = task;
        this.mTaskDispatcher = dispatcher;
    }

    @Override
    public void run() {

        Process.setThreadPriority(mTask.priority());

        long startTime = System.currentTimeMillis();

        mTask.setWaiting(true);
        mTask.waitToSatisfy();

        long waitTime = System.currentTimeMillis() - startTime;
        startTime = System.currentTimeMillis();

        // 执行Task
        mTask.setRunning(true);
        mTask.run();

        // 执行Task的尾部任务
        Runnable tailRunnable = mTask.getTailRunnable();
        if (tailRunnable != null) {
            tailRunnable.run();
        }

        if (!mTask.needCall() || !mTask.runOnMainThread()) {
            printTaskLog(startTime, waitTime);

            TaskStat.markTaskDone();
            mTask.setFinished(true);
            if(mTaskDispatcher != null){
                mTaskDispatcher.satisfyChildren(mTask);
                
                // --> 8
                mTaskDispatcher.markTaskDone(mTask);
            }
        }
        TraceCompat.endSection();
    }
}
复制代码

好了,是否是很简单? 优先执行须要依赖的Task, 而后再执行本身,等都执行完毕后,调用mTaskDispatcher.markTaskDone(mTask); 将该task从等待队列中移除,添加进结束队列,若是该task须要主线程等待的话,主线程的同步锁-1,等待队列数-1

再看下咱们本身的task

public class InitJPushTask extends Task {

    @Override
    public boolean needWait() {
        return true;
    }

    @Override
    public List<Class<? extends Task>> dependsOn() {
        List<Class<? extends Task>> tasks = new ArrayList<>();
        tasks.add(GetDeviceIdTask.class);
        return tasks;
    }

    @Override
    public void run() {
        // 模拟InitJPush初始化
        try {
            Thread.sleep(1500);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}
复制代码

咱们本身的这个Task就写完看,这是一个须要先执行完毕GetDeviceIdTask, 而后须要执行完毕本身,才能容许Application去加载页面的任务,看是否是很是简单,看下Application的改造

TaskDispatcher instance = TaskDispatcher.createInstance();
        instance.addTask(new InitBuglyTask()) // 默认添加,并发处理
                .addTask(new InitBaiduMapTask())  // 在这里须要先处理了另一个耗时任务initShareSDK,才能再处理它
                .addTask(new InitJPushTask())  // 等待主线程处理完毕,再进行执行
                .addTask(new GetDeviceIdTask())
                .start();
        instance.await();
复制代码

5.总结


6.END

这个启动器目前已经在某厂的一个比较成熟的项目中使用了,目测仍是蛮好用的,确实比以前启动速度提高了不少,大约能提高到3秒,还有其他的大概18-19章节,包含不少核心的优化及大型APP的容灾方案,前几天有人联系我,想让我把接下来的内容写书,犹豫了好久,不知道大家是爱看书,仍是喜欢看博客,欢迎在底下留言

相关文章
相关标签/搜索