在上一篇文章《怎么计算Android App发动时刻》中咱们讨论了怎么计算Android App的发动时刻,以及扼要剖析了App发动流程。这一篇文章首要讲怎么在实战中提高Android App的发动速度。下面咱们先回忆一下App的发动流程。转载请注明出处:Lawrence_Shen 一起能够参阅2019年的功能剖析文章:Android功能剖析&发动优化 ##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
,并将的onCreateView
,onViewCreated
办法修正为onLazyCreateView
,onLazyViewCreated
。修正如下图所示。
创立时直接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. 生命周期没有改变 |
办理以及创立代码需求修正 |
作用如下:
###运用后台线程 在发动的进程中,尽量把能在后台做的使命都放到后台,能够运用以下几个办法来履行后台使命:
- AsyncTask: 为UI线程与作业线程之间进行快速的切换供给一种简单便捷的机制。适用于当下当即需求发动,可是异步履行的生命周期短暂的运用场景。
- HandlerThread: 为某些回调办法或许等候某些使命的履行设置一个专属的线程,并供给线程使命的调度机制。
- ThreadPool: 把使命分解成不同的单元,分发到各个不同的线程上,进行一起并发处理。
- IntentService: 适合于履行由UI触发的后台Service使命,并能够把后台使命履行的状况经过必定的机制反馈给UI。
###运用EventBus 适当地运用EventBus能够拖延一些初始化。在需求的当地post一个事情,EventBus会告诉注册过这些事情的当地,这样能够把一些初始化在实在需求的时分再post一个触发事情,然后拖延初始化。
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); }
- 发送事情:
EventBus.getDefault().post(new MessageEvent());
更详细的运用拜见 How to get started with EventBus in 3 steps.
###发动闪屏主题设置 默认的发动闪屏是白色的,某些开发者会经过设置一个透明的发动闪屏主题来隐藏发动加载慢的问题,不过这种做法会影响用户体会。咱们能够经过设置一个带logo的发动闪屏主题来让用户感受到在点击桌面图标后立刻得到响应。不过这儿需求留意发动闪屏主题不能运用很大的图片资源,因为加载这些资源自身也是耗时的。 设置发动闪屏能够在榜首个展现的Acitivty设置主题:
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>
作用如下:
###其他能够优化的细节
- 削减广告等事务逻辑时刻 这儿属于事务逻辑的优化,可根据不同的应用开掘能够缩短的等候时刻。
- 将
SharePreferences
中的commit改为applySharePreferences
的操作涉及文件的读写,最好尽量运用apply办法替代commit办法。apply办法会先将成果保存在内存的SharePreferences
中并异步地更新SharePreferences文件 -
onPause
不要履行太多使命 在展现另一个Acitivty之前,需求经过上一个Acitvity的onPause()
办法,因此在Activity的onPause()
办法中不适合有耗时的作业。 -
ContentProvider
不要做太多静态初始化以及在onCreate()
中做耗时操作。 因为ContentProvider
的onCreate()
会在ApplicationonCreate()
之前调用。 - 削减View层级 削减View的层级能够有用防止过度制作,削减不必要的制作进程。
- 留意内存抖动 瞬间发生大量的目标会严峻占用Young Generation的内存区域,当到达阀值,剩下空间不够的时分,会触发GC。即使每次分配的目标占用了很少的内存,可是他们叠加在一起会添加Heap的压力,从而触发更多其他类型的GC。这个操作有可能会影响到帧率,并使得用户感知到功能问题。
- 用更快的办法获取信息,例如获取Webview UA
获取Webview UA能够经过创立要给Webview然后获取setting中的UserAgent,不过为了获取UA而创立Webview是一个比较耗时的操作。咱们能够在API17及以上的系统中经过
WebSettings.getDefaultUserAgent(context)
快速获取。 - 尽量删除没必要的中心过渡Activity,削减Activity切换时刻 Activity的切换是比较耗时的,如果没有必要,咱们能够将到达首要页面之前的Activity删除,或许修正成Fragment动态参加。
##跋文 经过之前的剖析以及这篇文章介绍的发动优化办法,咱们词典的发动速度得到了50%的提高,有用地提高了用户体会。在今后的开发进程中,当涉及到发动流程的代码时需求格外慎重,防止有耗时的操作参加。当然现在的词典发动速度还能够进一步优化,能够思考的方向一下几点:1. 进一步优化信息流布局,削减不必要的制作;2. 深入探索第三方SDK带来的发动速度延迟并测验优化;3. 获取更多实时广告的成功率并测验去除实时广告逻辑。
##参阅 【1】胡凯,2016.Android功能优化典范 – 第5季 【2】胡凯,2016.Android功能优化典范 – 第6季 【3】TellH的博客,2016.完成相似微信Viewpager-Fragment的惰性加载,lazy-loading