APP启动优化案例

布局优化

窗体的布局结构:

APP启动优化案例_第1张图片

窗体布局层次:

APP启动优化案例_第2张图片

窗体绘制:

上图中是表明DecorView的加载耗时:31.89ms

检测方法耗时

  • 手表性能太低,无法使用traceview去跟踪方法耗时,会造成ANR
  • aop的方式还要再集成或者自己开发来获取方法耗时,开发耗时周期太长
  • 简单粗暴的方法,直接再程序中加入日志打印计算在启动过程中的方法执行耗时
  • 可通过systemtrace来抓去trace文件分析启动过程中的卡顿以及耗时较长的操作,例如界面渲染,service启动等操作
  • 在systemtrace中用activityStart来衡量布局加载,绘制耗时

启动速度优化三要素

  • 异步加载
  • 懒加载
  • 延迟加载
LauncherApplication的onCreate方法:
03-06 15:18:55.651 14699-14699/com.xtc.i3launcher D/LaunchComputer: start launch compute
03-06 15:18:55.771 14699-14699/com.xtc.i3launcher I/LaunchComputer: initDialData [119] ms
03-06 15:18:55.772 14699-14699/com.xtc.i3launcher I/LaunchComputer: CommonInit.init [120] ms
03-06 15:18:55.779 14699-14699/com.xtc.i3launcher I/LaunchComputer: FrescoUtil.init [127] ms
03-06 15:18:55.793 14699-14699/com.xtc.i3launcher I/LaunchComputer: LauncherDbManager.getInstance().init [142] ms
03-06 15:18:55.803 14699-14699/com.xtc.i3launcher I/LaunchComputer: DownloadAppDbManager.getInstance().init [151] ms
03-06 15:18:55.808 14699-14699/com.xtc.i3launcher I/LaunchComputer: InitServiceDbManager.getInstance().init [157] ms
03-06 15:18:55.830 14699-14699/com.xtc.i3launcher I/LaunchComputer: ChatDbManager.getInstance().init [178] ms
03-06 15:18:55.832 14699-14699/com.xtc.i3launcher I/LaunchComputer: ContactDbManager.getInstance().init [181] ms
03-06 15:18:55.853 14699-14699/com.xtc.i3launcher I/LaunchComputer: GradeDbManager.getInstance().init [201] ms
03-06 15:18:55.858 14699-14699/com.xtc.i3launcher I/LaunchComputer: LocationDbManager.getInstance().init [207] ms
03-06 15:18:55.931 14699-14699/com.xtc.i3launcher I/LaunchComputer: 权限设置 [280] ms
03-06 15:18:55.968 14699-14699/com.xtc.i3launcher I/LaunchComputer: initNeedNet [316] ms
03-06 15:18:55.976 14699-14699/com.xtc.i3launcher I/LaunchComputer: initMsgTypeInfo [324] ms
03-06 15:18:56.187 14699-14699/com.xtc.i3launcher I/LaunchComputer: ContactApp.init [536] ms
03-06 15:18:56.233 14699-14699/com.xtc.i3launcher I/LaunchComputer: WeiChatApp.init [581] ms
03-06 15:18:56.471 14699-14699/com.xtc.i3launcher I/LaunchComputer: MonitorBehavior.burryBootScene [819] ms
03-06 15:18:56.472 14699-14699/com.xtc.i3launcher I/LaunchComputer: finish LauncherApplication.onCreate [821] ms

HomeActivity.onCreate:
03-06 15:18:57.856 14699-14699/com.xtc.i3launcher I/LaunchComputer: before setContentView for HomeActivity [2204] ms
03-06 15:19:00.080 14699-14699/com.xtc.i3launcher I/LaunchComputer: setContentView for HomeActivity [4429] ms
03-06 15:19:00.098 14699-14699/com.xtc.i3launcher I/LaunchComputer: SharedTool.saveOneTrafficBytes [4447] ms
03-06 15:19:00.112 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.initData [4460] ms
03-06 15:19:00.113 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.initAnimation [4461] ms
03-06 15:19:00.125 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.initView [4473] ms
03-06 15:19:00.128 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.initFragment [4476] ms
03-06 15:19:00.134 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.initWatchLoss [4483] ms
03-06 15:19:00.556 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.initClassMode [4905] ms
03-06 15:19:00.588 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.initPowerSave [4935] ms
03-06 15:19:00.846 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.initTheme [5195] ms
03-06 15:19:01.043 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.startWakeLock [5392] ms
03-06 15:19:02.095 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.initOther [6443] ms
03-06 15:19:02.162 14699-14699/com.xtc.i3launcher I/LaunchComputer: SystemBehavior.behaviorSystemInfo [6511] ms
03-06 15:19:02.164 14699-14699/com.xtc.i3launcher I/LaunchComputer: ShellUtil.systemShutdownOrAppCrashBehavior [6513] ms
03-06 15:19:02.187 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.showLoadingImage [6536] ms
03-06 15:19:02.187 14699-14699/com.xtc.i3launcher I/LaunchComputer: finish HomeActivity.onCreate [6536] ms

HomeActivity.onResume:
03-06 15:19:02.198 14699-14699/com.xtc.i3launcher I/LaunchComputer: before HomeActivity.onResume [6547] ms
03-06 15:19:02.204 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.onResume [6553] ms
03-06 15:19:02.210 14699-14699/com.xtc.i3launcher I/LaunchComputer: finish HomeActivity.onResume [6559] ms
03-06 15:19:02.310 14699-14699/com.xtc.i3launcher I/LaunchComputer: before HomeActivity.onResume [6659] ms
03-06 15:19:02.314 14699-14699/com.xtc.i3launcher I/LaunchComputer: HomeActivity.onResume [6662] ms
03-06 15:19:02.320 14699-14699/com.xtc.i3launcher I/LaunchComputer: finish HomeActivity.onResume [6669] ms

HomeActivity.onWindowFocusChanged:
03-06 15:19:17.676 14699-14699/com.xtc.i3launcher I/LaunchComputer: onWindowFocusChanged [22024] ms

  • 如上日志可以得出,Launcher的启动速度为22024ms,约为22s
  • 其中Application中的启动没有太大问题,基本都在1秒之内
  • HomeActivity的onCreate耗时6536-2204=4332ms,约为4s
  • 从HomeActivity的onResume到onWindowFocusChanged耗时22024-6669=15355ms,约为15s

所以Launcher的大部分启动耗时就花在onResume到onWindowFocusChanged这里,有效的优化点为onCreate方法和onResume到onWindowFocusChanged

这是我用一个空的TestHomeActivity来替换HomeActivity的测试结果:

03-06 17:36:22.881 2613-2613/com.xtc.i3launcher D/LaunchComputer: start launch compute
03-06 17:36:22.983 2613-2613/com.xtc.i3launcher I/LaunchComputer: initDialData [102] ms
03-06 17:36:22.984 2613-2613/com.xtc.i3launcher I/LaunchComputer: CommonInit.init [103] ms
03-06 17:36:22.986 2613-2613/com.xtc.i3launcher I/LaunchComputer: FrescoUtil.init [105] ms
03-06 17:36:23.004 2613-2613/com.xtc.i3launcher I/LaunchComputer: LauncherDbManager.getInstance().init [123] ms
03-06 17:36:23.009 2613-2613/com.xtc.i3launcher I/LaunchComputer: DownloadAppDbManager.getInstance().init [129] ms
03-06 17:36:23.015 2613-2613/com.xtc.i3launcher I/LaunchComputer: InitServiceDbManager.getInstance().init [134] ms
03-06 17:36:23.092 2613-2613/com.xtc.i3launcher I/LaunchComputer: ChatDbManager.getInstance().init [211] ms
03-06 17:36:23.094 2613-2613/com.xtc.i3launcher I/LaunchComputer: ContactDbManager.getInstance().init [213] ms
03-06 17:36:23.123 2613-2613/com.xtc.i3launcher I/LaunchComputer: GradeDbManager.getInstance().init [243] ms
03-06 17:36:23.136 2613-2613/com.xtc.i3launcher I/LaunchComputer: LocationDbManager.getInstance().init [255] ms
03-06 17:36:23.183 2613-2613/com.xtc.i3launcher I/LaunchComputer: 权限设置 [302] ms
03-06 17:36:23.509 2613-2613/com.xtc.i3launcher I/LaunchComputer: initNeedNet [628] ms
03-06 17:36:23.523 2613-2613/com.xtc.i3launcher I/LaunchComputer: initMsgTypeInfo [642] ms
03-06 17:36:23.759 2613-2613/com.xtc.i3launcher I/LaunchComputer: ContactApp.init [878] ms
03-06 17:36:23.807 2613-2613/com.xtc.i3launcher I/LaunchComputer: WeiChatApp.init [926] ms
03-06 17:36:23.883 2613-2613/com.xtc.i3launcher I/LaunchComputer: MonitorBehavior.burryBootScene [1002] ms
03-06 17:36:23.884 2613-2613/com.xtc.i3launcher I/LaunchComputer: finish LauncherApplication.onCreate [1004] ms
03-06 17:36:25.128 2613-2613/com.xtc.i3launcher I/LaunchComputer: TestHomeActivity.onCreate [2248] ms
03-06 17:36:25.137 2613-2613/com.xtc.i3launcher I/LaunchComputer: TestHomeActivity.onResume [2256] ms
03-06 17:36:28.341 2613-2613/com.xtc.i3launcher I/LaunchComputer: TestHomeActivity.onWindowFocusChanged [5460] ms
  • 通过对比可知,空的TestHomeActivity再onResume到onWindowFocusChanged之间耗时为5460-2256=3204ms,约3秒,因此怀疑是HomeActivity的布局排列渲染(measure,layout,draw),数据加载等造成了严重的延迟,因为Activity在onResume中是去对布局中的控件开始了measure,layout,draw,当跑到onWindowFocusChanged的时候,布局已经加载完成了

HomeActivity.onCreate优化

修改前:原先在onCreate中执行了太多任务

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DisplayUtil.isCreate = true;
        startMainCreateTimeMillis = SystemClock.elapsedRealtime();
        setContentView(R.layout.activity_home);
        ivFirstLoading = (ImageView) findViewById(R.id.iv_first_loading);
        LogUtil.d("onCreate width = " + ScreenUtils.getScreenWidth(this) +
                "---height = " + ScreenUtils.getScreenHeight(this));

        EventBus.getDefault().register(this);

        homeContainer = (ContainerView) findViewById(R.id.container_view);
        vpMain = (ForbidScrollViewPager) findViewById(R.id.vp_main);
        SharedTool.saveOneTrafficBytes(this, 0); // 开机启动流量置0,为了后面的流量统计提供使用
        initData();
        initAnimation();

        initView();
        initFragment();

        presenter.registerCallReceiver();

        initWatchLoss();
        initClassMode();
        initPowerSave();

        initTheme();
        presenter.startTimeChangeAlarm();
        presenter.registerNotificationListenerService(this);
        presenter.setScreenListener(screenListener);
        presenter.setSimStateCallback(simStateCallback);
        presenter.setClockChangeListener(clockChangeListener);
        CloudMusicReceiver.getInstance().registerReceiver(this);
        vibrateHelper = VibrateHelper.getInstance(this);
        batteryHelper = BatteryHelper.getInstance(this);

        DisplayUtil.startWakeLock(this, false);

        initOther();

        //埋点系统信息
        SystemBehavior.behaviorSystemInfo();
        ShellUtil.systemShutdownOrAppCrashBehavior(this);

        showLoadingImage();
    }

修改后:把不需要在onCreate中执行的任务放到onWindowFocusChanged第一次获取焦点的时候再去执行,因为这时候界面已经可见,界面可见后再去加载一些数据信息就不会影响到界面启动的速度了

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DisplayUtil.isCreate = true;
        startMainCreateTimeMillis = SystemClock.elapsedRealtime();
        LaunchComputer.trace("before setContentView for HomeActivity");
        setContentView(R.layout.activity_home); // TODO: 2018/3/6 耗时
        LaunchComputer.trace("setContentView for HomeActivity");

//        ivFirstLoading = (ImageView) findViewById(R.id.iv_first_loading);
        LogUtil.d("onCreate width = " + ScreenUtils.getScreenWidth(this) +
                "---height = " + ScreenUtils.getScreenHeight(this));

        EventBus.getDefault().register(this);

        homeContainer = (ContainerView) findViewById(R.id.container_view);
        vpMain = (ForbidScrollViewPager) findViewById(R.id.vp_main);
        SharedTool.saveOneTrafficBytes(this, 0); // 开机启动流量置0,为了后面的流量统计提供使用
        LaunchComputer.trace("SharedTool.saveOneTrafficBytes");

        initData();
        LaunchComputer.trace("HomeActivity.initData");

        initAnimation();
        LaunchComputer.trace("HomeActivity.initAnimation");

        initView();
        LaunchComputer.trace("HomeActivity.initView");

        /*initFragment();
        LaunchComputer.trace("HomeActivity.initFragment");*/

//        presenter.registerCallReceiver();

        /*initWatchLoss();
        LaunchComputer.trace("HomeActivity.initWatchLoss");*/

        /*initClassMode(); // TODO: 2018/3/6 耗时
        LaunchComputer.trace("HomeActivity.initClassMode");

        initPowerSave(); // TODO: 2018/3/6 耗时
        LaunchComputer.trace("HomeActivity.initPowerSave");

        initTheme(); // TODO: 2018/3/6 耗时
        LaunchComputer.trace("HomeActivity.initTheme");*/

        /*presenter.startTimeChangeAlarm();
        presenter.registerNotificationListenerService(this);
        presenter.setScreenListener(screenListener);
        presenter.setSimStateCallback(simStateCallback);
        presenter.setClockChangeListener(clockChangeListener);*/

        CloudMusicReceiver.getInstance().registerReceiver(this);
//        vibrateHelper = VibrateHelper.getInstance(this);
//        batteryHelper = BatteryHelper.getInstance(this);

        DisplayUtil.startWakeLock(this, false);
        LaunchComputer.trace("HomeActivity.startWakeLock");

        /*initOther();
        LaunchComputer.trace("HomeActivity.initOther");*/

        //埋点系统信息
        /*SystemBehavior.behaviorSystemInfo(); // TODO: 2018/3/6 耗时
        LaunchComputer.trace("SystemBehavior.behaviorSystemInfo");

        ShellUtil.systemShutdownOrAppCrashBehavior(this);
        LaunchComputer.trace("ShellUtil.systemShutdownOrAppCrashBehavior");*/

        showLoadingImage();
        LaunchComputer.trace("HomeActivity.showLoadingImage");

        LaunchComputer.trace("finish HomeActivity.onCreate");
    }

public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        LogUtil.i("wtx hasFocus" + hasFocus);
        if (hasFocus && !firstInit) {
            firstInit = true;
            LaunchComputer.trace("onWindowFocusChanged");
            windowFocused();
        }
        if (!hasFocus) {
            DisplayUtil.setNormalScreenOffTimeout(this);
            setHasSetTime(false);
        } else {
            setTimeOnClock();
        }
        startCostTimeBehavior();
    }

    private void windowFocused() {
        // 开机速度优化:把一些无需马上执行的操作放到这里执行
        initFragment();
        initWatchLoss();

        initClassMode();

        initPowerSave();

        initTheme();

        initForPresenter();
        initOther();

        SystemBehavior.behaviorSystemInfo();

        ShellUtil.systemShutdownOrAppCrashBehavior(this);

        SyncData.launcher(getApplicationContext());
    }

HomeActivity的布局优化

  • HomeActivity的布局的根节点改用merge,因为DecorView是FrameLayout,所以根布局如果也是FrameLayout的话就可直接用merge替换,可减少一层布局嵌套,布局的加载是再activity的onCreate方法中的setContentView的时候开始的

去掉在主界面中不必要的ImageView的加载,id为:iv_first_loading

修改前:

在HomeActivity.onCreate方法中执行如下方法:

private void showLoadingImage() {
        LogUtil.i("show loading dialog");
        ivFirstLoading.setVisibility(View.VISIBLE);
        Observable.timer(CANCEL_LOADING_TIME, TimeUnit.MILLISECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber() {
                    @Override
                    public void onCompleted() {
                        cancelLoadingImage();
                    }

                    @Override
                    public void onError(Throwable e) {
                        cancelLoadingImage();
                    }

                    @Override
                    public void onNext(Long aLong) {
                    }
                });
    }

修改后:

把这个ImageView直接去掉,不再加载

表盘预览界面的优化(PreviewFragment)

  • PreviewFragment在进入主界面的时候并不需要展示,只有在切换表盘的时候才会展示

修改前:

PreviewFragment加载耗时:116.924ms

private void showPreview(boolean isShow) {
        LogUtil.d("showPreview isShow = " + isShow);
        if (previewFragment != null) {
            LogUtil.i("previewFragment");
            previewFragment.setShow(isShow, 0, false);
        }
        setForbidScrollContainer(isShow);
        setForbidScrollViewPager(isShow);

        if (isShow) {
            startClockAnimation();
        } else {
            showHomeContainer(true);
        }
    }

布局文件:


修改后:

PreviewFragment在launcher启动初始化的时候不再加载,而是等到进入表盘预览界面的时候才会取加载一次,然后缓存起来,这么做可能在首次进入表盘预览界面的时候由于要加载数据可能会有延迟或者闪动的效果

private void initPreviewFragment() {
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        if (previewFragment == null) {
            LogUtil.d("initPreviewFragment");
            previewFragment = PreviewFragment.newInstance();
            previewFragment.setCallback(HomeActivity.this);
            fragmentTransaction.add(android.R.id.content, previewFragment, previewFragment.getClass().getSimpleName());
            fragmentTransaction.addToBackStack(null);
            fragmentTransaction.commit();
        }
    }

private void showPreview(boolean isShow) {
        LogUtil.d("showPreview isShow = " + isShow);
        if (isShow) {
            initPreviewFragment();
        }
        if (previewFragment != null) {
            LogUtil.i("previewFragment");
            previewFragment.setShow(isShow, 0, false);
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            if (isShow) {
                fragmentTransaction.show(previewFragment);
            } else {
                fragmentTransaction.hide(previewFragment);
            }
            fragmentTransaction.commitAllowingStateLoss();
        }
        setForbidScrollContainer(isShow);
        setForbidScrollViewPager(isShow);

        if (isShow) {
            startClockAnimation();
        } else {
            showHomeContainer(true);
        }
    }

布局文件就把fragment去掉

  • PreviewFragment布局中的TextView的background的懒加载

修改前:

TextView加载耗时:56.017ms,可以看到的background中decode了较多的bitmap



public void dealClockChanging() {
        LogUtil.i("dealClockChanging");
        setBlurImage();
        if (isShow) {
            rlClockChange.setVisibility(View.VISIBLE);
            rlClockChange.postDelayed(runnable, 5000);
            changingAnim = (AnimationDrawable) tvChanging.getBackground();
            changingAnim.start();
        }
    }

修改后:

APP启动优化案例_第3张图片

TextView加载耗时:5.07ms,在初始化加载过程中不再decode bitmap



public void dealClockChanging() {
        LogUtil.i("dealClockChanging");
        setBlurImage();
        if (isShow) {
            rlClockChange.setVisibility(View.VISIBLE);
            rlClockChange.postDelayed(runnable, 5000);
            if (tvChanging.getBackground() == null) {
                LogUtil.d("load tvChanging background");
                changingAnim = (AnimationDrawable) context.getDrawable(R.drawable.anim_changing);
                tvChanging.setBackground(changingAnim);
            } else {
                changingAnim = (AnimationDrawable) tvChanging.getBackground();
            }
            changingAnim.start();
        }
    }

HomeActivity的fragment优化

修改前:HomeActivity在启动的时候加载了较多fragment,而且这些fragment在加载布局后也会取刷新数据然后又会更新界面导致在启动过程中加载布局,绘制界面,加载数据,再次绘制界面,measure的代价太大

03-16 17:27:52.838 2635-2635/com.xtc.i3launcher I/LaunchComputer: finish HomeActivity.onResume [5708] ms
03-16 17:28:09.104 2635-2635/com.xtc.i3launcher I/LaunchComputer: onWindowFocusChanged [19381] ms

如上,measure花费了4.502s时间,而且从onResume到onWindowFocusChanged需要大概10-15秒的时间

现在我们来做一个测试,把HomeActivity启动过程中需要加载的fragment先注释掉,全部不加载:

APP启动优化案例_第4张图片

03-22 09:32:43.586: I/LaunchComputer(7512): finish HomeActivity.onResume [1582] ms
03-22 09:32:44.020: I/LaunchComputer(7512): onWindowFocusChanged [2015] ms

现在我们终于找到为什么onResume到onWindowFocusChanged需要花那么长的时间了,因为在onResume调用完了之后,系统会加载Activity中还未加载的控件(fragment),然后再进行布局的绘制(measure,layout,draw),最后添加到窗口中取然后再设置窗体可见,这时候Activity才是真正可见的,如果没有加载fragment,也没有刷新数据,自然就没有布局加载,没有界面绘制,那么从onResume到onWindowFocusChanged这个过程做的事情就很少了,因此时间花费也会很短

延迟加载主界面的fragment

  • 首先加载ClockFragment,其他的fragment都不加载,等ClockFragment加载完成并显示出来之后才加载其他的fragment,这个可大大降低HomeActivity的启动速度,把一些不需要第一时间可视化的加载操作延迟加载

通知栏(NotificationFragment),下拉通知显示(PullUpFragment)和状态栏(StatusBarFragment)的fragment优化

修改前:

在HomeActivity.onCreate方法中调用:

private void initFragment() {
        FragmentManager fragmentManager = getSupportFragmentManager();
        statusBarFragment = (StatusBarFragment) fragmentManager.findFragmentById(R.id.fragment_status_bar);
        notificationFragment = (NotificationFragment) fragmentManager.findFragmentById(R.id.fragment_notification);
        pullUpFragment = (PullUpFragment) fragmentManager.findFragmentById(R.id.fragment_pull_up);
        statusBarFragment.setCallback(this);
        notificationFragment.setCallback(this);
        pullUpFragment.setCallback(this);

        asyncInitPullUp();

        homeContainer.setPullDownList(notificationFragment.getPullDownList());
    }

修改后:

public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        LogUtil.i("wtx hasFocus" + hasFocus);
        if (hasFocus && !firstInit) {
            initFragment();
            LaunchComputer.trace("onWindowFocusChanged");
            firstInit = true;
        }
        if (!hasFocus) {
            DisplayUtil.setNormalScreenOffTimeout(this);
            setHasSetTime(false);
        } else {
            setTimeOnClock();
        }
        startCostTimeBehavior();
    }

private void initFragment() {
        // 开机速度优化:让fragment变成动态加载,并且放到onWindowFocusChanged中首次初始化执行
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        statusBarFragment = StatusBarFragment.newInstance();
        fragmentTransaction.add(R.id.ll_pull_down, statusBarFragment);

        notificationFragment = NotificationFragment.newInstance();
        fragmentTransaction.add(R.id.ll_pull_down, notificationFragment);

        fragmentTransaction.commit();

        /*FragmentManager fragmentManager = getSupportFragmentManager();
        statusBarFragment = (StatusBarFragment) fragmentManager.findFragmentById(R.id.fragment_status_bar);
        notificationFragment = (NotificationFragment) fragmentManager.findFragmentById(R.id.fragment_notification);*/
        pullUpFragment = (PullUpFragment) fragmentManager.findFragmentById(R.id.fragment_pull_up);

        statusBarFragment.setCallback(this);
        notificationFragment.setCallback(this);
        pullUpFragment.setCallback(this);

        asyncInitPullUp();

        homeContainer.setPullDownList(notificationFragment.getPullDownList());
    }

改成动态加载fragment的方式,这样在launcher启动初始化的时候就不需要取加载布局文件中的fragment,而是等主界面看到之后才会去动态加载

ClockFragment布局加载优化

  • 用ViewStub替代无需马上加载的控件,在用到的时候采取加载这些控件
private void initPromptStatus() {
        if (llPromptStatus == null) {
            ViewStub llPromptStatusViewStub = (ViewStub) clockLayout.findViewById(R.id.view_stub_ll_clock_prompt_status);
            llPromptStatusViewStub.inflate();
            llPromptStatus = (LinearLayout) clockLayout.findViewById(R.id.ll_clock_prompt_status);
            llPromptStatus.setOnClickListener(this);
        }
    }

    private void initPromptWeather() {
        if (llPromptWeather == null) {
            ViewStub llPromptWeatherViewStub = (ViewStub) clockLayout.findViewById(R.id.view_stub_ll_clock_prompt_weather);
            llPromptWeatherViewStub.inflate();
            llPromptWeather = (LinearLayout) clockLayout.findViewById(R.id.ll_clock_prompt_weather);
            llPromptWeather.setOnClickListener(clickListener);
            ivPromptWeatherAlarm = (ImageView) llPromptWeather.findViewById(R.id.iv_clock_prompt_weather_alarm);
        }
    }
  • 重复加载加载表盘界面,表盘界面需要取sd卡下加载dex包,会有OpenDexFilesFromOat开销,经测试发现加载了两次表盘的dex,所以执行了两次OpenDexFilesFromOat,实际上只需执行一次,所以把其中重复的那次去掉就行了

修改前:

加载表盘成功后需要把View添加到布局中,会引起measure,layout,draw一些列UI绘制操作,这里主要耗时在measure操作,一共耗时:1.96s

修改后:

修改后measure操作耗时:1.702s

  • 当前表盘有时候需要取sd卡下加载指定的dex包,这就会让系统去对dex包进行一些解析操作(dex2oat)在这个过程中就会产生一定的时间消耗

可以看出,仅仅加载一个ClockFragment,measure耗时2.072s,其中大部分时间耗费在OpenDexFilesFromOat操作上,第一个OpenDexFilesFromOat耗时1.125s,第二个OpenDexFilesFromOat耗时507.981ms,而我查看代码发现这个就是由于加载ClockFragment的时候取sd卡下加载dex包导致的

ContactFragment布局优化

  • 去掉多余的布局层级

修改前



    


加载ContactFragment布局耗时40.13ms

修改后:


APP启动优化案例_第5张图片

加载ContactFragment布局耗时34.126ms

WeichatFragment布局加载优化

  • 用ViewStub替代无需马上加载的控件,在用到的时候采取加载这些控件

修改前:



    

    
    
    
        

        
    

加载WeichatFragment的布局移动耗时:20.732ms

修改后:




    

    


替换ViewStub后WeichatFragment的布局加载耗时:11.995ms

MotionFragment布局加载优化

  • ViewStub替换优化

修改前:


    
    

ForbiddenVie是一个RelativeLayout,它有加载了一套布局:

public class ForbiddenView extends RelativeLayout{
    private ImageView stepIcon;
    public ForbiddenView(Context context){
        this(context,null);
    }
    public ForbiddenView(Context context, AttributeSet attrs){
        this(context,attrs,0);
    }
    public ForbiddenView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutInflater.from(context).inflate(R.layout.i11_motion_main_forbidden,this,true);
        stepIcon = (ImageView)findViewById(R.id.iv_step_icon);
    }
    public ImageView getHeaderTitle(){
       return stepIcon;
    }
}


    
    
    

加载MotionFragment布局耗时:70.3ms

修改后:


    

    


加载MotionFragment布局耗时:8.198ms

AppFragment布局优化

  • 去掉多余的布局层级

修改前:



    

加载AppFragment布局耗时:6.321ms

修改后:


APP启动优化案例_第6张图片

加载AppFragment布局耗时:10.976ms,那么问题来了,优化过后的布局加载耗时为何比优化前的布局加载耗时要长?这不扯淡?从理论的角度取分析,优化前的布局层级是2层,优化后的布局层级是1层,因此布局加载和绘制肯定是1层布局要优于两层布局的,这个毋庸置疑,但是为何优化后的时间反而加长?事实是在加载布局的过程中,可能收到其他因素的干扰导致主线程卡顿了:

APP启动优化案例_第7张图片

这里写图片描述

如上,再加载AppFragment布局过程中,UI线程处于sleeping状态,而此时RxIoScheduler-6处于running状态,这就说明此时的cpu资源被RxIoScheduler-6线程抢走,因此UI线程被挂起休眠,所以AppFragment布局加载到一半就在这卡了6.101ms,那么扣除这卡顿的时间,AppFragment最后大致的加载耗时为:10.976 - 6.101 = 4.875ms;注意这里的UI线程被挂起是随机的,并不是每次加载AppFragment布局的时候UI线程都会被挂起

HomeActivity的Fragment的初始化优化

  • 在onCreate方法中先加载并显示ClockFragment,因为ClockFragment是最先需要马上看到的

  • 把其他的Fragment(ContactFragment,WeichatFragment,MotionFragment,GradeFragment,AppFragment)的加载与显示放到onWindowFocusChanged当中取执行,避免在启动过程中加载大量的布局,刷新数据,绘制界面造成线程竞争,卡顿主线程,拖慢启动速度

  • 在onWindowFocusChanged中需要用handler执行UI刷新任务,这样才不会卡住onWindowFocusChanged,导致页面显示变慢,这样能做到的效果是ClockFragment先显示,然后再取刷新其他数据

修改后的代码:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        DisplayUtil.isCreate = true;
        startMainCreateTimeMillis = SystemClock.elapsedRealtime();
        LaunchComputer.trace("before setContentView for HomeActivity");
        setContentView(R.layout.activity_home); // TODO: 2018/3/6 耗时
        LaunchComputer.trace("setContentView for HomeActivity");

        LogUtil.d("onCreate width = " + ScreenUtils.getScreenWidth(this) +
                "---height = " + ScreenUtils.getScreenHeight(this));

        initData();
        LaunchComputer.trace("HomeActivity.initData");

        initAnimation();
        LaunchComputer.trace("HomeActivity.initAnimation");

        initView();
        LaunchComputer.trace("HomeActivity.initView");

        CloudMusicReceiver.getInstance().registerReceiver(this);
        EventBus.getDefault().register(this);

        LaunchComputer.trace("finish HomeActivity.onCreate");
    }

@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        LogUtil.i("wtx hasFocus" + hasFocus);
        if (hasFocus && !firstInit) {
            firstInit = true;
            LaunchComputer.trace("onWindowFocusChanged");
            windowFocusedInit();
            LaunchComputer.trace("windowFocused init finished");
        }
        if (!hasFocus) {
            DisplayUtil.setNormalScreenOffTimeout(this);
            setHasSetTime(false);
        } else {
            setTimeOnClock();
        }
        startCostTimeBehavior();
    }

    private void windowFocusedInit() {
        // 开机速度优化:把一些无需马上执行的操作放到这里执行
        initOther();
        LaunchComputer.trace("initOther");

        initFragment();
        LaunchComputer.trace("initFragment");

        initForPresenter();
        LaunchComputer.trace("initForPresenter");

        // 开机速度优化:这里用handler来post执行是为了不卡住windowFocusedInit方法
        // 让onWindowFocusChanged能马上执行完成,然后再执行以下任务取刷新UI
        presenter.invalidateUI(new Runnable() {
            @Override
            public void run() {
                initClassMode();
                LaunchComputer.trace("initClassMode");

                initWatchLoss();
                LaunchComputer.trace("initWatchLoss");

                initPowerSave();
                LaunchComputer.trace("initPowerSave");

                initTheme();
                LaunchComputer.trace("initTheme");

                DisplayUtil.startWakeLock(getApplicationContext(), false);
                LaunchComputer.trace("HomeActivity.startWakeLock");

                SharedTool.saveOneTrafficBytes(getApplicationContext(), 0); // 开机启动流量置0,为了后面的流量统计提供使用
                LaunchComputer.trace("SharedTool.saveOneTrafficBytes");

                SystemBehavior.behaviorSystemInfo();
                LaunchComputer.trace("behaviorSystemInfo");

                ShellUtil.systemShutdownOrAppCrashBehavior(getApplicationContext());
                LaunchComputer.trace("systemShutdownOrAppCrashBehavior");

                SyncData.launcher(getApplicationContext());
                LaunchComputer.trace("launcher");
            }
        });
    }

Launcher启动过程中关于InitService的优化

SyncData.launcher(getApplicationContext())方法:

public static void launcher(final Context context) {
        if (isInit) {
            LogUtil.d(TAG, "已经初始化过了");
            return;
        }
        LogUtil.d(TAG, "launcher");
        isInit = true;
        isLauncher = true;
        isTouchWatch = false;
        syncSuccess = false;
        syncCompleted = false;
        startSystemTaskCompleted = false;

        // 设置固定时间后,自动同步数据
        LogUtil.d(TAG, "手表启动,检测相关状态");
        Random random = new Random();
        long launcherTime = System.currentTimeMillis()
                + (1000L * 60 * (random.nextInt(20) + 10));
        SystemAlarm.setAlarm(context, getInitIntent(context), launcherTime);

        Intent intent = new Intent(context, InitService.class);
        intent.putExtra("startSystemTask", true);
        context.startService(intent);
    }

把SyncData.launcher(getApplicationContext())放到HomeActivity的onWindowFocusChanged的第一次focus的时候执行,避免在Launcher启动过程中,界面还未显示就同时也去加载一些数据,可以等界面显示出来后再去加载数据也不迟

Launcher启动过程中的广播优化

  • 在HomeActivity的onCreate方法中执行了子方法initOther(),在这个方法中发送了一个广播
private void initOther() {
        LogUtil.d("initOther");
        showSimCardLock(presenter.isSimCardLock());
        PopWindowManager.initPopWindow(this);
        HighTemperatureReceiver.initSystemHighTemperature(this);

        Intent intent = new Intent(ACTION_LAUNCHER_START);
        sendBroadcast(intent);
        DelayInit.execute(this);

        ModemLogServeImpl.getInstance(this).checkLastModemLog(true);
        TcpDumpServeImpl.getInstance(this).checkLastTcpDump();
    }

修改前:

APP启动优化案例_第8张图片

ActivityThread.java

Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
                    handleReceiver((ReceiverData)msg.obj);
                    maybeSnapshot();
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
handleReceiver()方法

Application app = packageInfo.makeApplication(false, mInstrumentation);

            if (localLOGV) Slog.v(
                TAG, "Performing receive of " + data.intent
                + ": app=" + app
                + ", appName=" + app.getPackageName()
                + ", pkg=" + packageInfo.getPackageName()
                + ", comp=" + data.intent.getComponent().toShortString()
                + ", dir=" + packageInfo.getAppDir());

            ContextImpl context = (ContextImpl)app.getBaseContext();
            sCurrentBroadcastIntent.set(data.intent);
            receiver.setPendingResult(data);
            receiver.onReceive(context.getReceiverRestrictedContext(),
                    data.intent);

此广播发送后,接收者执行onReceive方法,处理广播流程的过程中耗时:136.162ms

修改后:

把initOther()方法放到HomeActivity的onWindowFocusChanged的第一次focus的时候执行,这样在Launcher启动过程中就不会取发送广播,自然也就不用处理广播了

public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        LogUtil.i("wtx hasFocus" + hasFocus);
        if (hasFocus && !firstInit) {
            windowFocused();
            LaunchComputer.trace("onWindowFocusChanged");
            firstInit = true;
        }
        if (!hasFocus) {
            DisplayUtil.setNormalScreenOffTimeout(this);
            setHasSetTime(false);
        } else {
            setTimeOnClock();
        }
        startCostTimeBehavior();
    }

    private void windowFocused() {
        initFragment();
        initOther();
        // 开机速度优化:把数据同步操作放到onWindowFocusChanged中执行
        SyncData.launcher(getApplicationContext());
    }

优化结果

你可能感兴趣的:(android)