安卓Activity(二)

164 阅读5分钟

一.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。

image.png

可以看到,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开发艺术探索》任玉刚