Activity那些事儿

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的使用只有这些但是作为开发者要了解的不仅仅是这些。

  1. 系统默认会帮我实现该方法,并且系统提供的view都onSaveInstanceState,所以当前activity生命周期异常时,view自己会保存当前状态。
  2. onSaveInstanceState在onStop之前调用,与onPause无先后关系。
  3. 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重叠问题
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值