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

1.为何要用启动器

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

public class MyApplication extends Application {

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

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

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

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

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


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() 定义一个栅栏ide

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


3.实现启动器

外部调用post

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的容灾方案,前几天有人联系我,想让我把接下来的内容写书,犹豫了好久,不知道大家是爱看书,仍是喜欢看博客,欢迎在底下留言

相关文章
相关标签/搜索