美文网首页
Activity详解

Activity详解

作者: code希必地 | 来源:发表于2020-09-22 17:19 被阅读0次

    1、Intent

    Intent是各个组件之间交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,而且还能在各组件之间传递数据。Intent一般可用于启动Activity、启动Service、发送广播等场景。
    Intent大致可分为2中:

    • 1、显示Intent
    • 2、隐式Intent

    1.1、显示Intent打开Activity

     fun openActivity(){
            val intent = Intent(this, KotlinFirstActivity::class.java)
            intent.putExtra("param", "testParams")
            startActivity(intent)
        }
    

    注意:KotlinFirstActivity::class.java就相当于Java中的KotlinFirstActivity.class

    1.2、隐式Intent打开程序内的Activity

    相比于显示Intent,隐式Intent并不指明启动那个Activity而是指定了一系列的action和category,然后交由系统去分析找到合适的Activity并打开。
    什么是合适的Activity,其实就是和隐式Intent中指定的action和category完全匹配的Activity,而action和category我们可以在AdnroidManifest中指定。

    <activity android:name=".SecondActivity">
                <intent-filter>
                    <action android:name="com.example.abu.alertdialogdemo.ACTION_START" />
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </activity>
    

    在标签<intent-filter>中配置了action和category,只有和隐式Intent中的action和category完全匹配才能正常的打开该页面。

      val intent = Intent("com.example.abu.alertdialogdemo.ACTION_START")
      startActivity(intent)
    

    不是说action和category要完全匹配才能打开页面吗?这是因为android.intent.category.DEFAULT是一种默认的category,在调用startActivity()时会自动将这个category添加到Intent中。所以在Manifest中一定不要忘记配置这个默认的category:android.intent.category.DEFAULT,否则会报错。
    还有一点需要注意:Intent中只能添加一个action,但是可以添加多个category。

    1.3、隐式Intent打开程序外的Activity

    比如我们要打开系统的浏览器

    fun openWeb(view: View) {
            val intent = Intent(Intent.ACTION_VIEW)
            intent.data = Uri.parse("https://www.baidu.com")
            startActivity(intent)
        }
    

    Intent.ACTION_VIEW是系统内置的动作,然后将https://www.baidu.com通过Uri.parse()转换成Uri对象,传递给intent.setData(Uri uri)函数。Kotlin中intent.data=Uri.parse("https://www.baidu.com")就相当于Java中的intent.setData(Uri.parse("https://www.baidu.com")),这是Kotlin中的语法糖。
    与此对应在标签<intent-filter>中配置一个<data>标签,用于更精确的指定当前Activity能够相应的数据。<data>标签中主要可以配置一下内容:

    • 1、android:scheme:用于指定数据的协议部分,如https
    • 2、android:host:用于指定数据的主机名部分,如www.baidu.com
    • 3、android:port:用于指定数据的端口,一般紧随主机名后
    • 4、android:path:用于指定数据的路径
    • 5、android:mimeType:用于指定支持的数据类型
      只有当<data>标签中指定的内容和Intent中携带的data完全一致时,当前Activity才能响应该Intent。下面我们通过设置data,让它也能响应打开网页的Intent。
          <activity android:name=".ThirdActivity">
                <intent-filter tools:ignore="AppLinkUrlError">
                    <action android:name="android.intent.action.VIEW" />
    
                    <category android:name="android.intent.category.DEFAULT" />
                    <data android:scheme="https"/>
                </intent-filter>
            </activity>
    

    我们在ThirdActivity的<intent-filter>中配置当前Activity能够响应的action是Intent.ACTION_VIEW的常量值,而category指定了默认的category值,另外在标签data中我们通过android:scheme="https"指定了数据的协议必须是https。另外由于AndroidStudio认为能够响应ACTION_VIEW的Activity都应该加上BROWSABLE的category,否则会报出警告。加上BROWSABLE的category是为了实现deep link 功能和目前学习无关,所以我们在intent-filter标签上添加tools:ignore="AppLinkUrlError"忽略警告。
    然后我们就能通过隐式Intent的方法打开ThirdActivity了,代码如下:

     val intent= Intent(Intent.ACTION_VIEW)
            intent.data=Uri.parse("https:")
            startActivity(intent)
    

    除了指定android:schemehttps我们也能随意指定它的值,只需要保证AndroidManifest中设置的值和Intent中设置的data相对应即可。

    2、Activity的生命周期

    Activity类中定义了7个回调方法,覆盖了Activity声明周期的每一个环节。

    • 1、onCreate(): 在Activity第一次创建时调用
    • 2、onStart():在Activity可见但是没有焦点时调用
    • 3、onResume():在Activity可见并且有焦点时调用
    • 4、onPause():这个方法会在准备启动或者恢复另一个Activity时调用,我们通常在该方法中释放消耗CPU的资源或者保存数据,但在该方法内不能做耗时操作,否则影响另一个另一个Activity的启动或恢复。
    • 5、onStop():在Activity不可见时调用,它和onPause主要区别就是:onPause在失去焦点时会调用但是依然可见,而onStop是完全不可见。
    • 6、onDestory():在Activity被销毁前调用
    • 7、onRestart():在Activity由不在栈顶到再次回到栈顶并且可见时调用。
      为了更好的理解Activity的生命周期,看下图


      Activity生命周期.png

    3、体验Activity的生命周期

    下面我们通过实例更直观的看下Activity的生命周期过程。

    • 1、打开FirstActivity,生命周期过程如下
    FirstActivity  onCreate
    FirstActivity  onStart
    FirstActivity  onResume
    
    • 2、在FirstActivity中打开SecondActivity,生命周期过程如下
    FirstActivity onPause
    SecondActivity onCreate
    SecondActivity onStart
    SecondActivity onResume
    FirstActivity onStop
    

    可以看到在打开SecondActivity时FirstActivity的onPause会执行,所以在onPause中是不能做耗时操作的,否则会影响SecondActivity的打开。

    • 3、按返回键回到FirstActivity,生命周期过程如下
    SecondActivity onPause
    FirstActivity onRestart
    FirstActivity onStart
    FirstActivity onResume
    SecondActivity onStop
    SecondActivity onDestroy
    

    可以看到在返回FirstActivity时会调用SecondActivity的onPause,如果SecondActivity的onPause中做了耗时操作的话,那么也会影响Activity的返回。而且当FirstActivity再次回到栈顶时会调用其onRestart,此时并不会执行onCreate因为FirstActivity并没有销毁。

    4、Activity被回收了时的生命周期

    现在描述一种场景:打开ActivityA,然后在ActivityA的页面中打开ActivityB,此时ActivityA不在栈顶了如果内存不足可能会被回收,此时从ActivityB再回到ActivityA,下面描述下整个过程的生命周期。

    • 1、打开ActivityA,执行的生命周期
    ActivityA  onCreate
    ActivityA  onStart
    ActivityA  onResume
    
    • 2、打开ActivityB执行的生命周期
    ActivityA  onPause
    ActivityB  onCreate
    ActivityB  onStart
    ActivityB  onResume
    ActivityA  onStop
    
    • 3、此时因内存不足,导致ActivityA被回收了,并返回ActivityA
    ActivityA  onDestory
    ActivityB  onPause
    ActivityA  onCreate
    ActivityA  onStart
    ActivityA  onResume
    ActivityB  onStop
    

    从上面可以看出在ActivityA被回收后再次回到ActivityA时不再调用ActivityA的onRestart了,而是调用了ActivityA的onCreate,因为ActivityA已经被销毁了。

    5、Activity被销毁,其中的临时数据怎么办

    ActivityA被销毁了,这也就说明其中的临时数据也会丢失了,比如ActivityA中有一个EditText,我们在其中输入了一段文字,然后启动ActivityB,此时由于内存紧张处于停止状态的ActivityA被回收了,返回到ActivityA你会发现EditText中输入的文字不见了,因为ActivityA重新创建了。
    为此Activity提供了onSaveInstanceState方法,该方法能确保在被回收之前被调用,我们就能在onSaveInstanceState方法中进行临时数据的保存,然后我们可以在onCreate(savedInstanceState: Bundle?)中利用savedInstanceState进行数据的恢复。Activity被回收重新创建时onCreate(savedInstanceState: Bundle?)中的savedInstanceState不再为null否则为null,其中带有在savedInstanceState中保存的所有数据。
    具体代码如下:

    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            Log.e("tag", "$tag onCreate")
            //恢复数据
            val tempData = savedInstanceState?.getString("data") ?: ""
            et.setText(tempData)
        }
    
        //保存数据
        override fun onSaveInstanceState(outState: Bundle?) {
            super.onSaveInstanceState(outState)
            Log.e("tag", "$tag onSaveInstanceState")
            val tempData = et.text.toString().trim()
            outState?.putString("data", tempData)
        }
    

    6、onSaveInstanceState()调用时机

    • 1、用户按下HOME键
    • 2、长按HOME键进入其他程序
    • 3、按下电源键关闭屏幕
    • 4、从ActivityA跳转到一个新的Activity
    • 5、屏幕方向切换时,如从竖屏变成横屏
      总而言之,onSaveInstanceState()是在你的Activity有可能在未经允许的情况下被回收时调用,Activity被销毁之前onSaveInstanceState()肯定会被触发,我们可以onSaveInstanceState()中保存临时数据,持久化的数据可以在onPause()中保存。
      另外,虽然屏幕方向切换时,会造成Activity的重建会调用onSaveInstanceState(),但是不建议使用onSaveInstanceState()进行数据的保存,我们可以禁止屏幕的旋转或禁止屏幕旋转时Activity的重建。
      禁止屏幕旋转
      可以在Mainifest中设置屏幕固定为竖屏
    android:screenOrieritation="portrait"
    

    禁止屏幕旋转时Activity的重建

    android:configChanges="orientation丨keyboardHidden丨screenSize"
    

    7、Activity的启动模式

    启动模式分为4种:

    • 1、standard
    • 2、singleTop
    • 3、singleTask
    • 4、singleInstance
      我们可以在Manifest中通过android:launchMode指定启动模式。

    7.1、standard模式

    如果不显示指定启动模式,那么Activity的启动模式就是standard,在该模式下不管Activity栈中有无该Activity,均会创建一个新的Activity并入栈,并处于栈顶的位置。

    7.2、singleTop模式

    • 1、要启动的Activity位于栈顶
      在启动一个ActivityA时,如果栈顶的Activity就是ActivityA,那么就不会重新创建ActivityA,而是直接使用,此时并不会调用ActivityA的onCreate(),因为并没有重新创建Activity,ActivityA的生命周期如下:
    ActivityA onPause
    ActivityA onNewIntent
    ActivityA onResume
    

    可以看到调用了onNewIntent(intent: Intent?),我们可以在onNewIntent(intent: Intent?)中通过intent来获取新传递过来的数据,因为此时数据可能已经发生了变化。

    • 2、要启动的Activity不在栈顶
      虽然我们指定了ActivityA的启动模式为singleTop,但是如果ActivityA在栈中但是不在栈顶的话,那么此时启动ActivityA的话会重新创建一个新的ActivityA并入栈,此时栈中就有2个ActivityA的实例了。

    7.3、singleTask模式

    如果准备启动的ActivityA的启动模式为singleTask的话,那么会先从栈中查找是否存在ActivityA的实例:

    • 场景一、如果存在则将ActivityA之上的Activity都出栈,并调用ActivityA的onNewIntent()
    • 场景二、如果ActivityA位于栈顶,则直接使用并调用onNewInent(),此时和singleTop一样。
    • 场景三、 如果栈中不存在ActivityA的实例则会创建一个新的Activity并入栈。
      场景一:ActivityA启动ActivityB,然后启动ActivityA,此时生命周期过程:
    ActivityB onPause
    ActivityA onRestart
    ActivityA onStart
    ActivityA onNewIntent
    ActivityA onResume
    ActivityB onStop
    ActivityB onDestroy
    

    此时ActivityA不在栈顶,ActivityA之上有ActivityB,所以在启动ActivityA时ActivityA之上的ActivityB会出栈,ActivityA将置于栈顶,所以ActivityA的onRestart和ActivityB的onDestory会执行。
    场景二:ActivityA启动ActivityA,此时生命周期过程:

    ActivityA onPause
    ActivityA onNewIntent
    ActivityA onResume
    

    此时ActivityA位于栈顶,此时singleTask和singleTop作用一样,都是直接使用ActivityA,并且会调用ActivityA的onPause、onNewIntent、onResume
    场景三:ActivityA启动ActivityB,此时生命周期过程:

    ActivityA onCreate
    ActivityA onStart
    ActivityA onResume
    

    7.4、singleInstance模式

    不同于另外3个模式,指定singleInstance模式的Activity会启动一个新的返回栈来管理这个Activity(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈),这么做的意义是什么?
    想象一个场景:如果我们程序内的一个Activity是允许其他程序访问的,如果想实现其他程序和我们程序能共享这个Activity实例,该怎么实现呢?使用前面3中模式是无法做到的,因为每个应用程序都有自己的返回栈,同一个Activity在不同返回栈中肯定都创建了新的实例,而使用singleInstance就可以解决这个问题。在这种模式下,会有一个单独的返回栈来管理这个Activity,无论哪个应用程序来访问这个Activity,都在同一个返回栈中,也就解决了共享Activity实例的问题。
    为了更好的理解下面我们实战一下:

    • 1、将ActivityB的启动模式修改为singleInstance
     <activity
                android:name=".ActivityB"
                android:launchMode="singleInstance" />
    
    • 2、在ActivityA、ActivityB、ActivityC的onCreate中打印taskId
     ##ActivityA
     val tag = javaClass.simpleName
    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            Log.e("TAG", "$tag taskId=$taskId")
        }
        
        ##ActivityB
         val tag = javaClass.simpleName
    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(activity_second)
            Log.e("TAG", "$tag taskId=$taskId")
        }
        
        ##ActivityC
         val tag = javaClass.simpleName
    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(activity_c)
            Log.e("TAG", "$tag taskId=$taskId")
        }
    
    • 在Kotlin中javaClass是当前实例的Class对象,相当于Java的getClass().
    • 在Kotlin中ActivityB::class.java是获取ActivityB类的Class对象,相当于Java中的ActivityB.class
    • 3、启动ActivityA->启动ActivityB->启动ActivityC,taskId打印结果如下:
    ActivityA taskId=4163
    ActivityB taskId=4164
    ActivityC taskId=4163
    

    可以看到ActivityB的taskId是不同于ActivityA 和ActivityC的,这也说明了ActivityB 是在一个单独的栈中的,并且返回栈中只有这一个Activity。

    • 4、在ActivityC中按返回键,返回到ActivityA,再按返回键返回到ActivityB,这是为什么呢?其实很好理解:ActivityA 和ActivityC 在同一个返回栈中,在ActivityC 中按返回键ActivityC 出栈,此时ActivityA就位于栈顶了,ActivityA就展示在界面上了,在ActivityA中再按返回键,这是当前的返回栈已经空了,于是就展示了另一个返回栈栈顶的ActivityB了。

    相关文章

      网友评论

          本文标题:Activity详解

          本文链接:https://www.haomeiwen.com/subject/bpovyktx.html