通常我们在开发中会有这样的需要:让用户跳转到特定的 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 按钮返回事件.
原文链接 代码地址
来源: http://www.jianshu.com/p/9d6f7da83a34