如何优化Androd App启动速度(上篇)

阿凡达2018-07-09 10:34

 在上一篇文章《如何统计Android App启动时间》中我们探讨了如何统计Android App的启动时间,以及简要分析了App启动流程。这一篇文章主要讲如何在实战中提升Android App的启动速度。下面我们先回顾一下App的启动流程。转载请注明出处:Lawrence_Shen

App 启动流程分析

  上一篇文章《如何统计Android App启动时间》我们定义了从用户角度上观察的启动时间。我们把这段时间再细分成两段,一段是从用户点击Launcher图标到进入第一个Acitivity的时间,另一段是从第一个Activity到最后首页Activity完全展示出来用户可进行操作的时间。在第一段时间中耗时的任务主要体现在Application的创建,第二段时间耗时主要是因为Activity的创建以及在最后首页Activity展示之前的业务流程。主要解决的思路有两个:一个是尽可能将初始化延后到真正调用的时候,另一个是尽可能将不是用户第一时间能体验的业务功能延后。经过对我们App的详细分析以及对业务的了解,可以通过以下一些方法来解决应用启动慢的问题。

解决问题

控制Static初始化范围

  启动过程可能会用到一些Utils等工具类,这些类中包含了几乎整个项目需要使用到的工具。我们在优化的过程中发现某些Utils类中定义了静态变量,而这些静态变量的初始化会有一定耗时。这里需要注意可以把静态变量的初始化移到第一次使用的时候。这样可以避免在用到工具类的其他方法时提前做了没必要的初始化。例如一个Utils如下:

public class ExampleUtils {
    private static HeavyObject sHeavyObject = HeavyObject.newInstance(); //比较耗时的初始化
    ...
    public static void useHeavyObject() {
        sHeavyObject.doSomething();
    }

    /**
     * 
     * 启动过程中需要用到的方法
     */
    public static void methodUseWhenStartUp() {
      ...
    }
    ...
}

可以修改为:

public class ExampleUtils {
    private static HeavyObject sHeavyObject;
    ...
    public static void useHeavyObject() {
        if (sHeavyObject == null) {
            sHeavyObject = HeavyObject.newInstance(); //比较耗时的初始化
        }
        sHeavyObject.doSomething();
    }

    /**
     * 
     * 启动过程中需要用到的方法
     */
    public static void methodUseWhenStartUp() {
      ...
    }
    ...
}

ViewStub 初始化延迟

  对于一些只有在特定情况下才会出现的view,我们可以通过ViewStub延后他们的初始化。例如出于广告业务的需求,在有广告投放的时候需要在首页展示一个视频或者一个h5广告。由于视频控件以及webview的初始化需要耗费较长时间,我们可以使用ViewStub,然后在需要显示的时候通过ViewStub的inflate显示真正的view。例如在启动页的xml中某一段如下:

<com.example.ad.h5Ad.ui.H5AdWebView
    android:id="@+id/ad_web"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="gone" />

可以修改为:

<ViewStub
    android:id="@+id/ad_web_stub"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout="@layout/h5_ad_layout"/>

并新建一个h5_ad_layout.xml如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.example.ad.h5Ad.ui.H5AdWebView
        android:id="@+id/ad_web"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

</LinearLayout>

然后在代码中需要显示webview时进行inflate:

...
    private void setupView() {
        ...  
        mAdWebViewStub = (ViewStub) findViewById(R.id.ad_web_stub);
        mAdWebViewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
            @Override
            public void onInflate(ViewStub stub, View inflated) {
                isAdWebStubInflated = true;
            }
        });
        ...
    }

    /**
     * 显示H5交互广告
     */
    private void showWebAd() {
        ...
        if (!isAdWebStubInflated) {
            View h5AdLayout = mAdWebViewStub.inflate();
            mAdWebView = (H5AdWebView) h5AdLayout.findViewById(R.id.ad_web);
        }
        ...
    }

Fragment懒加载

  如果应用使用一层甚至几层ViewPager,然后为了让加载后Fragment不被销毁而改变了setOffscreenPageLimit()来缓存所有Fragment,那么ViewPager会一次性将所有Fragment进行渲染,如果Fragment本身又包含了耗时很长的初始化将严重影响App的启动速度。即使是使用默认设置setOffscreenPageLimit(1),也会加载前一页和后一页的Fragment。因此我们考虑需要对Fragment进行懒加载。这里可以使用两种方式来实现Fragment的懒加载。   第一种方式是继承模式,通过继承懒加载Fragment基类,在得到用户焦点后再调用生命周期方法。具体实现如下:

/**
 * 使用继承方式实现的懒加载Fragment基类
 */
public abstract class InheritedFakeFragment extends Fragment {
    protected FrameLayout rootContainer;
    private boolean isLazyViewCreated = false;
    private LayoutInflater inflater;
    private Bundle savedInstanceState;

    @Nullable
    @Override
    public final View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        this.inflater = inflater;
        this.savedInstanceState = savedInstanceState;
        rootContainer = new FrameLayout(getContext().getApplicationContext());
        rootContainer.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        return rootContainer;
    }

    @Override
    public final void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser && !isLazyViewCreated && inflater != null) {
            View view = onLazyCreateView(inflater, rootContainer, savedInstanceState);
            rootContainer.addView(view);
            isLazyViewCreated = true;
            onLazyViewCreated(rootContainer, savedInstanceState);
        }
    }

    /**
     * 获取真实的fragment是否已经初始化view
     *
     * @return 已经初始化view返回true,否则返回false
     */
    @SuppressWarnings("unused")
    public boolean isLazyViewCreated() {
        return isLazyViewCreated;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        isLazyViewCreated = false;
    }

    /**
     * 用于替代真实Fragment的onCreateView,在真正获取到用户焦点后才会调用
     *
     * @param inflater           - The LayoutInflater object that can be used to inflate any views in the fragment,
     * @param container          - If non-null, this is the parent view that the fragment's UI should be attached to. The fragment should not add the view itself, but this can be used to generate the LayoutParams of the view.
     * @param savedInstanceState - If non-null, this fragment is being re-constructed from a previous saved state as given here.
     * @return Return the View for the fragment's UI, or null.
     */
    protected abstract View onLazyCreateView(LayoutInflater inflater, ViewGroup container, @Nullable Bundle savedInstanceState);

    /**
     * 用来代替真实Fragment的onViewCreated,在真正获得用户焦点并且{@link #onLazyViewCreated(View, Bundle)}
     *
     * @param view               - The View returned by onCreateView(LayoutInflater, ViewGroup, Bundle).
     * @param savedInstanceState - If non-null, this fragment is being re-constructed from a previous saved state as given here.
     */
    protected abstract void onLazyViewCreated(View view, @Nullable Bundle savedInstanceState);

}

真正的Fragment需要继承InheritedFakeFragment,并将的onCreateViewonViewCreated方法修改为onLazyCreateViewonLazyViewCreated。修改如下图所示。

创建时直接new出来InheritedLazyFragment.newInstance("InheritedLazyFragment", position);

  第一种方式是代理模式,先创建代理的Fragment,当代理Fragment得到用户焦点之后再将真实的Fragment加入其中。具体实现如下:

/**
 * 使用代理方式实现的懒加载Fragment基类
 */
public class ProxyFakeFragment extends Fragment {
    private static final String REAL_FRAGMENT_NAME = "realFragmentName";

    private String realFragmentName;

    private Fragment realFragment;

    private LayoutInflater inflater;
    private boolean isRealFragmentAdded = false;
    private boolean isCurrentVisiable = false;


    public ProxyFakeFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param realFragmentName 需要替换的真实fragment.
     * @return A new instance of fragment FakeFragment.
     */
    @SuppressWarnings("unused")
    public static ProxyFakeFragment newInstance(String realFragmentName) {
        ProxyFakeFragment fragment = new ProxyFakeFragment();
        Bundle args = new Bundle();
        args.putString(REAL_FRAGMENT_NAME, realFragmentName);
        fragment.setArguments(args);
        return fragment;
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param realFragmentName 需要替换的真实fragment.
     * @param bundle           放入真实fragment 需要的bundle
     * @return A new instance of fragment FakeFragment.
     */
    @SuppressWarnings("unused")
    public static ProxyFakeFragment newInstance(String realFragmentName, Bundle bundle) {
        ProxyFakeFragment fragment = new ProxyFakeFragment();
        Bundle args = new Bundle();
        args.putString(REAL_FRAGMENT_NAME, realFragmentName);
        if (bundle != null) {
            args.putAll(bundle);
        }
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (TextUtils.isEmpty(realFragmentName) && getArguments() != null) {
            realFragmentName = getArguments().getString(REAL_FRAGMENT_NAME);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        this.inflater = inflater;
        View view = inflater.inflate(R.layout.fragment_fake, container, false);
        setUserVisibleHint(isCurrentVisiable);
        return view;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        isCurrentVisiable = isVisibleToUser;
        if (TextUtils.isEmpty(realFragmentName) && getArguments() != null) {
            realFragmentName = getArguments().getString(REAL_FRAGMENT_NAME);
        }
        if (!TextUtils.isEmpty(realFragmentName) && isVisibleToUser &&
                !isRealFragmentAdded) {
            getRealFragment();
            if (inflater != null) {
                addRealFragment();
            }
        }
        if (isRealFragmentAdded) {
            realFragment.setUserVisibleHint(isVisibleToUser);
        }
    }

    /**
     * 获取对应的真正的fragment实体
     *
     * @return 真正的fragment实体
     */
    public Fragment getRealFragment() {
        if (TextUtils.isEmpty(realFragmentName) && getArguments() != null) {
            realFragmentName = getArguments().getString(REAL_FRAGMENT_NAME);
        }
        if (!TextUtils.isEmpty(realFragmentName) && realFragment == null) {
            try {
                realFragment = (Fragment) Class.forName(realFragmentName).newInstance();
                realFragment.setArguments(getArguments());
                return realFragment;
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        } else if (realFragment != null) {
            return realFragment;
        } else {
            return null;
        }
    }

    private void addRealFragment() {
        if (realFragment != null) {
            getChildFragmentManager()
                    .beginTransaction()
                    .add(R.id.fake_fragment_container, realFragment)
                    .commit();
            getChildFragmentManager().executePendingTransactions();
            isRealFragmentAdded = true;
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (TextUtils.isEmpty(realFragmentName) && getArguments() != null) {
            realFragmentName = getArguments().getString(REAL_FRAGMENT_NAME);
        }
    }
}

使用这种代理的方式,并不需要对真实的Fragment做特殊的改动,只需要在创建的时候通过代理Fragment进行创建:

Bundle bundle = new Bundle();
bundle.putString(OriginFragment.FRAGMENT_MSG, "ProxyLazyFragment");
bundle.putInt(OriginFragment.FRAGMENT_POS, position);
return ProxyFakeFragment.newInstance(OriginFragment.class.getName(), bundle);

具体实现代码见github项目:shenguojun/LazyFragmentTest

以下看看不同方式对Fragment生命周期的影响。 先看正常的Fragment生命周期如下:

05-03 16:59:17.420 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , setUserVisibleHint: false
05-03 16:59:17.438 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onCreateView
05-03 16:59:17.439 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onViewCreated
05-03 16:59:17.439 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onActivityCreated
05-03 16:59:17.443 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onStart
05-03 16:59:17.444 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onResume
05-03 16:59:20.662 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , setUserVisibleHint: true
05-03 16:59:49.417 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , setUserVisibleHint: false
05-03 16:59:50.678 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onPause
05-03 16:59:50.678 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onStop
05-03 16:59:50.678 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest0: Pos: 0 , onDestroyView

使用继承方式真实Fragment生命周期如下:

05-03 17:00:20.795 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , setUserVisibleHint: false
05-03 17:00:20.800 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onActivityCreated
05-03 17:00:20.801 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onStart
05-03 17:00:20.801 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onResume
05-03 17:00:22.365 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onLazyCreateView
05-03 17:00:22.366 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onLazyViewCreated
05-03 17:00:22.366 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , setUserVisibleHint: true
05-03 17:00:25.197 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , setUserVisibleHint: false
05-03 17:00:26.037 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onPause
05-03 17:00:26.037 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onStop
05-03 17:00:26.038 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest1: Pos: 1 , onDestroyView

使用代理方式Fragment生命周期如下:

05-03 17:01:01.257 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , setUserVisibleHint: false
05-03 17:01:01.260 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onCreateView
05-03 17:01:01.260 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onViewCreated
05-03 17:01:01.260 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onActivityCreated
05-03 17:01:01.261 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onStart
05-03 17:01:01.261 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onResume
05-03 17:01:01.761 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , setUserVisibleHint: true
05-03 17:01:03.625 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , setUserVisibleHint: false
05-03 17:01:04.132 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onPause
05-03 17:01:04.133 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onStop
05-03 17:01:04.134 27200-27200/me.xshen.lazyfragmenttest D/FragmentTest2: Pos: 2 , onDestroyView

可以看出使用代理方式不改变Fragment的生命周期,但是使用继承方式改变了Fragment的调用顺序。两种方式的优缺点如下表:

实现方式 优点 缺点
继承方式 不需要改变创建及管理代码 onResume()等方法在真实的createView之前调用,生命周期与没延迟化之前有差异
代理方式 1. 不需要改变真实Fragment代码
2. 生命周期没有变化
管理以及创建代码需要修改

效果如下:

相关阅读:如何优化Androd App启动速度(下篇)

本文来自网易实践者社区,经作者申国骏授权发布。