在上一篇文章《如何統(tǒng)計(jì)Android App啟動(dòng)時(shí)間》中我們探討了如何統(tǒng)計(jì)Android App的啟動(dòng)時(shí)間,以及簡要分析了App啟動(dòng)流程。這一篇文章主要講如何在實(shí)戰(zhàn)中提升Android App的啟動(dòng)速度。下面我們先回顧一下App的啟動(dòng)流程。轉(zhuǎn)載請注明出處:Lawrence_Shen
同時(shí)可以參考2019年的性能分析文章:Android性能分析&啟動(dòng)優(yōu)化
App 啟動(dòng)流程分析
上一篇文章《如何統(tǒng)計(jì)Android App啟動(dòng)時(shí)間》我們定義了從用戶角度上觀察的啟動(dòng)時(shí)間。我們把這段時(shí)間再細(xì)分成兩段,一段是從用戶點(diǎn)擊Launcher圖標(biāo)到進(jìn)入第一個(gè)Acitivity的時(shí)間,另一段是從第一個(gè)Activity到最后首頁Activity完全展示出來用戶可進(jìn)行操作的時(shí)間。在第一段時(shí)間中耗時(shí)的任務(wù)主要體現(xiàn)在Application的創(chuàng)建,第二段時(shí)間耗時(shí)主要是因?yàn)锳ctivity的創(chuàng)建以及在最后首頁Activity展示之前的業(yè)務(wù)流程。主要解決的思路有兩個(gè):一個(gè)是盡可能將初始化延后到真正調(diào)用的時(shí)候,另一個(gè)是盡可能將不是用戶第一時(shí)間能體驗(yàn)的業(yè)務(wù)功能延后。經(jīng)過對我們App的詳細(xì)分析以及對業(yè)務(wù)的了解,可以通過以下一些方法來解決應(yīng)用啟動(dòng)慢的問題。
解決問題
控制Static初始化范圍
啟動(dòng)過程可能會(huì)用到一些Utils等工具類,這些類中包含了幾乎整個(gè)項(xiàng)目需要使用到的工具。我們在優(yōu)化的過程中發(fā)現(xiàn)某些Utils類中定義了靜態(tài)變量,而這些靜態(tài)變量的初始化會(huì)有一定耗時(shí)。這里需要注意可以把靜態(tài)變量的初始化移到第一次使用的時(shí)候。這樣可以避免在用到工具類的其他方法時(shí)提前做了沒必要的初始化。例如一個(gè)Utils如下:
public class ExampleUtils {
private static HeavyObject sHeavyObject = HeavyObject.newInstance(); //比較耗時(shí)的初始化
...
public static void useHeavyObject() {
sHeavyObject.doSomething();
}
/**
*
* 啟動(dòng)過程中需要用到的方法
*/
public static void methodUseWhenStartUp() {
...
}
...
}
可以修改為:
public class ExampleUtils {
private static HeavyObject sHeavyObject;
...
public static void useHeavyObject() {
if (sHeavyObject == null) {
sHeavyObject = HeavyObject.newInstance(); //比較耗時(shí)的初始化
}
sHeavyObject.doSomething();
}
/**
*
* 啟動(dòng)過程中需要用到的方法
*/
public static void methodUseWhenStartUp() {
...
}
...
}
ViewStub 初始化延遲
對于一些只有在特定情況下才會(huì)出現(xiàn)的view,我們可以通過ViewStub延后他們的初始化。例如出于廣告業(yè)務(wù)的需求,在有廣告投放的時(shí)候需要在首頁展示一個(gè)視頻或者一個(gè)h5廣告。由于視頻控件以及webview的初始化需要耗費(fèi)較長時(shí)間,我們可以使用ViewStub,然后在需要顯示的時(shí)候通過ViewStub的inflate顯示真正的view。例如在啟動(dòng)頁的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"/>
并新建一個(gè)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時(shí)進(jìn)行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會(huì)一次性將所有Fragment進(jìn)行渲染,如果Fragment本身又包含了耗時(shí)很長的初始化將嚴(yán)重影響App的啟動(dòng)速度。即使是使用默認(rèn)設(shè)置setOffscreenPageLimit(1),也會(huì)加載前一頁和后一頁的Fragment。因此我們考慮需要對Fragment進(jìn)行懶加載。這里可以使用兩種方式來實(shí)現(xiàn)Fragment的懶加載。
??第一種方式是繼承模式,通過繼承懶加載Fragment基類,在得到用戶焦點(diǎn)后再調(diào)用生命周期方法。具體實(shí)現(xiàn)如下:
/**
* 使用繼承方式實(shí)現(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);
}
}
/**
* 獲取真實(shí)的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;
}
/**
* 用于替代真實(shí)Fragment的onCreateView,在真正獲取到用戶焦點(diǎn)后才會(huì)調(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);
/**
* 用來代替真實(shí)Fragment的onViewCreated,在真正獲得用戶焦點(diǎn)并且{@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方法修改為onLazyCreateView,onLazyViewCreated。修改如下圖所示。
創(chuàng)建時(shí)直接new出來InheritedLazyFragment.newInstance("InheritedLazyFragment", position);。
第一種方式是代理模式,先創(chuàng)建代理的Fragment,當(dāng)代理Fragment得到用戶焦點(diǎn)之后再將真實(shí)的Fragment加入其中。具體實(shí)現(xiàn)如下:
/**
* 使用代理方式實(shí)現(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 需要替換的真實(shí)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 需要替換的真實(shí)fragment.
* @param bundle 放入真實(shí)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實(shí)體
*
* @return 真正的fragment實(shí)體
*/
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);
}
}
}
使用這種代理的方式,并不需要對真實(shí)的Fragment做特殊的改動(dòng),只需要在創(chuàng)建的時(shí)候通過代理Fragment進(jìn)行創(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);
具體實(shí)現(xiàn)代碼見github項(xiàng)目: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
使用繼承方式真實(shí)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)缺點(diǎn)如下表:
| 實(shí)現(xiàn)方式 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|
| 繼承方式 | 不需要改變創(chuàng)建及管理代碼 |
onResume()等方法在真實(shí)的createView之前調(diào)用,生命周期與沒延遲化之前有差異 |
| 代理方式 | 1. 不需要改變真實(shí)Fragment代碼</br> 2. 生命周期沒有變化 | 管理以及創(chuàng)建代碼需要修改 |
效果如下:

使用后臺(tái)線程
在啟動(dòng)的過程中,盡量把能在后臺(tái)做的任務(wù)都放到后臺(tái),可以使用以下幾個(gè)方式來執(zhí)行后臺(tái)任務(wù):
- AsyncTask: 為UI線程與工作線程之間進(jìn)行快速的切換提供一種簡單便捷的機(jī)制。適用于當(dāng)下立即需要啟動(dòng),但是異步執(zhí)行的生命周期短暫的使用場景。
- HandlerThread: 為某些回調(diào)方法或者等待某些任務(wù)的執(zhí)行設(shè)置一個(gè)專屬的線程,并提供線程任務(wù)的調(diào)度機(jī)制。
- ThreadPool: 把任務(wù)分解成不同的單元,分發(fā)到各個(gè)不同的線程上,進(jìn)行同時(shí)并發(fā)處理。
- IntentService: 適合于執(zhí)行由UI觸發(fā)的后臺(tái)Service任務(wù),并可以把后臺(tái)任務(wù)執(zhí)行的情況通過一定的機(jī)制反饋給UI。
使用EventBus
適當(dāng)?shù)厥褂肊ventBus可以延后一些初始化。在需要的地方post一個(gè)事件,EventBus會(huì)通知注冊過這些事件的地方,這樣可以把一些初始化在真實(shí)需要的時(shí)候再post一個(gè)觸發(fā)事件,然后延后初始化。
EventBus使用3步驟
- 定義事件:
public static class MessageEvent { /* Additional fields if needed */ } - 在需要的地方注冊:
可以指定線程模式 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); } - 發(fā)送事件:
EventBus.getDefault().post(new MessageEvent());
更詳細(xì)的使用參見 How to get started with EventBus in 3 steps.
啟動(dòng)閃屏主題設(shè)置
默認(rèn)的啟動(dòng)閃屏是白色的,某些開發(fā)者會(huì)通過設(shè)置一個(gè)透明的啟動(dòng)閃屏主題來隱藏啟動(dòng)加載慢的問題,不過這種做法會(huì)影響用戶體驗(yàn)。我們可以通過設(shè)置一個(gè)帶logo的啟動(dòng)閃屏主題來讓用戶感受到在點(diǎn)擊桌面圖標(biāo)后馬上得到響應(yīng)。不過這里需要注意啟動(dòng)閃屏主題不能使用很大的圖片資源,因?yàn)榧虞d這些資源本身也是耗時(shí)的。
??設(shè)置啟動(dòng)閃屏可以在第一個(gè)展示的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>
效果如下:

其他可以優(yōu)化的細(xì)節(jié)
- 減少廣告等業(yè)務(wù)邏輯時(shí)間
??這里屬于業(yè)務(wù)邏輯的優(yōu)化,可根據(jù)不同的應(yīng)用發(fā)掘可以縮短的等待時(shí)間。 - 將
SharePreferences中的commit改為apply
??SharePreferences的操作涉及文件的讀寫,最好盡量使用apply方法代替commit方法。apply方法會(huì)先將結(jié)果保存在內(nèi)存的SharePreferences中并異步地更新SharePreferences文件 -
onPause不要執(zhí)行太多任務(wù)
??在展示另一個(gè)Acitivty之前,需要經(jīng)過上一個(gè)Acitvity的onPause()方法,因此在Activity的onPause()方法中不適合有耗時(shí)的工作。 -
ContentProvider不要做太多靜態(tài)初始化以及在onCreate()中做耗時(shí)操作。
??因?yàn)?code>ContentProvider的onCreate()會(huì)在ApplicationonCreate()之前調(diào)用。 - 減少View層級
??減少View的層級可以有效避免過度繪制,減少不必要的繪制過程。 - 注意內(nèi)存抖動(dòng)
??瞬間產(chǎn)生大量的對象會(huì)嚴(yán)重占用Young Generation的內(nèi)存區(qū)域,當(dāng)達(dá)到閥值,剩余空間不夠的時(shí)候,會(huì)觸發(fā)GC。即使每次分配的對象占用了很少的內(nèi)存,但是他們疊加在一起會(huì)增加Heap的壓力,從而觸發(fā)更多其他類型的GC。這個(gè)操作有可能會(huì)影響到幀率,并使得用戶感知到性能問題。 - 用更快的方式獲取信息,例如獲取Webview UA
??獲取Webview UA可以通過創(chuàng)建要給Webview然后獲取setting中的UserAgent,不過為了獲取UA而創(chuàng)建Webview是一個(gè)比較耗時(shí)的操作。我們可以在API17及以上的系統(tǒng)中通過WebSettings.getDefaultUserAgent(context)快速獲取。 - 盡量刪除沒必要的中間過渡Activity,減少Activity切換時(shí)間
??Activity的切換是比較耗時(shí)的,如果沒有必要,我們可以將達(dá)到主要頁面之前的Activity刪除,或者修改成Fragment動(dòng)態(tài)加入。
后記
通過之前的分析以及這篇文章介紹的啟動(dòng)優(yōu)化方法,我們詞典的啟動(dòng)速度得到了50%的提升,有效地提升了用戶體驗(yàn)。在以后的開發(fā)過程中,當(dāng)涉及到啟動(dòng)流程的代碼時(shí)需要格外謹(jǐn)慎,避免有耗時(shí)的操作加入。當(dāng)然目前的詞典啟動(dòng)速度還可以進(jìn)一步優(yōu)化,可以思考的方向一下幾點(diǎn):1. 進(jìn)一步優(yōu)化信息流布局,減少不必要的繪制;2. 深入探索第三方SDK帶來的啟動(dòng)速度延遲并嘗試優(yōu)化;3. 獲取更多實(shí)時(shí)廣告的成功率并嘗試去除實(shí)時(shí)廣告邏輯。
參考
【1】胡凱,2016.Android性能優(yōu)化典范 - 第5季
【2】胡凱,2016.Android性能優(yōu)化典范 - 第6季
【3】TellH的博客,2016.實(shí)現(xiàn)類似微信Viewpager-Fragment的惰性加載,lazy-loading