Part 2 App启动优化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zsjlovesm521/article/details/87929876

Part 2 App启动优化

一 App启动优化介绍

1、背景

用户的第一体验,八秒定律(70%用户放弃等待)

2、启动分类

App startup time官方教程(https://developer.android.google.cn/topic/performance/vitals/launch-time)

冷启动

耗时最多,衡量标准

ClickEvent(用户点击) -> IPC(触发ipc) -> Process.start(进程的创建) -> ActivityThread(单独进程app的入口类似main) -> bindApplication(通过反射创建Application) -> LifeCycle(我们的activity) -> ViewRootImpl(界面的绘制)

热启动

最快

后台 -> 前台

温启动

较快

LifeCycle(只重走activity的生命周期)

3、相关任务

冷启动之前

启动app、加载空白window、创建进程(这三个都是系统行为,无法多做干涉)

随后任务

创建Application、创建主线程、创建MainActivity、加载布局、布置屏幕、首帧绘制

4、优化方向

Application和Activity的生命周期(我们开发者能控制的)

二 启动时间测量方式

1、adb命令方式

adb shell am start -W packagename/首屏Activity

ThisTime:最后一个Activity的启动耗时

TotalTime:所有Activity的启动耗时

WaitTime:AMS启动Activity的总耗时

特点:线下使用方便,不能带到线上、非严谨精确时间

adb命令显示的启动时间

2、手动打点方式

启动时埋点,启动结束埋点,二者差值

工具类
public class LaunchTimer {
    private static long sTime;

    public static void startRecord() {
        sTime = System.currentTimeMillis();
    }

    public static void endRecord() {
        endRecord("");
    }

    public static void endRecord(String msg) {
        long cost = System.currentTimeMillis() - sTime;
        LogUtils.i(msg + "cost " + cost);
    }
}

启动位置放置在Application的attachBaseContext中,结束位置放置在MainActivity中

特点:精确,可带到线上,推荐使用

误区:onWindowFocusChange只是首帧时间

正解:真实数据展示,Feed第一条数据展示

三 启动优化工具选择

1、traceview

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

使用方式

    第一种:使用Android studio的Android profiler点击cpu就可以查看
    第二种:嵌入代码
    Debug.startMethodTracing("文件名");//使用
    Debug.stopMethodTracing();//结束
    生成文件在sd卡:Android/data/packagename/files可以直接DeviceFileExplorer中找到
    或者通过adb pull /mnt/sdcard/文件名.trace 指定文件目录中.将trace导出指定的文件夹中

在这里插入图片描述
参考文档:https://www.jianshu.com/p/7e9ca2c73c97 (Android性能优化—TraceView的使用)

总结:traceview运行时开销严重,整体都会变慢(会抓取所有线程所有函数的状态和时间),可能会带偏优化方向(影响某些函数),可在代码中埋点使用(使用方便)

2、systrace

结合Android内核的数据,生成Html报告。API18以上使用,推荐TraceCompat

使用方式

    python systrace.py -t 10 [other-options][categories]     python脚本
    https://developer.android.google.cn/studio/command-line/systrace
    
    python systrace.py -b 32768 -t 5 -a com.optimize.performance -o performance.html

参考文档:https://blog.csdn.net/itfootball/article/details/48915935 (Android性能专项测试之Systrace工具)

cputime与walltime的区别(cputime是优化的核心)
walltime:是代码执行的时间
cputime:是代码消耗cpu的时间(重点指标)
举例:锁冲突

总结:轻量级,开销小。直观反映cpu利用率。

四 优雅获取方法耗时讲解

1、常规方法

背景:需要知道启动阶段所有方法耗时
实现:手动埋点

    开始 long time = System.currentTimeMillis();
    结束 long cost = System.currentTimeMillis()-time;

总结:侵入性强,工作量大

2、AOP介绍

Aspect Oriented Programming 面向切片编程
针对同一类问题的同一处理
无侵入添加代码

AspectJ使用
classpath ‘com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0’ (项目的build.gradle下的dependencies)
implementation ‘org.aspectj:aspectjrt:1.8.+’
apply plugin: ‘android-aspectjx’

Join Points

程序运行时的执行点,可以作为切面的地方

函数调用、执行
获取或者设置变量
类初始化

PointCut

带条件的JoinPoints

Advice

一种Hook,要插入代码的位置

before:PointCut之前执行
after:PointCut之后执行
Around:PointCut之前之后分别执行

语法介绍

Before:Advice,具体插入位置
execution:处理Join Point的类型,call、execution
(* android.app.Activity.on**(…)) 匹配规则
onActivityCalled 要插入的代码

参考文档
https://www.jianshu.com/p/f577aec99e17 (关于android中使用AspectJ)
https://blog.csdn.net/qq_30682027/article/details/82493913 (AspectJ详解)
https://www.jianshu.com/p/27b997677149 (AspectJ基本用法)

3、AOP实战

实战

@Aspect
public class PerformanceAop {

    @Around("call(* com.optimize.performance.PerformanceApp.**(..))")
    public void getTime(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        String name = signature.toShortString();
        long time = System.currentTimeMillis();
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        LogUtils.i(name + " cost " + (System.currentTimeMillis() - time));
    }

}

总结:

无侵入性,修改方便

优雅获取方法的耗时,AOP的理解及使用

五 异步优化详解

1、优化技巧

1、Theme切换:感觉上的快

在这里插入图片描述

2、异步优化

1、核心思想:子线程分担主线程任务,并行减少时间
简单示例

        ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
        service.submit(new Runnable() {
            @Override
            public void run() {
                //子线程中执行一个初始化的任务

            }
        });
        
        这种使用会出现application走完了但是线程中初始化并没有完成而出现错误
        解决办法:
        private CountDownLatch mCountDownLatch = new CountDownLatch(1);//表示CountDownLatch需要被满足一次,具体多少次自己根据场景进行设置
        
        @Override
        public void onCreate() {
        super.onCreate();
        ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
        service.submit(new Runnable() {
            @Override
            public void run() {
                //子线程中执行一个初始化的任务

            }
        });

        service.submit(new Runnable() {
            @Override
            public void run() {
                //子线程中执行一个初始化的任务
                initWeex();
                //在需要等待初始化完成的任务中调用,满足一次
                mCountDownLatch.countDown();
            }
        });

        try {
            //mCountDownLatch在onCreate中最后一步进行等待
            mCountDownLatch.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

异步优化注意

不符合异步要求(修改成符合异步要求,或者放弃)
需要在某阶段完成
区分cpu密集型和io密集型任务

六 异步初始化最优解-启动器

1、启动器介绍

1、核心思想:充分利用cpu多核,自动梳理任务顺序

代码Task化,启动逻辑抽象为Task
根据所有任务依赖关系排序生成一个有向无环图
多线程按照排序后的优先级依次执行

在这里插入图片描述

[启动器代码](链接:https://pan.baidu.com/s/1Ih1WflOlhiFO7mX4SOibtA
提取码:znse )

2、启动器实战

接口说明

public interface ITask {

    /**
     * 优先级的范围,可根据Task重要程度及工作量指定;之后根据实际情况决定是否有必要放更大
     *
     * @return
     */
    @IntRange(from = Process.THREAD_PRIORITY_FOREGROUND, to = Process.THREAD_PRIORITY_LOWEST)
    int priority();

    void run();

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

    /**
     * 依赖关系
     *
     * @return
     */
    List<Class<? extends Task>> dependsOn();

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

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

    /**
     * 只是在主进程执行
     *
     * @return
     */
    boolean onlyInMainProcess();

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

    void setTaskCallBack(TaskCallBack callBack);

    boolean needCall();
}

实例代码

创建task
/**
 * 主线程执行的task
 */
public class InitWeexTask extends MainTask {
    @Override
    public boolean needWait() {
        return true;
    }

    @Override
    public void run() {
        InitConfig config = new InitConfig.Builder().build();
        WXSDKEngine.initialize((Application) mContext, config);
    }
}

/**
 * 子线程执行
 * 需要在getDeviceId之后执行
 */
public class InitJPushTask extends Task {

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

    @Override
    public void run() {
        JPushInterface.init(mContext);
        PerformanceApp app = (PerformanceApp) mContext;
        JPushInterface.setAlias(mContext, 0, app.getDeviceId());
    }
}

/**
 * Application中执行的代码
 * 
 */
        TaskDispatcher.init(PerformanceApp.this);

        TaskDispatcher dispatcher = TaskDispatcher.createInstance();

        dispatcher.addTask(new InitAMapTask())
                .addTask(new InitStethoTask())
                .addTask(new InitWeexTask())
                .addTask(new InitBuglyTask())
                .addTask(new InitFrescoTask())
                .addTask(new InitJPushTask())
                .addTask(new InitUmengTask())
                .addTask(new GetDeviceIdTask())
                .start();

        dispatcher.await();

七 更优秀的延迟初始化方案

1、常规方案

不需要再application中初始化的可以在Feed展示后调用,new Handler().postDelayed

    @Override
    public void onFeedShow() {
        //如果一系列操作耗时会导致用户卡顿
        new DispatchRunnable(new DelayInitTaskA()).run();
        new DispatchRunnable(new DelayInitTaskB()).run();
        使用postDelayed的话时间不好指定
    }

总结:时机不便控制、导致界面卡顿

2、更优方案

核心思想:对延迟的任务分批初始化

利用IdleHandler的特性,空闲执行

//IdleHandler封装类
public class DelayInitDispatcher {

    private Queue<Task> mDelayTasks = new LinkedList<>();

    private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle() {
            if(mDelayTasks.size()>0){
                Task task = mDelayTasks.poll();
                new DispatchRunnable(task).run();
            }
            return !mDelayTasks.isEmpty();
        }
    };

    public DelayInitDispatcher addTask(Task task){
        mDelayTasks.add(task);
        return this;
    }

    public void start(){
        Looper.myQueue().addIdleHandler(mIdleHandler);
    }
}


//使用方式
 @Override
    public void onFeedShow() {
        DelayInitDispatcher delayInitDispatcher = new DelayInitDispatcher();
        delayInitDispatcher.addTask(new DelayInitTaskA())
                .addTask(new DelayInitTaskB())
                .start();

    }

总结:执行时机明确,解决界面卡顿

七 启动优化其它方案

1、优化总方针

异步、延迟、懒加载(如高德地图初始化只需要在使用的界面进行初始化即可)
技术、业务相结合

2、注意事项

walltime和cputime
cputime才是我们优化的方向
按照systrace及cputime跑满cpu

在这里插入图片描述

监控的完善
线上监控多阶段时间(App、Activity、生命周期间隔时间)
处理聚合看趋势

3、其他方案

提前加载SharePreferences(在Multidex之前加载,利用此阶段的cpu)
覆写getApplicationContext()返回this

    @Override
    public Context getApplicationContext() {
        return this;
    }

启动阶段不启动子进程
子进程会共享cpu资源,导致主进程cpu资源紧张

类加载优化:提前异步类加载

Class.forName()只加载类本身及其静态变量的引用类

new 类实例 可以额外加载类成员变量的引用类

哪些类需要提前异步类加载呢?(替换系统的ClassLoader,自定义的ClassLoader中添加log打印出所有的类就是需要处理的)

黑科技系列

启动阶段抑制GC(NativeHook的方案)

CPU锁频(可能会引起其他问题)

八 总结

1、获取方法耗时

2、异步、延迟初始化

3、其他方案

猜你喜欢

转载自blog.csdn.net/zsjlovesm521/article/details/87929876