目录
Android生命周期
说到生命周期,学过Android的人想必能闭着眼睛,一字不差的说出来,这是当然背和睁眼闭眼能有什么关系呢?对于生命周期,我有自己的理解,解释的同时力争简洁明了方便理解。
常规的生命周期流程:onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory() (再加上一个onRestart()),接下来我们来解释一下各位大爷的含义。
方法 | 含义 | 注意点 |
---|---|---|
onCreate() | 表示调用setContentView方法,去加载布局 | savedInstanceState的作用 |
onStart() | 可见,但是未获取焦点,不可操作。 | - |
onResume() | 可见,获取焦点,可操作。 | - |
onPause() | 可见,失去焦点不可操作。 | 可以进行轻量级的数据存储,动画的停止,但不可太耗时,因为会影响到下一个Activity的显示 |
onStop() | 不可见,失去焦点。 | 可以进行轻量级的资源释放 |
onDestory() | Activity被销毁,资源释放。 | - |
onRestart() | 重新加载,等同有onCreate()的作用。 | 在onStop之后被调用 |
细节纠错
针对上面加粗的可见和焦点,我有必要强调一下。
1.可见的Activity不一定可以操作。
2.可操作的Activity一定可见。
我们来看一张腾讯体育的效果图
我们将这张图拆分成两个部分,一个是底部的页面,一个提示升级的弹框。此时底部的页面我们可见,但是不能操作,为什么呢?因为焦点被升级弹框所占据。那么onStart所表示的就是这个意思,可见但没焦点->没焦点就等于不可操作。所以可操作的一定有焦点,有焦点那么一定可见。
每日一练
接下来,我们来举个例子,在结合输出日志方便我们去理解各个生命周期出现的时机。
请问:点击App,跳转到A页面(A是首页),在从A页面跳转到B页面,然后按home键回到桌面,再点击App回到,再按返回键。
针对这一问题,我们需要分解开来,将这个问题分解成五个部分
1.点击App跳转到A页面:onCreate(A)->onStart(A)->onResume(A)
2.从A页面跳转到B页面:onPause(A)->onCreate(B)->onStart(B)->onResume(B)->onStop(A)。这也就是为什么如果onPause进行耗时操作,会影响下一个界面的显示。
3.按home键回到桌面:onPause(B)->onStop(B)
4.再点击App:onRestart(B)->onStart(B)->onResume(B)
5.按返回键:onPause(B)->onRestart(A)->onStart(A)->onResume(A)->onStop(B)->onDestory(B)
了解这些大家就具备对生命周期该有的熟练度。
异常生命周期
异常生命周期主要分两种,一种是在默认的模式下,进行了横竖屏切换,那么Activity会重新加载,生命周期如下onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory()->横竖屏切换->onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory();另一种就是因为资源内存不足,低优先级的Activity会被杀死。
横竖屏切换
比如一个页面里有视频播放,视频具备全屏功能,这时候就需要横竖屏切换,若不作处理,每次横竖屏切换都会重走生命周期,那么性能、交互性等都不会是特别的理解,为了解决这一问题需要用到configChanges这一属性。
<activity
android:name=".XxxxxActivity"
android:configChanges="orientation|screenSize"
android:screenOrientation="landscape" />
这里用到configChanges,它的作用是捕获手机状态改变,捕获什么状态改变呢?捕获orientation和screenSize。
- orientation:表示屏幕旋转
- screenSize:表示屏幕尺寸发送改变
放设置了configChanges,在屏幕发生旋转时,我们可以通过onConfigurationChanged方法,去监听屏幕的旋转。
另外提一下screenOrientation,这个属性表示设置当前Activity默认的屏幕方向,所以XxxxxActivity当前默认的方向是横屏的
- landscape:表示横屏
- portrait:表示竖屏
资源不足
资源不足很好理解,一部手机的内存是有限的,但是一部手机可以运行很多App,若不对各个App的运行所需的内存做处理,那么必定会导致手机卡顿,所以手机会给每个App分配最大的运行内存。当App运行所需的内存要大于手机系统所分配的内存时怎么办呢?App会根据优先级去杀死对应的servce、activity。
前台交互的Activity>可见Activity>后台Activity
- 前台交互就是有焦点,可见
- 可见就是没焦点,可见
- 后天就是在堆栈中的Activity
解决方式
在此,主要讲解两个方法。onSaveInstanceState,onRestoreInstanceState和onCreate。
- onSaveInstanceState(Bundle outState)
作用:可以通过outState去保存系统崩溃前的数据,以便在onRestoreInstanceState和onCreate中去获取,并加载。 - onRestoreInstanceState(Bundle savedInstanceState)
可以通过savedInstanceState去获取崩溃前保存下来的数据,并去显示。 - onCreate(Bundle savedInstanceState)
可以通过savedInstanceState去获取崩溃前保存下来的数据,并去显示。
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("msg", "呀系统奔溃了 我赶紧保存数据");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
System.out.println(savedInstanceState.getString("msg"));
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
System.out.println(savedInstanceState.getString("msg"));
}
为了方便复现,有1个前提条件就是1.该activity不能设置configChanges 还有一个注意点就是onCreate中savedInstanceState的空判断,因为正常证明周期savedInstanceState是空的。
虽然onSaveInstanceState的使用只有这些但是作为开发者要了解的不仅仅是这些。
- 系统默认会帮我实现该方法,并且系统提供的view都onSaveInstanceState,所以当前activity生命周期异常时,view自己会保存当前状态。
- onSaveInstanceState在onStop之前调用,与onPause无先后关系。
- onRestoreInstanceState在onStart方法之前调用,与onCreate无先后关系。
启动模式
启动模式主要分为4种,standard、singTop、singleTask、singleInstance
- standard:就是标准模式,每次启动新的Activity,都会添加到Activity栈。
如A跳B跳C跳D再跳B,那么栈结构如下图
- singleTop:表示栈顶复用。
如A跳B跳C跳A再跳A(A的模式是singleTop,BC的模式是standard),那么栈结构如下图
我们可以看到当A再跳转到A的时候,栈没有添加A到栈中,也就是说当前索要跳转的页面与栈顶一致时,则不会再次添加到栈中,拿之前的A进行复用,故名为栈顶复用。
那么此时的A的生命周期走了哪些呢?
A不会在回调onCreate,onStart,onRestart,取而代之的是onNewIntent
- singleTask:表示栈内复用。
还是一样举个例子便于理解。如如A跳B跳C跳B再跳B(A C是standard模式,B是singleTask模式),那么栈结构如下图
可以看到我们先启动A-B-C页面,然后再跳转B,因为栈内已经有B了,则清除B以上所有的页面,拿栈内的B进行复用,顾名思义为栈内复用。
它的生命周期也是一样,不走onCreate,onStart,onRestart,取而代之的是onNewIntent
-
singleInstance:单例模式。该模式的activity会被添加一个新的栈中去,怎么理解?咱们上例子吧。
(1)A跳B(A是standard模式,B是singleInstance模式),然后再跳A
注意:这里需要注意的是,每一个设置了singleInstance的Activity都会被添加一个新的栈中去,之后要跳转的Activity不会被添加到当前设置singleInstance的栈中去,也就是添加到standard、singleTop和singleTask的栈中去。
问:此时退出App需要点击几次back键?
一共是三次!为什么呢?我们来分析一下,因为此时显示的页面是在栈1的栈顶的A,所以 先按一下 back键,回到了栈2的B,此时栈1还剩一个A,栈B还剩一个B,接着再 按一下 back键,此时从栈2的B回到栈1的A,此时只剩栈1的A,最后再 点一下 back键,退出栈1的最有一个A,App退出成功。
(2)A跳B(A是singleTop/singleTask模式,B是singleInstance模式),然后在跳A
问:此时退出App需要点击几次back键?
一共是两次!当前所在的Activity是栈1的A中,因为A会被复用,所以当跳转到新的A时,不会添加新的A到栈中,第一次按back键,会从栈1回到栈2,接着 再按一次 back键,当前没有栈了所以退出App。
启动优化
为什么我们要做启动优化?为什么App第一次开启的时候,有的手机白屏?有的手机黑屏?为什么有的App白屏时间久?为了解决这些问题,我们有必要进行启动优化。我们先来了解如何测量启动时间,在结合优化方案后去比较优化了多少时间。
测量启动时间
如何测量启动时间呢?我这里给出两种方法
DisplayTime
DisplayTime起始就可以通过Log控制台查找,API19之后,可以在Logcat中过滤ActivityManager
我们可以看到HelloPageActivity是我引导页,一共要用514ms,首页MainNoDrawerActivity医用过了169ms。大家可以来看看自己的APP启动的时间,这方法非常简单。
adb指令
指令如下 adb shell am start -W 包名/包名.xxxActivity
通过adb,去测量我的HelloPageActivity的启动时间,指令如下
adb shell am start -W com.xxx.aaa/com.xxx.aaa.activity,HelloPageActivity
这里一共有三个time,ThisTime,TotalTime,WaitTime。那么三个time代表什么意思呢?
- ThisTime
表示最有一个Activity的启动时间 - TotalTime
表示所有的Activity的启动时间 - WaitTime
表示ActivityManager启动App的启动时间。
问:ThisTime和TotalTime有什么关系?为什么时间是一样的?
就拿当前例子来说,当前测量的是一个启动页也就是App的第一个Activity,所以测量单Activity的时候,ThisTime=TotalTime。那么相对的,如果启动的是多个Activity,ThisTime>TotalTime
问:WaitTime为什么比ThisTime和TotalTime时间长?
就拿这个APP而言,需要启动的Activity不只是HelloPageActivity,还有APP的图标,其实App图标也是一个Activity,所以WaitTime一定大于ThisTime和TotalTime。其实ThisTime和TotalTime的时间,是从App图标的onPause之后开始计时。
所以可以总结单页面启动的时候,ThisTime=TotalTime<WaitTime。多Activity的时候,ThisTime<TotalTime<WaitTime。
冷热启动
启动分为两种,一种是冷启动,一种是热启动。
- 冷启动:后台进程中没有该App进程,故所有的资源都要从头开始去初始化,所以时间是最久的。要从Application开始初始化->MainActivity初始化
- 热启动:后台进程中已有该进程(如App中按back、home键等),故资源不需要全部重新加载所以时间较短。不需要初始化Application,只需要初始化MainActivity。
优化方案
白屏/黑屏问题
在启动App的时候,会出现白屏或者是黑屏的等待时间,那么为什么会出现白屏或者黑屏呢?
主要原因: 是因为在Activity渲染之前,会有一个默认的Window也就是StartingWindow,等应用加载好后会无缝衔接过去。虽然是无缝衔接,但是当加载更多第三方资源等情况的时候,应用渲染时间更久,会导致白屏时间长,用户体验不好,所以我们需要解决该问题。
方案: 针对这个问题,我们可以修改StartingWindow的样式,让该window的背景和应用的引导页一模一样,这样就可以达到视觉的上启动优化。
<style name="SplashTheme" parent="@style/Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowBackground">@mipmap/skip</item>
</style>
直接给我们第一个显示的Activity设置该主题,通过设备windowBackground来改变StartingWindow的样式。
不过该问题不能优化启动时间,只不过给我们造成了一个视觉上启动很快的假象。
启动时间优化
一个APP的启动的时候,不就是做了些资源的初始化?系统资源,非系统资源。系统资源我们无法控制,那么就从非系统资源里下手。
Application优化
冷启动的时间比热启动要就,因为冷启动时,后台进程里没有该APP的进程,所以要加载全部资源,最先从Application开始加载。那么在开发过程中,我们通常需要在Application里做什么?加载第三方资源!加载第三方资源!加载第三方资源! 没错大部分的第三方资源我们都会选择将其放在Application中加载,必然增加了我们启动的时间,所以我们可以从第三方资源加载下手。
- 在Application中,通过开启一个服务(IntentService/JobIntentService)去加载第三方。
- 耗时操作通过延时加载。
为什么用IntentService/JobIntentService?因为该service中利用的到了Handler,在任务执行完后,会自动销毁。不需要stopService/unBind。后面会针对Service详讲
MainActivity优化
MainActivity是我们的主页面,通常需要进行大量的View绘制,或者是数据加载,所以我们从View和数据方面入手。
布局优化
- FrameLayout
帧布局,有时候可以来替代RelativeLayout,因为FrameLayout更加轻量级,性能更好。故在布局中优先级大于RleativeLayout
- RelativeLayout和LinearLayout
大家常用的布局,然后我们也需要分情况有限选择。因为在源码中,RelativeLayout的onMeasure方法中,会对子View进行两次的绘制,即水平绘制一次,竖直绘制一次。LinearLayout在子View使用Weight的属性的时候,才会绘制两次,即水平绘制一次,竖直绘制一次。若子View没使用Weight属性,则绘制一变。
所以在需要多层嵌套时,选用RelativeLayout。不怎么嵌套则使用LinearLayout。
- 避免overDraw
可以通过手机的开发者选项,去开启GPU过去绘制,去查看当前应用是否过度绘制。
蓝色表示一次;绿色表示两次;淡红色表示三次;暗红色表示四次;若是暗红色则可以考虑优化布局。
- ConstraintLayout
该布局为约束布局,Android Studio2.0之后出的,主要是来处理多层嵌套,负责页面,相对与RelativeLayout来说性能更优,至少快40%
- include
主要是复用率高的布局,可使用include标签。使用如下
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
app_bar_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</RelativeLayout>
就是include去引用一个app_bar_main.xml布局,来达到复用的效果。
- merge
则需要搭配include来使用,主要针对被引用的app_bar_main.xml,使用如下。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true“>
<!-- 通过include去引用app_bar_main布局-->
<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
app_bar_main.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
</android.support.design.widget.CoordinatorLayout>
</merge>
此时在编译时,就会用include的父View去替换merge标签。merge标签就会被LinearLayout去替换
- ViewStub
ViewStub是一个惰性装载控件。ViewStub是一个无形的、零大小的视图。通俗的话来说就是,只有在显示的时候,还回去加载视图去消耗系统资源,不显示的时候消耗系统资源很小,比setVisibility(GONE)还小。可以setVisibility()或inflate()方法去显示ViewStub。
所以我们可以利用ViewStub去做延时加载布局,从而减少MainActivity加载布局的时间。
数据优化
- SharePreference
这里主要讲常用的SharePreference,系统自带的轻量级存储,相比大家一定都用过,但是在初始化SharePreference需要注意的是,SharePreference会一次性将所有的存储的xml读取到内存中,这就会影响到启动的时间,所以我需要在子线程中去初始化SharePreference。后面会对SharePreference进行源码分析。
- 数据缓存
1.对于数据缓存就有很多要做的了,比如常用的数据我们可以通过SharePreference进行缓存,减少启动APP的请求数据的次数。2.一些首页加载的图片我们可以进行缓存。在启动APP时,可以先显示缓存中的图片,等视图加载好后再去请求网络数据。3.Message通过obtainMessage去复用。4.Listview利用convertView复用(虽然现在都用recycleview)。5.用StringBuffer/StringBuilder去替换string的字符串拼接。
下集预告
Activity那些事儿就告辞一段楼,下一篇会写Fragment那些事儿。主要内容有
- Fragment生命周期
- 懒加载
- Activity与Fragment交互
- Fragment出栈问题
- Fragment重叠问题