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:scheme
为https
我们也能随意指定它的值,只需要保证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了。
网友评论