如何優(yōu)化Androd App啟動速度

在上一篇文章《如何統(tǒng)計Android App啟動時間》中我們探討了如何統(tǒng)計Android App的啟動時間背蟆,以及簡要分析了App啟動流程禁偎。這一篇文章主要講如何在實戰(zhàn)中提升Android App的啟動速度。下面我們先回顧一下App的啟動流程。轉(zhuǎn)載請注明出處:Lawrence_Shen
同時可以參考2019年的性能分析文章:Android性能分析&啟動優(yōu)化

App 啟動流程分析

上一篇文章《如何統(tǒng)計Android App啟動時間》我們定義了從用戶角度上觀察的啟動時間。我們把這段時間再細分成兩段,一段是從用戶點擊Launcher圖標到進入第一個Acitivity的時間锈拨,另一段是從第一個Activity到最后首頁Activity完全展示出來用戶可進行操作的時間。在第一段時間中耗時的任務(wù)主要體現(xiàn)在Application的創(chuàng)建羹唠,第二段時間耗時主要是因為Activity的創(chuàng)建以及在最后首頁Activity展示之前的業(yè)務(wù)流程奕枢。主要解決的思路有兩個:一個是盡可能將初始化延后到真正調(diào)用的時候,另一個是盡可能將不是用戶第一時間能體驗的業(yè)務(wù)功能延后佩微。經(jīng)過對我們App的詳細分析以及對業(yè)務(wù)的了解缝彬,可以通過以下一些方法來解決應(yīng)用啟動慢的問題。

解決問題

控制Static初始化范圍

啟動過程可能會用到一些Utils等工具類哺眯,這些類中包含了幾乎整個項目需要使用到的工具谷浅。我們在優(yōu)化的過程中發(fā)現(xiàn)某些Utils類中定義了靜態(tài)變量,而這些靜態(tài)變量的初始化會有一定耗時奶卓。這里需要注意可以把靜態(tài)變量的初始化移到第一次使用的時候一疯。這樣可以避免在用到工具類的其他方法時提前做了沒必要的初始化。例如一個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 初始化延遲

對于一些只有在特定情況下才會出現(xiàn)的view夺姑,我們可以通過ViewStub延后他們的初始化墩邀。例如出于廣告業(yè)務(wù)的需求,在有廣告投放的時候需要在首頁展示一個視頻或者一個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懶加載

如果應(yīng)用使用一層甚至幾層ViewPager竹海,然后為了讓加載后Fragment不被銷毀而改變了setOffscreenPageLimit()來緩存所有Fragment慕蔚,那么ViewPager會一次性將所有Fragment進行渲染,如果Fragment本身又包含了耗時很長的初始化將嚴重影響App的啟動速度斋配。即使是使用默認設(shè)置setOffscreenPageLimit(1)孔飒,也會加載前一頁和后一頁的Fragment。因此我們考慮需要對Fragment進行懶加載艰争。這里可以使用兩種方式來實現(xiàn)Fragment的懶加載十偶。
??第一種方式是繼承模式,通過繼承懶加載Fragment基類园细,在得到用戶焦點后再調(diào)用生命周期方法。具體實現(xiàn)如下:

/**
 * 使用繼承方式實現(xiàn)的懶加載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是否已經(jīng)初始化view
     *
     * @return 已經(jīng)初始化view返回true接校,否則返回false
     */
    @SuppressWarnings("unused")
    public boolean isLazyViewCreated() {
        return isLazyViewCreated;
    }

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

    /**
     * 用于替代真實Fragment的onCreateView猛频,在真正獲取到用戶焦點后才會調(diào)用
     *
     * @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蛛勉,并將的onCreateView鹿寻,onViewCreated方法修改為onLazyCreateViewonLazyViewCreated诽凌。修改如下圖所示毡熏。

繼承延遲加載Fragment對比.PNG

創(chuàng)建時直接new出來InheritedLazyFragment.newInstance("InheritedLazyFragment", position);

第一種方式是代理模式侣诵,先創(chuàng)建代理的Fragment痢法,當代理Fragment得到用戶焦點之后再將真實的Fragment加入其中。具體實現(xiàn)如下:

/**
 * 使用代理方式實現(xiàn)的懶加載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);
        }
    }

    /**
     * 獲取對應(yīng)的真正的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做特殊的改動财搁,只需要在創(chuàng)建的時候通過代理Fragment進行創(chuàng)建:

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

具體實現(xiàn)代碼見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的調(diào)用順序尖奔。兩種方式的優(yōu)缺點如下表:

實現(xiàn)方式 優(yōu)點 缺點
繼承方式 不需要改變創(chuàng)建及管理代碼 onResume()等方法在真實的createView之前調(diào)用,生命周期與沒延遲化之前有差異
代理方式 1. 不需要改變真實Fragment代碼</br> 2. 生命周期沒有變化 管理以及創(chuàng)建代碼需要修改

效果如下:

fragment-lazy-load.gif

使用后臺線程

在啟動的過程中穷当,盡量把能在后臺做的任務(wù)都放到后臺提茁,可以使用以下幾個方式來執(zhí)行后臺任務(wù):

  • AsyncTask: 為UI線程與工作線程之間進行快速的切換提供一種簡單便捷的機制。適用于當下立即需要啟動馁菜,但是異步執(zhí)行的生命周期短暫的使用場景茴扁。
  • HandlerThread: 為某些回調(diào)方法或者等待某些任務(wù)的執(zhí)行設(shè)置一個專屬的線程,并提供線程任務(wù)的調(diào)度機制火邓。
  • ThreadPool: 把任務(wù)分解成不同的單元丹弱,分發(fā)到各個不同的線程上德撬,進行同時并發(fā)處理。
  • IntentService: 適合于執(zhí)行由UI觸發(fā)的后臺Service任務(wù)躲胳,并可以把后臺任務(wù)執(zhí)行的情況通過一定的機制反饋給UI蜓洪。

使用EventBus

適當?shù)厥褂肊ventBus可以延后一些初始化。在需要的地方post一個事件坯苹,EventBus會通知注冊過這些事件的地方隆檀,這樣可以把一些初始化在真實需要的時候再post一個觸發(fā)事件,然后延后初始化粹湃。

EventBus使用3步驟

  1. 定義事件:
    public static class MessageEvent { /* Additional fields if needed */ }
    
  2. 在需要的地方注冊:
    可以指定線程模式 thread mode:
    @Subscribe(threadMode = ThreadMode.MAIN)  
    public void onMessageEvent(MessageEvent event) {/* Do something */};
    
    注冊與反注冊
     @Override
     public void onStart() {
         super.onStart();
         EventBus.getDefault().register(this);
     }
    
     @Override
     public void onStop() {
         super.onStop();
         EventBus.getDefault().unregister(this);
     }
    
  3. 發(fā)送事件:
     EventBus.getDefault().post(new MessageEvent());
    

更詳細的使用參見 How to get started with EventBus in 3 steps.

啟動閃屏主題設(shè)置

默認的啟動閃屏是白色的恐仑,某些開發(fā)者會通過設(shè)置一個透明的啟動閃屏主題來隱藏啟動加載慢的問題,不過這種做法會影響用戶體驗为鳄。我們可以通過設(shè)置一個帶logo的啟動閃屏主題來讓用戶感受到在點擊桌面圖標后馬上得到響應(yīng)裳仆。不過這里需要注意啟動閃屏主題不能使用很大的圖片資源,因為加載這些資源本身也是耗時的孤钦。
??設(shè)置啟動閃屏可以在第一個展示的Acitivty設(shè)置主題:

AndroidManifest.xml:

<activity
    android:name=".activity.DictSplashActivity"
    android:theme="@style/MyLightTheme.NoActionBar.FullScreen">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

styles.xml:

<style name="MyLightTheme.NoActionBar.FullScreen" parent="MyLightTheme.NoActionBar">
    <item name="android:windowBackground">@drawable/bg_launcher</item>
    <item name="android:windowFullscreen">true</item>
</style>

bg_launcher.xml:

<?xml version="1.0" encoding="utf-8"?><!--
  ~ @(#)bg_launcher.xml, 2017-02-06.
  ~
  ~ Copyright 2014 Yodao, Inc. All rights reserved.
  ~ YODAO PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  -->

<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:opacity="opaque">
    <!-- The background color, preferably the same as your normal theme -->
    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/background_grey"/>
            <size android:height="640dp" android:width="360dp"/>
        </shape>
    </item>
    <!-- Your product logo - 144dp color version of your app icon -->
    <item>
        <bitmap
            android:gravity="bottom|center"
            android:src="@drawable/splash_bottom" />
    </item>
</layer-list>

效果如下:

啟動閃屏主題.gif

其他可以優(yōu)化的細節(jié)

  • 減少廣告等業(yè)務(wù)邏輯時間
    ??這里屬于業(yè)務(wù)邏輯的優(yōu)化歧斟,可根據(jù)不同的應(yīng)用發(fā)掘可以縮短的等待時間。
  • SharePreferences中的commit改為apply
    ??SharePreferences的操作涉及文件的讀寫偏形,最好盡量使用apply方法代替commit方法静袖。apply方法會先將結(jié)果保存在內(nèi)存的SharePreferences中并異步地更新SharePreferences文件
  • onPause不要執(zhí)行太多任務(wù)
    ??在展示另一個Acitivty之前,需要經(jīng)過上一個Acitvity的onPause()方法俊扭,因此在Activity的onPause()方法中不適合有耗時的工作队橙。
  • ContentProvider不要做太多靜態(tài)初始化以及在onCreate()中做耗時操作。
    ??因為ContentProvideronCreate()會在Application onCreate()之前調(diào)用萨惑。
  • 減少View層級
    ??減少View的層級可以有效避免過度繪制捐康,減少不必要的繪制過程。
  • 注意內(nèi)存抖動
    ??瞬間產(chǎn)生大量的對象會嚴重占用Young Generation的內(nèi)存區(qū)域庸蔼,當達到閥值吹由,剩余空間不夠的時候,會觸發(fā)GC朱嘴。即使每次分配的對象占用了很少的內(nèi)存倾鲫,但是他們疊加在一起會增加Heap的壓力,從而觸發(fā)更多其他類型的GC萍嬉。這個操作有可能會影響到幀率乌昔,并使得用戶感知到性能問題。
  • 用更快的方式獲取信息壤追,例如獲取Webview UA
    ??獲取Webview UA可以通過創(chuàng)建要給Webview然后獲取setting中的UserAgent磕道,不過為了獲取UA而創(chuàng)建Webview是一個比較耗時的操作。我們可以在API17及以上的系統(tǒng)中通過WebSettings.getDefaultUserAgent(context)快速獲取行冰。
  • 盡量刪除沒必要的中間過渡Activity溺蕉,減少Activity切換時間
    ??Activity的切換是比較耗時的伶丐,如果沒有必要,我們可以將達到主要頁面之前的Activity刪除疯特,或者修改成Fragment動態(tài)加入哗魂。

后記

通過之前的分析以及這篇文章介紹的啟動優(yōu)化方法,我們詞典的啟動速度得到了50%的提升漓雅,有效地提升了用戶體驗录别。在以后的開發(fā)過程中,當涉及到啟動流程的代碼時需要格外謹慎邻吞,避免有耗時的操作加入组题。當然目前的詞典啟動速度還可以進一步優(yōu)化,可以思考的方向一下幾點:1. 進一步優(yōu)化信息流布局抱冷,減少不必要的繪制崔列;2. 深入探索第三方SDK帶來的啟動速度延遲并嘗試優(yōu)化;3. 獲取更多實時廣告的成功率并嘗試去除實時廣告邏輯旺遮。

參考

【1】胡凱峻呕,2016.Android性能優(yōu)化典范 - 第5季
【2】胡凱,2016.Android性能優(yōu)化典范 - 第6季
【3】TellH的博客趣效,2016.實現(xiàn)類似微信Viewpager-Fragment的惰性加載,lazy-loading

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猪贪,一起剝皮案震驚了整個濱河市跷敬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌热押,老刑警劉巖西傀,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桶癣,居然都是意外死亡拥褂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門牙寞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饺鹃,“玉大人,你說我怎么就攤上這事间雀』谙辏” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵惹挟,是天一觀的道長茄螃。 經(jīng)常有香客問我,道長连锯,這世上最難降的妖魔是什么归苍? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任用狱,我火速辦了婚禮,結(jié)果婚禮上拼弃,老公的妹妹穿的比我還像新娘夏伊。我一直安慰自己,他們只是感情好肴敛,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布署海。 她就那樣靜靜地躺著,像睡著了一般医男。 火紅的嫁衣襯著肌膚如雪砸狞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天镀梭,我揣著相機與錄音刀森,去河邊找鬼。 笑死报账,一個胖子當著我的面吹牛研底,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播透罢,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼榜晦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了羽圃?” 一聲冷哼從身側(cè)響起乾胶,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎朽寞,沒想到半個月后识窿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡脑融,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年喻频,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肘迎。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡甥温,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出妓布,到底是詐尸還是另有隱情窿侈,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布秋茫,位于F島的核電站史简,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜圆兵,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一跺讯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧殉农,春花似錦刀脏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至轮傍,卻和暖如春暂雹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背创夜。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工杭跪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人驰吓。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓涧尿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親檬贰。 傳聞我的和親對象是個殘疾皇子姑廉,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容