通常我们在开发中会有这样的需要:让用户跳转到特定的activity页面。一个典型的案例是通知栏点击启动应用程序并跳转到指定activity页面。Android Oreo--Notifications这篇文章介绍了如何在android oreo中新建通知栏,这一切貌似都没有什么难度,但是如果你尝试在跳转到的落地页activity中点击导航栏的back键,就可能会有点迷糊或者困惑。
下面代码是MainActivity,布局中有一个按钮点击它进入SecondActivity:
class MainActivity : AppCompatActivity() {
private val serviceScheduler: ServiceScheduler by lazyFast {
ServiceScheduler(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
serviceScheduler.takeIf { it.isEnabled }?.apply {
startService()
}
button?.setOnClickListener {
startActivity(Intent(this, SecondActivity::class.java))
}
}
}
在AndroidManifes.xml代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.stylingandroid.oreo.notifications">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:ignore="GoogleAppIndexingWarning">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:parentActivityName=".MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
</activity>
</application>
</manifest>
添加parentActivityName和meta-data是配置了导航栏的层次结构。在这种情况下,点击导航栏的back按钮将返回到MainActivity。这样做是一个很好的习惯,而且以后还会很有用。
SecondActivity代码:
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
supportActionBar?.apply {
setHomeButtonEnabled(true)
setDisplayHomeAsUpEnabled(true)
}
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
item?.takeIf { it.itemId == android.R.id.home }?.run {
onBackPressed()
}
return super.onOptionsItemSelected(item)
}
}
创建通知栏的代码:
private fun buildNotification(message: Message, channelId: String): Notification =
with(NotificationCompat.Builder(context, channelId)) {
message.apply {
setContentTitle(sender)
setContentText(text)
setWhen(timestamp.toEpochMilli())
}
setSmallIcon(getIconId(channelId))
setShowWhen(true)
setGroup(GROUP_KEY)
setContentIntent(getContentIntentOld())
build()
}
private fun getIconId(channelId: String) =
when (channelId) {
IMPORTANT_CHANNEL_ID -> R.drawable.ic_important
LOW_CHANNEL_ID -> R.drawable.ic_low
else -> R.drawable.ic_message
}
private fun getContentIntentOld(): PendingIntent = Intent(context, SecondActivity::class.java).run {
PendingIntent.getActivity(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT)
}
应用会有一下情况:
- 应用未启动:点击通知栏会启动SecondActivity,重复点击通知栏也只会有这一个SecondActivity,点击导航栏的back按钮会直接返回到桌面而不是MainActivity
- 应用启动了:点击通知栏会启动SecondActivity,重复点击通知栏也会重复创建SecondActivity,点击导航栏的back按钮会返回上一个SecondActivity,直到返回MainActivity
对于第二个情况,你有没有感到迷惑呢?你可能会呵呵一下,认为很简单,只需要在方法getContentIntentOld()里面加上一行代码this.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
就能不再重复创建SecondActivity。事实上是错的,加上那一行代码不起任何作用。设置singleTask的模式才能避免那个问题,即在方法getContentIntentOld()里面加上这行代码:this.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
这样的代码点击导航栏的back按钮总是返回上一个的activity页面,而不能返回某个指定的activity页面。有人可能会想出这样的解决方案:PendingIntent跳转到MainActivity并设置MainActivity为singleTask,在MainActivity的onNewIntent中再指定跳转页面。这样的确可行,但是我们来研究下TaskStackBuilder。
TaskStackBuilder在API 16中被引入,在v4 core utils support library包中也能使用它,点击导航栏的back按钮可以返回到指定的activity页面。
使用下面的方法代替老的getContentIntentOld()方法:
private fun getContentIntent(): PendingIntent =
TaskStackBuilder.create(context).run {
addParentStack(SecondActivity::class.java)
addNextIntent(createIntent(SecondActivity::class.java))
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT) as PendingIntent
}
private fun createIntent(cls: Class<*>): Intent =
Intent(context, cls).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
}
addParentStack()方法会读取它的参数Activity在AndroidManifest.xml中设置的android:parentActivityName属性并将这个属性值作为点击导航栏back按钮的返回的指定的落地activity页面。
这样的话有下面的效果:
- 应用未启动:点击通知栏会启动SecondActivity,重复点击通知栏也会重复创建SecondActivity,点击导航栏的back按钮会直接返回到android:parentActivityName指定的MainActivity而不是SecondActivity也不是桌面,注意,这个MainActivity是重新创建的并执行了onCreate方法
- 应用启动了:同上
虽然设置了SingleTop,但是PendingIntent每次都是新建一个SecondActivity。那么按照之前说过的方法,把singleTop改为singleTask即把方法createIntent()里的flags这行代码改为flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
,结果还是不行。这是TaskStackBuilder的特性导致的,TaskStackBuilder总是会重置当前的task,清空当前task的所有activity并重新创建自己指定的新的activity。
TaskStackBuilder主要结合android:parentActivityName使用来处理点击导航栏的back按钮返回事件。