Android性能优化实践——启动优化

写在前面

赶着学赶着写,实不实用就完事了!

这里的优化我们针对的是冷启动时的优化,有关冷启动等相关概念可以看这篇文章:APP启动方式分析——冷启动、热启动、温启动

而冷启动的第二阶段,创建app对象、启动主进程、创建MainActivity、渲染视图、执行onLayout、执行onDraw,这部分是我们能够真正控制的时间,即Application和Activity生命周期中进行的操作。


目录

  • 启动时间测量

  • traceview

  • 开屏白屏——Theme切换

  • 异步优化


启动时间测量

对启动进行优化,首先我们要学会如何去测量启动时间

1.adb命令

adb shell am start -W packagename/首屏Activity
adb命令有问题的可以看一下这篇文章:adb问题

Android性能优化实践——启动优化_第1张图片

运行后是这个样子,我们会得到三个数据,ThisTime、TotalTime和WaitTime

ThisTime:最后一个Activity启动耗时
TotalTime:所有Activity启动耗时
WaitTime:AMS启动Activity的总耗时

这种方法测量,在线下使用方便,但是不能带到线上运行,且测量的时间不是精确时间

2.手动埋点

原理:启动时埋点,启动结束埋点,计算二者差值

误区:onWindowFocusChanged只是首帧时间
正确:真实数据展示,Feed第一条展示

Android性能优化实践——启动优化_第2张图片

Android性能优化实践——启动优化_第3张图片

这种方法测量的时间精确,且可带到线上


traceview

traceview可以通过图形的形式展示执行时间、调用栈等,信息全面,包含所有线程

使用方式:

Debug.startMethodTracing(getFilesDir() + "/App.trace");

// code...

Debug.stopMethodTracing(getFilesDir() + "/App.trace");

这里最好手动为其设置trace文件输出路径,如果出现问题找不到trace文件可以看一下这篇文章:解决使用Debug.startMethodTracing后找不到对应的.trace文件

Android性能优化实践——启动优化_第4张图片

打开trace文件:

Android性能优化实践——启动优化_第5张图片

这里的调用关系是上面的函数调用了下面的函数,而对应的颜色:

橙色:系统
绿色:应用自身的函数调用
蓝色:第三方

可以通过右键->jump to source看对应代码

traceview测量的运行时间不是准确的运行时间,因为traceview运行时开销严重,整体都会变慢:会抓取当前运行的所有线程的所有执行函数。如果过度依赖traceview的时间分析,可能会带偏优化方向。


Theme切换

Theme切换是针对启动时的白屏问题,因为此时是系统运行时间,不可控,所以这里的优化不是真正的缩短运行时间,而是给人感觉启动时间快。

具体操作方式:给首屏Activity添加一个style,背景自定义,这样开屏就会显示出想要的样子;首屏Activity的onCreate中,在setContentView前将style改回。

Android性能优化实践——启动优化_第6张图片

Android性能优化实践——启动优化_第7张图片

Android性能优化实践——启动优化_第8张图片


异步优化

此处的异步优化针对的是Application在启动时需要初始化的n多任务,此处占用了App启动的很多时间。

核心思想:子线程分担主线程任务,并行减少时间;将线性的多个任务,改为多线程并行,缩短时间

自定义启动器
核心思想:充分利用CPU多核的能力;自动梳理任务顺序
1)代码Task化,启动逻辑抽象为Task
2)根据所有任务依赖关系排序生成一个有向无环图
3)多线程按照排序后的优先级依次执行
这里要注意:
1)初始化是否符合异步要求
2)Task之间的先后依赖关系
3)区分CPU密集型和IO密集型

代码实现

代码连接:LaunchOptimizeManager.java
首先最开始列出所有的任务,为其定义唯一的名称,String[] allTasks用来统计所有的Task(后面要将未执行的Task筛选掉,防止出现我注释掉了一个Task,导致需要他先执行的后面的Task无法执行的情况出现)。我们是通过拓扑排序来决定每个Task的执行顺序的,void topologicalSort(List tasks)中的逻辑分为一下几步:

1.使用多线程的线程池去执行Task:无前置任务的Task才能够执行,即能够执行的Task之间不会相互依赖,使用多线程执行可减少执行时间;线程池事先封装好,方便管理
2.队列+boolean[],轮询所有Task
3.每个Task的实际执行就是一个Runnable,再次封装增加完成的回调
4.AtomicInteger记录完成数量,作为循环跳出条件
5.增加TIME_OUT防卡死

/**
 * @author Johnny Deng
 * @version 1.0
 * @description 优化启动
 * @date 2020/6/25 17:50
 */
public class LaunchOptimizeManager {
    private static final int TIME_OUT = 5000;

    public static final String TASK_LEAKCANARY = "task_leakcanary";
    public static final String TASK_BMOB = "task_bmob";
    public static final String TASK_BUGLY = "task_bugly";
    public static final String TASK_ANDFIX = "task_andfix";

    private static String[] allTasks = new String[] {
            TASK_LEAKCANARY, TASK_BMOB, TASK_BUGLY, TASK_ANDFIX};

    /**
     * 拓扑排序执行启动项
     *
     * @param tasks tasks
     */
    public static void topologicalSort(List tasks) {
        if (tasks == null || tasks.size() == 0) {
            return;
        }
        
        // 执行时间
        long startTime = System.currentTimeMillis();

        // 统计当前完成的数量
        AtomicInteger count = new AtomicInteger();

        ExecutorService executorService = ThreadUtils.getCachedThreadPool();
        Set set = new HashSet<>();
        LinkedList queue = new LinkedList<>();

        // 任务执行后,刷新排序图
        Callback callback = name -> {
            synchronized (tasks) {
                count.getAndIncrement();
                for (LaunchTask task : tasks) {
                    if (task.pres.size() > 0) {
                        task.pres.remove(name);
                        if (task.pres.size() == 0) {
                            queue.add(task);
                            tasks.remove(task);
                        }
                    }
                }
            }
        };

        for (LaunchTask task : tasks) {
            set.add(task.name);
            task.setCallback(callback);
        }

        // 排除未执行的Task
        if (set.size() < allTasks.length) {
            for (String sT : allTasks) {
                if (!set.contains(sT)) {
                    for (LaunchTask task : tasks) {
                        task.pres.remove(sT);
                    }
                }
            }
        }

        // TODO 环

        // 将可以执行的任务加入队列中
        for (int i = tasks.size() - 1; i >= 0; i --) {
            LaunchTask task = tasks.get(i);
            if (task.pres.isEmpty()) {
                queue.add(task);
                tasks.remove(task);
            }
        }

        while (!queue.isEmpty() || !tasks.isEmpty() || count.get() < tasks.size()) {
            long cur = System.currentTimeMillis();
            if (cur - startTime > TIME_OUT) {
                LogUtils.e("App Launch up Time out!");
                break;
            }

            if (queue.isEmpty()) {
                continue;
            }

            LaunchTask task = queue.poll();
            executorService.submit(task);
        }
    }

    public static class LaunchTask implements Runnable {
        private Runnable runnable;
        private List pres;
        private String name;
        private Callback callback;

        public LaunchTask(Runnable runnable, List pres, String name) {
            this.runnable = runnable;
            this.pres = pres;
            this.name = name;
        }

        public void setCallback(Callback callback) {
            this.callback = callback;
        }

        @Override
        public void run() {
            runnable.run();
            if (callback != null) {
                callback.onComplete(name);
            }
        }
    }

    public interface Callback {
        void onComplete(String name);
    }
}

最后贴一下项目地址:jio-deng/FFmpegInAndroid

持续更新~

你可能感兴趣的:(Android优化系列,android,启动优化,异步,启动白屏黑屏,启动白屏,启动黑屏)