一.Activity的启动模式
稍微熟悉安卓的读者都知道,Activity主要有两种启动方式,显式启动和隐式启动,下面就逐一介绍。
二.显示启动
显示启动比较简单,只需指定目的Activity的class文件即可。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent=Intent(this,SecondActivity::class.java)
startActivity(intent)
}
}
上述代码就是MainActivity启动SecondActivity的实例代码,这里需要注意的是,SecondActivity::class.java在Kotlin中等效于SecondActivity.class。
三.隐式启动
隐式启动主要是通过设置intent-filter标签来实现目标Activity匹配,说起intent-filter大家可能不熟悉,但对如下AndroidManifest文件中MainActivity的配置代码大概是不陌生的。
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
可以看到MainActivity中intent-filter里有两个标签,action和category,这两个标志比较特殊,它们共同确定当应用启动时,系统应该启动哪个Activity,缺一不可,当系统启动某个应用时找不到配置了上述标签的Activity时,也就是说找不到MainActivity时,就会报错。
其实,上述intent-filter还少了一个data标签,intent-filter标签有action,category和data三个嵌套标签。
需要注意的是,一个Activity可以有多个intent-filter标签,只需要成功匹配一个intent-filter就能成功启动。
下述代码是隐式启动Activity的示例代码。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent=Intent("android.intent.action.MAIN")
intent.addCategory("android.intent.category.LAUNCHER")
startActivity(intent)
}
}
3.1 action标签
action标签的匹配规则很简单,尽管一个intent-filter可以有多个action标签,但只需匹配一个action标签即可。
需要注意的是,当intent-filter有定义action标签,Intent实例必须指定action,否则将会匹配失败。
3.2 category标签
category标签的匹配规则与action标签类似,一个intent-filter可以有多个category标签,但只需匹配一个
category标签即可。
不过,与action标签不同的是,当Intent实例没有指定category标签时,系统会自动帮该Intent实例添加
"android.intent.category.DEFAULT"标签。
3.3 data标签
data标签比较特殊,由URI和mimeType组成,URI是资源定位符,mimeType是类型。
URI的结构如下所示。
<scheme>://<host>:<port>/[<path>|<pathPrefix>|<pathPattern>]
这里挑两个必不可少的标签和来讲。
scheme标签的内容是模式,可以选的有content,file和http,Intent实例添加URI时,必须要有scheme,否则该URI无效。
host标签是主机,Intent实例添加URI时,必须要有host,否则该URI无效。
上述内容可能不大好懂,没关系,来个例子就会清晰多了。
http://www.baidu.com
上述是百度搜索的网址,想必大家再熟悉不过,那么http就是scheme,www.baidu.com 就是host。
这里需要注意的是,如果intent-filter标签没有设置data标签,那么该Activity的scheme标签默认为content或者file。
此外,data标签分开写,或者合并写的效果是一样的。
<activity
android:name=".SecondActivity"
android:launchMode="singleTask"
android:exported="false" >
<intent-filter>
<data android:scheme="http"
android:host="www.baidu.com"
>
</data>
</intent-filter>
<intent-filter>
<data android:scheme="http" />
<data android:host="www.baidu.com" />
</intent-filter>
</activity>
上面两个intent-filter是等效的。
这里额外要提一下Intent实例的data标签设置方式,要使用setDataAndType()进行设置,不能分开用setType()和setData()方法进行设置,源码如下所示。
public @NonNull Intent setType(@Nullable String type) {
mData = null;
mType = type;
return this;
}
public @NonNull Intent setData(@Nullable Uri data) {
mData = data;
mType = null;
return this;
}
可以看到这两个方法都会将另一个数据设为空,而setDataAndType()并不会出现这个问题。
public @NonNull Intent setDataAndType(@Nullable Uri data, @Nullable String type) {
mData = data;
mType = type;
return this;
}
需要注意的是,当Activity配置intent-filter标签时没有指定uri标签时,scheme标签默认为content或者file。
四.示例
隐式启动的基本知识点已经讲完,再来一个小demo,新建一个SecondActivity。
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
Log.d("SecondActivity","SecondActivity is created")
}
}
修改AndroidManifest文件,如下所示。
<activity
android:name=".SecondActivity"
android:launchMode="singleTask"
android:exported="false" >
<intent-filter>
<action android:name="android.demo.demo"/>
<category android:name="android.demo.demo"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*"/>
</intent-filter>
</activity>
通过MainActivity来启动SecondActivity。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent=Intent("android.demo.demo")
intent.addCategory("android.demo.demo")
intent.setDataAndType(Uri.parse("content://abc"),"image/png");
startActivity(intent)
}
}
安装应用并运行,检查LogCat。
可以看到,SecondActivity成功启动了。
需要注意的是,当系统找不到匹配的Activity时,应用会发生崩溃,报如下所示的的错。
这个demo为了演示隐式启动,可以说是为MainActivity量身定制了一个SecondActivity,因此不用检查是否有匹配的Activity,但在大型项目中,为了程序的健壮性,下述代码才是更推荐的做法。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent=Intent("android.demo.demo")
intent.addCategory("android.demo.demo")
intent.setDataAndType(Uri.parse("content://abc"),"image/png")
val queryIntentActivities = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
for (activity in queryIntentActivities){
Log.d("MainActivity","${activity.activityInfo}")
}
if (queryIntentActivities.isNotEmpty()){
startActivity(intent)
}
}
}
安装并启动应用,检查LogCat。
可以看到,LogCat将SecondActivity的全限定名打印了出来。
queryIntentActivities()方法是将匹配Intent实例的Activity都列了出来,需要注意的是该方法第二个参数flag,MATCH_DEFAULT_ONLY标志的作用是只寻找category标签包含
<category android:name="android.intent.category.DEFAULT" />
的Activity,将其这么设置的原因是只有包含该category标签的Activity才能隐式启动。
类似queryIntentActivities()的方法还有resolveActivity(),用法类似,这里就不再赘述。
五.总结
Activity的内容看似繁琐,其实无非就几个点,生命周期,启动模式,启动方式,然后再根据每个点往下展开就会变得有条理多了。
比如说将Activity的生命周期这一知识点展开讲,又分为正常生命周期和异常生命周期。
正常生命周期总共有七个方法,可以延伸出下列几个问题,这么多方法哪两个是各自一对的,判定标准是什么,哪些方法应该执行什么操作等等。
异常生命周期可以延伸出下面几个问题,系统是如何保存和恢复Activity的状态的,系统一定会保存Activity状态吗等等。
这样不就手到擒来?
参考资料
1.《Android开发艺术探索》任玉刚