APP启动速度优化

一:应用启动类型

应用启动类型分为三种:冷启动、热启动、温启动
1.1:冷启动
简介:从点击应用图标到开始创建应用UI界面完全显示且用户可操作的全部过程。特点是耗时最多,是APP启动速度的衡量标准。
冷启动流程分析
Click Event -> IPC -> Process.start -> ActivityThread -> bindApplication -> LifeCycle -> ViewRootImpl

点击应用,加载并启动APP,创建APP进程。接下来执行ActivityThread的main方法,在main方法中会执行Loop和Handler的创建,创建完成之后,就会执行到 bindApplication 方法,在这里使用了反射去创建 Application以及调用了 Application相关的生命周期,Application结束之后,便会执行Activity的生命周期,在Activity生命周期结束之后,最后,就会执行到 View的绘制。
进程的创建是系统行为,我们没办法优化,我们可以着手优化的点在Application创建,Avtivity创建,View绘制。

1.2:热启动
应用从后台切换到前台。

1.3:温启动
简介:温启动时由于app的进程仍然存在,只执行冷启动第二阶段流程
温启动常见场景:
1、用户双击返回键退出应用
2、app由于内存不足被回收

二:APP启动耗时检测

通过耗时检测,可以验证我们的优化方案是否有效和优化方案的效果。可以检测到具体的耗时任务,针对耗时任务进行优化。
1:查看Logcat
在Android Studio Logcat中过滤关键字“Displayed”,可以看到对应的冷启动耗时日志。

2:adb shell
adb shell am start -W [packageName]/[AppstartActivity全路径]
比如:adb shell am start -W com.demo/.ui.SplashActivity

3:函数插桩
原理:编辑一个统计耗时的工具类,记录某个方法的结束时间-开始时间,把方法的耗时记录到本地。然后上传到服务器。

其中需要注意的有:
在上传数据到服务器时建议根据用户ID的尾号来抽样上报。
在项目中核心基类的关键回调函数和核心方法中加入打点。

特点:精确,可带到线上,但是代码有侵入性,修改成本高。

插桩:在目标程序代码中某些位置插入或修改成一些代码,从而在目标程序运行过程中获取某些程序状态并加以分析。简单来说就是在代码中插入代码。 那么函数插桩,便是在函数中插入或修改代码。

代码:

使用
class AppApplication : CommonApplication() {

    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        TimeMonitorManager.getInstance()
            .resetTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START)
    }

    override fun onCreate() {
        super.onCreate()
        TimeMonitorManager.getInstance()
            .getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START)
            .recordingTimeTag("Application-onCreate")
        //打印结果 TimeMonitorApplication-onCreate: 417
    }

}
/**
 * 采用单例管理各个耗时统计的数据。
 */
public class TimeMonitorManager {

    private static TimeMonitorManager mTimeMonitorManager = null;
    private HashMap<Integer, TimeMonitor> mTimeMonitorMap = null;

    public synchronized static TimeMonitorManager getInstance() {
        if (mTimeMonitorManager == null) {
            mTimeMonitorManager = new TimeMonitorManager();
        }
        return mTimeMonitorManager;
    }

    public TimeMonitorManager() {
        this.mTimeMonitorMap = new HashMap<Integer, TimeMonitor>();
    }

    /**
     * 初始化打点模块
     */
    public void resetTimeMonitor(int id) {
        if (mTimeMonitorMap.get(id) != null) {
            mTimeMonitorMap.remove(id);
        }
        getTimeMonitor(id).startMonitor();
    }

    /**
     * 获取打点器
     */
    public TimeMonitor getTimeMonitor(int id) {
        TimeMonitor monitor = mTimeMonitorMap.get(id);
        if (monitor == null) {
            monitor = new TimeMonitor(id);
            mTimeMonitorMap.put(id, monitor);
        }
        return monitor;
    }
}

/**
 *  耗时监视器对象,记录整个过程的耗时情况,可以用在很多需要统计的地方,
 *  比如Activity的启动耗时和Fragment的启动耗时。
 */
public class TimeMonitor {

    private final String TAG = TimeMonitor.class.getSimpleName();
    private int mMonitorId = -1;

    // 保存一个耗时统计模块的各种耗时,tag对应某一个阶段的时间
    private HashMap<String, Long> mTimeTag = new HashMap<>();
    private long mStartTime = 0;

    public TimeMonitor(int mMonitorId) {
        LogUtils.d(TAG + "init TimeMonitor id: " + mMonitorId);
        this.mMonitorId = mMonitorId;
    }

    public int getMonitorId() {
        return mMonitorId;
    }

    public void startMonitor() {
        // 每次重新启动都把前面的数据清除,避免统计错误的数据
        if (mTimeTag.size() > 0) {
            mTimeTag.clear();
        }
        mStartTime = System.currentTimeMillis();
    }

    /**
     * 每打一次点,记录某个tag的耗时
     */
    public void recordingTimeTag(String tag) {
        // 若保存过相同的tag,先清除
        if (mTimeTag.get(tag) != null) {
            mTimeTag.remove(tag);
        }
        long time = System.currentTimeMillis() - mStartTime;
        LogUtils.w(TAG + tag + ": " + time);
        mTimeTag.put(tag, time);
    }

    public void end(String tag, boolean writeLog) {
        recordingTimeTag(tag);
        end(writeLog);
    }

    public void end(boolean writeLog) {
        if (writeLog) {
            //写入到本地文件
        }
    }

    public HashMap<String, Long> getTimeTags() {
        return mTimeTag;
    }
}

4、✨AOP(Aspect Oriented Programming) 打点
面向切面编程,通过预编译和运行期动态代理实现程序功能统一维护的一种技术。
1、作用
利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合性降低,提高程序的可重用性,同时大大提高了开发效率。

5、✨启动速度分析工具
5.1:TraceView
5.2: Systrace

三:应用启动优化方案

一、常见问题

(1)点击应用图标,显示白屏。
(2)首页显示太慢:初始化任务太多。
(3)首页显示后无法进行操作:太多延迟初始化任务占用主线程CPU时间片。

二、如何分析问题

1:对于启动应用白屏:我们可以设置启动背景图。
2:通过耗时分析,对代码进行针对性优化,同样也可以来验证我们优化的成果。通过过滤关键字“Displayed”,可以看到对应的冷启动耗时日志;通过函数插桩和AOP打点,统计耗时时间和上传耗时时间到服务器,分析线上启动耗时情况。通过分析工具TraceView,来查找单次执行最耗时的方法和执行次数最多的方法;
3:在APP版本升级过程中,新的版本反馈启动过慢,进行版本代码比较,分析新版本新增了哪些耗时操作。
4:对于可以异步初始化的任务:我们可以使用异步启动器在Application的onCreate方法中执行加载。
5:对于不能异步执行的,但不是必须在onCreate完成前执行的,我们可以利用延迟启动器进行加载。
6:如果任务可以到用时再加载,可以使用懒加载的方式。
7:数据缓存,缓存启动页和首页的数据等。

三、设置启动背景图

设置Activity的theme属性windowBackground,预先设置一个启动图片(layer-list实现)。避免了启动白屏和点击启动图标不响应的情况。

<style name="Splash" parent="AppCompat.FullScreen">
    <item name="android:windowBackground">
    @drawable/bg_splash</item>
</style>
<!--AppCompat FullScreen-->
<style name="AppCompat.FullScreen" parent="AppTheme">
    <item name="windowNoTitle">true</item>
    <item name="windowActionBar">false</item>
    <item name="android:windowFullscreen">true</item>
    <item name="android:windowIsTranslucent">false</item>
</style>

四、异步初始化

(1)核心思想
子线程分担主线程任务,并行减少时间。
(2)异步初始化方案演进
1、new Thread->IntentService->线程池(合理配置并选择CPU密集型和IO密集型线程池)->异步启动器
(3)异步启动器优点
1、任务Task化,启动逻辑抽象成Task(Task即对应一个个的初始化任务)。
2、根据所有任务依赖关系排序生成一个有向无环图:例如推送SDK初始化任务需要依赖于获取设备id的初始化任务,各个任务之间都可能存在依赖关系,所以将它们的依赖关系排序生成一个有向无环图能将并行效率最大化。
3、多线程按照排序后的优先级依次执行:例如必须先初始化获取设备id的初始化任务,才能去进行推送SDK的初始化任务。
原理:
初始化多个list,主要存放所有task、和先执行的task。初始化map,存放依赖关系的task。然后对list进行排序,形成一个有向无环图。然后遍历,开启runnable,来执行task任务。
源码:https://github.com/zeshaoaaa/LaunchStarter
看使用效果代码,体验代码的优雅

open class CommonApplication : BaseApplication() {
    var deviceId: String? = null

    override fun onCreate() {
        super.onCreate()

        TaskDispatcher.init(this)
        val dispatcher: TaskDispatcher = TaskDispatcher.createInstance()
        dispatcher
            .addTask(InitCommonTask())
            .addTask(InitDependsTask())
            .addTask(InitMainTask())
            .addTask(GetDeviceIdTask())
            .start()
        dispatcher.await()
        DelayInitDispatcher().addTask(DelayInitTaskA()).addTask(DelayInitTaskB()).start()
        LogUtils.e("CommonApplication onCreate end")
    }
   

五、延迟初始化

原理:利用IdleHandler特性,在CPU空闲时执行,对延迟任务进行分批初始化。避免UI卡顿,提升用户体验。
在延迟启动器中,我们提供了mDelayTasks队列用于将每一个task添加进来,使用者只需调用addTask方法即可。当CPU空闲时,mIdleHandler便会回调自身的queueIdle方法,这个时候我们可以将task一个一个地拿出来并执行。这种分批执行的好处在于每一个task占用主线程的时间相对来说很短暂,并且此时CPU是空闲的,这样能更有效地避免UI卡顿,真正地提升用户的体验。

(1)延迟初始化启动器源码

原理:我们采用了IdleHandler,当前线程空闲的时候,会回调IdleHandler的queueIdle方法。

/**
 * 延迟初始化启动器
 */
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);
    }

}
(2)IdleHandler源码解析

(1)在ActivityThread的main方法中,会调用Looper.loop()方法。

public static void main(String[] args) {
    Looper.prepareMainLooper();
    Looper.loop();
}

(2)在Looper.loop()的方法中,开启循环,调用MessageQueue的next方法。

public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (; ; ) {
        Message msg = queue.next(); // might block
        if (msg == null) {
        //只有主动调用停止方法,才会返回null
         // No message indicates that the message queue is quitting.
            return;
        }
    }
}

(3)MessageQueue的next

//MessageQueue添加IdleHandler方法,会把我们自定义的handler添加到队列mIdleHandlers中。
public void addIdleHandler(@NonNull IdleHandler handler) {
    //、、、
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}
/**
 * MessageQueue的next方法
 */
Message next() {
   //1、开启死循环,不断获取msg。如果获取到msg,会return msg
    for (;;) {
        Message msg = mMessages;
        if (msg != null) {
            msg = msg.next;
            return msg;
        }
        //2、给pendingIdleHandlerCount赋值
        pendingIdleHandlerCount = mIdleHandlers.size();
        mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        //3、循环取出mPendingIdleHandlers中的IdleHandler
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final MessageQueue.IdleHandler idler = mPendingIdleHandlers[i];
            boolean keep = false;
         //4、回调IdleHandler中的queueIdle方法
            keep = idler.queueIdle();
         //5、如果queueIdle返回false,mIdleHandlers会删除idler。  
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
    }
}

总结:
1、在ActivityThread的main方法中,会调用Looper.loop()方法。
2、在Looper.loop()的方法中,开启死循环,调用MessageQueue的next方法。
3、在MessageQueue的next,也会开启一个死循环,不停的获取msg。如果msg不为空,就返回给Looper,让looper去处理msg。如果为空,会去判断mIdleHandlers是否为空。不为空,就会获取到mIdleHandlers中的我们添加的IdleHandler,会回调IdleHandler的queueIdle。如果返回false,就从mIdleHandlers这个集合删掉我们添加的IdleHandler。
源码分析:

六、页面数据预加载

在主页空闲时,将其它页面的数据加载好保存到内存或数据库,等到打开该页面时,判断已经预加载过,就直接从内存或数据库取数据并显示。

七、闪屏页与主页的绘制优化

1、布局优化。
2、过渡绘制优化。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
酷屏是一款休闲娱乐桌面帮助小软件.软件大小只有1M左右,而且小巧灵活不占电脑桌面,而且酷屏壁纸播放器经过360安全认证,觉得无毒,真正免费的桌面软件广大用户可以放心实用。 酷屏3功能: 酷屏壁纸播放器功能介绍: 每天可以随你的心情自动更换桌面壁纸。想换什么就换什么,一键操作。 每天定时更换桌面壁纸.用户可以根据自己的喜好,定时更换自己平时喜好的桌面壁纸,让你的桌面天天不一样。而且还可以星期一到星期天可以随时自动更换不同主题的桌面。让你的桌面每天焕然一新。 软件内存无限,这样用户不必担心自己喜欢的壁纸太多而装不下。用户可以根据自己的爱好在网站上任意下载壁纸。有多少下多少。 智能分类,酷屏壁纸播放器可以根据用户的喜好,把壁纸进行分类。这样用户就可以方便快捷的找到自己喜欢的壁纸了。 对喜欢的壁纸进行收藏。酷屏壁纸播放软件给广大用户提供了壁纸收藏功能。让用户喜欢的壁纸永远得到珍藏。 酷屏壁纸播放器软件上有个快捷下载建,当你想换壁纸的时候,又一时找不到合适的网站,只要用户轻轻点下按钮就会弹出壁纸吧,让你随心所欲的选择自己喜欢的壁纸。然后下载到播放器里面 酷屏壁纸播放器还提供记事本,计算器,常用的导航网址 桌面零碎文档自动整理功能。只要轻轻一点按钮就可以把你的桌面变的整整齐齐。把一些临时文件全部放到一个指定文件夹里面。 每次自动启动酷屏软件的时候,右下角会弹出一个提示框。提示用户是否替换当前桌面。如果点是,那么软件就会自动把用户以前的桌面换掉。点否则就保留以前桌面壁纸。如果用户点的是这个选项,又想恢复以前自己的桌面,那么当你关闭酷屏的时候用户原有的壁纸就会自动还原。这样就为用户减少了一些不必要的麻烦。做到真正的人性化设计。 酷屏3 beta3更新: 1、优化资源加载速度 2、壁纸图库主页新增加专题行数 3、桌面主题新增加操作系统提示 4、修改酷屏应用有可能出现崩溃的情况 5、增加上千条壁纸及主题数据。 6、修正沉余软件BUG。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值