一线大厂资深APP性能优化系列(二)异步优化与拓扑排序

executorService.submit(new Runnable() {

@Override
public void run() {

initShareSDK();
}
});
// Debug.stopMethodTracing();
}
复制代码

经过测试,现在打开页面基本是秒开的。

但是又出现了问题,比如有的是必须要先执行完毕才能进入页面的。也就是说,得先让它执行完毕,才能进入主页。

4.第二次改造(CountDownLatch)

很简单-加锁就行,关于锁的介绍就不在这里赘述了,有很多,但是作者用的是CountDownLatch, 又称之为门栓,构造函数就是指定门栓的个数, latch.countDown(); 每次调用都减少一个门栓数, latch.await(); 就是开启等待,当门栓数为0时,就放开去执行下面的逻辑。

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

// Debug.startMethodTracing(“MyApplication”);
final CountDownLatch latch = new CountDownLatch(1);
ExecutorService executorService = Executors.newFixedThreadPool(CORE_POOL_SIZE);

executorService.submit(new Runnable() {

@Override
public void run() {

initBugly();
latch.countDown();
}
});

executorService.submit(new Runnable() {

@Override
public void run() {

initBaiduMap();
}
});

executorService.submit(new Runnable() {

@Override
public void run() {

initJPushInterface();
}
});

executorService.submit(new Runnable() {

@Override
public void run() {

initShareSDK();
}
});

try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// Debug.stopMethodTracing();
}
复制代码

这样就行了,只有当initBugly执行完毕才能继续跳转页面,当然值得补充的是,之所以要加门栓,是因为在onCreate方法里面,可能还有其他必须在主线程才能初始化的其他耗时任务,而initBugly可以不需要在主线程里面初始化,但是又必须得初始化完毕才能跳转页面。所以为了不再增加时间,才启动线程池+门栓去初始化

好了,既加快了速度,又可以保证一些不需要在主线程优化而又启动之前必须初始化完成的任务不出问题,

但是尼,这么写,有点Low,而且如果有的耗时方法存在关联,比如必须先执行完A,根据A的返回值,再执行B,然后根据B的返回再执行C,那么必须串联的话,我们该如何优化尼?

5.第三次改造(启动器)

先看图,目的就是进行任务分类

看了图,是不是略微明白了?我们的任务基本就是这样的,有的必须要在所以任务之前初始化,有的必须要在主线程初始化,有的可以有空在初始化,有的必须要在有的任务执行完毕再初始化,比如激光推送需要设备ID,那么就必须要在获取设备ID这个方法执行完才能执行,所以我们要对耗时任务先分类。

于是有了上图

  • head task : 我们会把一些必须先启动的task放到这里
  • 主线程:将必须要在主线程中初始化的task放入这里
  • 并发:将非必须在主线程中初始化的task放入这里
  • tall task: 一些在所有任务执行完毕之后才去初始化的放入这里,比如一些log打印等
  • ilde task: 通过字面就知道了将一些可以有空再初始化的task放入这里

好了分好类了之后,我们发现如果用常规的方式去做,比如线程池+门栓就很麻烦了,而且效率不高,为了极致的性能体验,我们会自己做一个启动器

先看下我们完善后的代码

// 使用启动器的方式
TaskDispatcher.init(this);
TaskDispatcher instance = TaskDispatcher.createInstance();
instance.addTask(new initBuglyTask()) // 默认添加,并发处理
instance.addTask(new initBaiduMapTask()) // 在这里需要先处理了另外一个耗时任务initShareSDK,才能再处理它
instance.addTask(new initJPushInterface()) // 等待主线程处理完毕,再进行执行
.start();

instance.await();
复制代码

看无语伦比的简单,无论是需要别的任务执行完再执行的继承关系,还是必须主线程执行完的等待,还是可以并发的执行,在定义task里面,只需要一个方法即可。

5.1 启动器

什么是启动器?为啥用它?

在应用启动的时候,我们通常会有很多工作需要做,为了提高启动速度,我们会尽可能让这些工作并发进行。但这些工作之间可能存在前后依赖的关系,所以我们又需要想办法保证他们执行顺序的正确性,很麻烦。

虽然阿里也出了个启动器 alibaba / alpha

但是尼,在狗东用阿里的框架总感觉怪怪的,接下来的时间就带领大家去打造一款属于自己的启动器。

要想做启动器,首先是要解决一些依赖关系,比如,我们传入的任务是A,B,C但是尼,如果A依赖于B,那么就需要先初始化B,同时处理C,然后再处理A。

怎么排序尼,这个有个专业的名称,叫做了 任务的有向无环图的拓扑排序

不知道的去看这里 有向无环图的拓扑排序

好了,我先贴出算法的代码,有个简单的认识便可,下期带领大家一步一步的打架自己的启动器。

TaskSortUtil.java

public class TaskSortUtil {

private static List sNewTasksHigh = new ArrayList<>();// 高优先级的Task

/**

  • 任务的有向无环图的拓扑排序
  • @return
    */
    public static synchronized List getSortResult(List originTasks,
    List<Class<? extends Task>> clsLaunchTasks) {
    long makeTime = System.currentTimeMillis();

Set dependSet = new ArraySet<>();
Graph graph = new Graph(originTasks.size());
for (int i = 0; i < originTasks.size(); i++) {
Task task = originTasks.get(i);
if (task.isSend() || task.dependsOn() == null || task.dependsOn().size() == 0) {
continue;
}
for (Class cls : task.dependsOn()) {
int indexOfDepend = getIndexOfTask(originTasks, clsLaunchTasks, cls);
if (indexOfDepend < 0) {
throw new IllegalStateException(task.getClass().getSimpleName() +
" depends on " + cls.getSimpleName() + " can not be found in task list ");
}
dependSet.add(indexOfDepend);
graph.addEdge(indexOfDepend, i);
}
}
List indexList = graph.topologicalSort();
List newTasksAll = getResultTasks(originTasks, dependSet, indexList);

DispatcherLog.i("task analyse cost makeTime " + (System.currentTimeMillis() - makeTime));
printAllTaskName(newTasksAll);
return newTasksAll;
}

@NonNull
private static List getResultTasks(List originTasks,
Set dependSet, List indexList) {
List newTasksAll = new ArrayList<>(originTasks.size());
List newTasksDepended = new ArrayList<>();// 被别人依赖的
List newTasksWithOutDepend = new ArrayList<>();// 没有依赖的
List newTasksRunAsSoon = new ArrayList<>();// 需要提升自己优先级的,先执行(这个先是相对于没有依赖的先)
for (int index : indexList) {
if (dependSet.contains(index)) {
newTasksDepended.add(originTasks.get(index));
} else {
Task task = originTasks.get(index);
if (task.needRunAsSoon()) {
newTasksRunAsSoon.add(task);
} else {
newTasksWithOutDepend.add(task);
}
}
}
// 顺序:被别人依赖的—》需要提升自己优先级的—》需要被等待的—》没有依赖的
sNewTasksHigh.addAll(newTasksDepended);
sNewTasksHigh.addAll(newTasksRunAsSoon);
newTasksAll.addAll(sNewTasksHigh);
newTasksAll.addAll(newTasksWithOutDepend);
return newTasksAll;
}

private static void printAllTaskName(List newTasksAll) {
if (true) {
return;
}
for (Task task : newTasksAll) {
DispatcherLog.i(task.getClass().getSimpleName());
}
}

public static List getTasksHigh() {
return sNewTasksHigh;
}

/**

  • 获取任务在任务列表中的index
  • @return
    */
    private static int getIndexOfTask(List originTasks,
    List<Class<? extends Task>> clsLaunchTasks, Class cls) {
    int index = clsLaunchTasks.indexOf(cls);
    if (index >= 0) {
    return index;
    }

// 仅仅是保护性代码
final int size = originTasks.size();
for (int i = 0; i < size; i++) {
if (cls.getSimpleName().equals(originTasks.get(i).getClass().getSimpleName())) {
return i;
}
}
return index;
}
}

复制代码

Graph.java

/**

  • 有向无环图的拓扑排序算法
    */
    public class Graph {
    //顶点数
    private int mVerticeCount;
    //邻接表
    private List[] mAdj;

public Graph(int verticeCount) {
this.mVerticeCount = verticeCount;
mAdj = new ArrayList[mVerticeCount];
for (int i = 0; i < mVerticeCount; i++) {
mAdj[i] = new ArrayList();
}
}

/**

  • 添加边

*/

面试复习笔记

这份资料我从春招开始,就会将各博客、论坛。网站上等优质的Android开发中高级面试题收集起来,然后全网寻找最优的解答方案。每一道面试题都是百分百的大厂面经真题+最优解答。包知识脉络 + 诸多细节。
节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

《960页Android开发笔记》

《1307页Android开发面试宝典》

包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。

[外链图片转存中…(img-ewhJSIP2-1720115535737)]

《507页Android开发相关源码解析》

只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。

真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。

[外链图片转存中…(img-lw2UUgh0-1720115535737)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值