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

5,038 阅读8分钟

1.为什么要用启动器

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

public class MyApplication extends Application {

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

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

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

但是如果有的方法自己需要依赖的方法执行完毕才能执行,比如 initJPushInterface() 可能需要先执行完毕 GetDeviceID() 执行完毕才能进行再执行,那么把它们都放入线程池里面并行执行就会产生问题,另外有的方法比如initBugly(); 必须先执行完它之后,主线程才能执行完毕,再跳转页面。那么因为这些问题,如果只是用线程池来并行,就会导致代码写起来过于复杂。

这也就是为什么要推出启动器的原因,当然阿里做的还是不错的,但是狗东用阿里做的启动器感觉怪怪的,所以跟着作者一起从零搭建一个启动器吧。


1.定义task接口

首先,我们要定义自己的一些task, 就是用来执行耗时方法的。先定义个接口吧。

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

    void run();

    /**
     * Task执行所在的线程池,可指定,一般默认
     */
    Executor runOnExecutor();

    /**
     * 存放需要先执行的task任务集合(也就是添加需要先执行的依赖)
     */
    List<Class<? extends ITask>> dependentArr();

    /**
     * 开始锁
     * */
    void startLock();

    /**
     * 解锁
     * */
    void unlock();

    /**
     * 异步线程执行的Task是否需要在被调用await的时候等待,默认不需要
     *
     * @return
     */
    boolean needWait();

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

    /**
     * Task主任务执行完成之后需要执行的任务
     */
    Runnable getTailRunnable();
}

好了,这些基本够用了。


2.实现task接口

public abstract class Task implements ITask {

    // 当前Task依赖的Task数量(需要等待被依赖的Task执行完毕才能执行自己),默认没有依赖
    private CountDownLatch taskCountDownLatch = new CountDownLatch(dependentArr() == null ? 0 : dependentArr().size());

    /**
     * 当前Task等待,让依赖的Task先执行
     */
    @Override
    public void startLock() {
        try {
            taskCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 依赖的Task执行完一个
     */
    @Override
    public void unlock() {
        taskCountDownLatch.countDown();
    }

    /**
     * 是否需要尽快执行,解决特殊场景的问题:一个Task耗时非常多但是优先级却一般,很有可能开始的时间较晚,
     * 导致最后只是在等它,这种可以早开始。
     */
    public boolean needRunAsSoon() {
        return false;
    }

    /**
     * Task的优先级,运行在主线程则不要去改优先级
     */
    @Override
    public int priority() {
        return Process.THREAD_PRIORITY_BACKGROUND;
    }

    /**
     * Task执行在哪个线程池,默认在IO的线程池;
     */
    @Override
    public ExecutorService runOnExecutor() {
        return DispatcherExecutor.getIOExecutor();
    }

    /**
     * 异步线程执行的Task是否需要在被调用await的时候等待,默认不需要
     */
    @Override
    public boolean needWait() {
        return false;
    }

    /**
     * 当前Task依赖的Task集合(需要等待被依赖的Task执行完毕才能执行自己),默认没有依赖
     */
    @Override
    public List<Class<? extends ITask>> dependentArr() {
        return null;
    }

    @Override
    public boolean runOnMainThread() {
        return false;
    }

    @Override
    public Runnable getTailRunnable() {
        return null;
    }
}

很简单,主要做的是:
1.dependentArr() 定义一个栅栏

很好理解,传入的task(我们的耗时任务),因为需要依赖,比如TaskA,必须得等TaskB,TaskC加载完毕才能加载TaskA,dependentArr()返回的就是TaskB,TaskC,也就是在TaskA中加了几个同步锁(锁的数量就是TaskA所需要依赖的Task数量),unlock()就减少一把锁。


3.实现启动器

外部调用

 TaskManager manager = TaskManager.getInstance(this);
        manager.add(new InitBuglyTask()) // 默认添加,并发处理
                .add(new InitBaiduMapTask())  // 在这里需要先处理了另外一个耗时任务initShareSDK,才能再处理它
                .add(new InitJPushTask())  // 等待主线程处理完毕,再进行执行
                .add(new InitShareTask())
                .start();
        manager.startLock();

构建启动器

public class TaskManager {

    private static TaskManager sInstance;
    private Context mContext;

    // 维持task和它的依赖Task的依赖关系,这里是仿照EventBus的存放事件的机制设计
    private HashMap<Class<? extends ITask>, ArrayList<ITask>> dependOfTaskArray = new HashMap<>();

    // 存放已经执行完毕的Task队列
    private volatile List<Class<? extends ITask>> taskFinishedArray = new ArrayList<>();

    // 存放所有的task
    private List<Task> taskAll = new ArrayList<>();
    private List<Class<? extends Task>> taskAllClazz = new ArrayList<>();

    // 需要在主线程中执行的Task队列
    private volatile List<Task> mainThreadTaskArray = new ArrayList<>();

    // 主线程需要等待先执行的task数量
    private AtomicInteger mainNeedWaitCount = new AtomicInteger();
    private CountDownLatch mCountDownLatch;

    private static final int WAITTIME_TIME = 996 * 31;
    private List<Future> futureArray = new ArrayList<>();

    private TaskManager(Context context) {
        if (context == null) {
            throw new IllegalArgumentException("Context is null.");
        }
        mContext = context;
    }

    /**
     * 使用单例模式创建对象
     */
    public static TaskManager getInstance(Context context) {

        if (sInstance == null) {
            sInstance = new TaskManager(context);
        }
        return sInstance;
    }

    /**
     * 添加任务
     */
    public TaskManager add(Task task) {

        if (task == null) {
            throw new IllegalArgumentException("task is null !");
        }
        
        // ->> 1
        setDependentOfTask(task);
        
        // ->> 2
        taskAll.add(task);
        taskAllClazz.add(task.getClass());

        // ->> 3
        // 非主线程且需要wait的
        if (ifNeedWait(task)) {
            // 主线程的锁加一把
            mainNeedWaitCount.getAndIncrement();
        }
        return this;
    }

    /**
     * 获取依赖的集合,主要做的为两件事
     *
     *  1.是以依赖类为Key,对应的依赖者的集合为value添加进map里面
     *  2.在从完成的任务集合里面查询,该task所依赖的类是否已经完成,完成的话进行解锁
     * */
    private void setDependentOfTask(Task task) {
        if (task.dependentArr() != null && task.dependentArr().size() > 0) {
            for (Class<? extends ITask> dependTaskClazz : task.dependentArr()) {
                if (dependOfTaskArray.get(dependTaskClazz) == null) {
                    dependOfTaskArray.put(dependTaskClazz, new ArrayList<ITask>());
                }

                // 如果该task所依赖的依赖任务已经加载过了,就解锁其中已经完成的
                dependOfTaskArray.get(dependTaskClazz).add(task);
                if (taskFinishedArray.contains(dependTaskClazz)) {
                    task.unlock();
                }
            }
        }
    }

    private boolean ifNeedWait(Task task) {
        return !task.runOnMainThread() && task.needWait();
    }

    @UiThread
    public void start() {
        if (Looper.getMainLooper() != Looper.myLooper()) {
            throw new RuntimeException("小子,启动器必须要在主线程启动");
        }
        if (taskAll.size() > 0) {

            // 4.->> 效率排序
            taskAll = TaskSortUtil.getSortResult(taskAll, taskAllClazz);
           
            // 5.->> 构建同步锁
            mCountDownLatch = new CountDownLatch(mainNeedWaitCount.get());

            // 6.->> 分发任务
            dispatchTasks();
            runOnMainThread();
        }
    }

    /**
     * task分发,根据设定的不同规则,分发到不同的线程
     */
    private void dispatchTasks() {
        for (final Task task : taskAll) {
            // 如果是需要在主线程中运行的,加入到主线程队列中
            if (task.runOnMainThread()) {
                mainThreadTaskArray.add(task);
            } else {
                // 异步线程中执行,是否执行取决于具体线程池
                Future future = task.runOnExecutor().submit(new TaskRunnable(task, this));
                futureArray.add(future);
            }
        }
    }

    private void runOnMainThread() {
        for (Task task : mainThreadTaskArray) {
            new TaskRunnable(task,this).run();
        }
    }

    @UiThread
    public void startLock() {
        try {
            if (mainNeedWaitCount.get() > 0) {
                mCountDownLatch.await(WAITTIME_TIME, TimeUnit.MILLISECONDS);
            }
        } catch (InterruptedException e) {
        }
    }

    /**
     * 取消
     * */
    public void cancel() {
        for (Future future : futureArray) {
            future.cancel(true);
        }
    }

    /**
     * 当完成一个任务之后,通知所有依赖它的任务,并解锁他们
     */
    public void unLockForChildren(Task task) {
        ArrayList<ITask> arrayList = dependOfTaskArray.get(task.getClass());
        if (arrayList != null && arrayList.size() > 0) {
            for (ITask subTask : arrayList) {
                subTask.unlock();
            }
        }
    }

   // 7 ->>
    public void finish(Task task) {
        if (ifNeedWait(task)) {
            taskFinishedArray.add(task.getClass());
            mCountDownLatch.countDown();
            mainNeedWaitCount.getAndDecrement();
        }
    }
}

首先是通过getInstance()构造了一个实例对象,然后通过addTask() 添加我们的Task, 如果它不为空的话

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

  1. 调用setDependentOfTask(),遍历该task所依赖的全部task,并且以它所依赖的task为Key, 对应的依赖者的集合为value添加进map里面,然后检查,该task中的依赖是否已经执行过了,如果已经执行过了,调用该task的unlock()方法减该task的一把锁。
  2. 然后将这个task和它的class文件添加到2个集合中,方便后面使用。
  3. 如果该Task需要主线程等其完成再执行的话,等待队列计数器+1
  4. 拓扑排序,经典的算法,用于描述依赖关系的排序,在上一章节有过介绍也给出过源码,这里就不再赘述
  5. 这里实际上就是构建一把锁,这个锁注意并不在Task里面,Task里面的锁,注意是为了先执行依赖的Task,执行完毕,再执行自己,而这里的锁是在启动器上,其作用是让主线程等待,优先执行那些必须要先执行完毕才能让主线程继续执行完毕,再跳转页面的task
  6. 根据需要分发不同的线程去执行,如果是需要在主线程中执行,那就先存储起来,如果是需要在一异步中执行,那就直接调用task.runOnExecutor()方法来异步执行耗时task,runOnExecutor()可复写,不写为默认线程池
  7. 如果该线程需要在主线程中执行,等待队列-1,添加进结束队列,如果该task需要主线程等待的话,主线程的同步锁-1

4.TaskRunnable的实现

好了如何执行任务尼?

/**
 * 任务真正执行的地方
 */
public class TaskRunnable implements Runnable {
    private Task task;
    private TaskManager taskManager;

    public TaskRunnable(Task task) {
        this.task = task;
    }

    public TaskRunnable(Task task, TaskManager taskManager) {
        this.task = task;
        this.taskManager = taskManager;
    }

    @Override
    public void run() {
        TraceCompat.beginSection(task.getClass().getSimpleName());
        Process.setThreadPriority(task.priority());

        task.startLock();
        task.run();

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

        if (!task.runOnMainThread()) {
            if(taskManager != null){
                taskManager.unLockForChildren(task);
                taskManager.finish(task);
            }
        }
        TraceCompat.endSection();
    }
}

好了,是不是很简单? 优先执行需要依赖的Task, 然后再执行自己,等都执行完毕后,调用 taskManager.unLockForChildren(mTask); 将该task从等待队列中移除,添加进结束队列,如果该task需要主线程等待的话,主线程的同步锁-1,等待队列数-1

再看下我们自己的task

public class InitJPushTask extends Task {

    @Override
    public boolean needWait() {
        return true;
    }
    
    @Override
    public List<Class<? extends ITask>> dependentArr() {
        List<Class<? extends ITask>> tasks = new ArrayList<>();
        tasks.add(InitShareTask.class);
        return tasks;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1500);
            System.out.println("InitJPushTask运行完毕,它所在的线程是:"+Thread.currentThread().getName());
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
}

我们自己的这个Task就写完看,这是一个需要先执行完毕GetDeviceIdTask, 然后需要执行完毕自己,才能允许Application去加载页面的任务,看是不是非常简单,看下Application的改造

   TaskManager manager = TaskManager.getInstance(this);
        manager.add(new InitBuglyTask()) // 默认添加,并发处理
                .add(new InitBaiduMapTask())  // 在这里需要先处理了另外一个耗时任务initShareSDK,才能再处理它
                .add(new InitJPushTask())  // 等待主线程处理完毕,再进行执行
                .add(new InitShareTask())
                .start();
        manager.startLock();

5.总结


6.END

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