通常我們?cè)陂_發(fā)中會(huì)有這樣的需要:讓用戶跳轉(zhuǎn)到特定的activity頁(yè)面谢肾。一個(gè)典型的案例是通知欄點(diǎn)擊啟動(dòng)應(yīng)用程序并跳轉(zhuǎn)到指定activity頁(yè)面桦锄。Android Oreo--Notifications這篇文章介紹了如何在android oreo中新建通知欄甫菠,這一切貌似都沒有什么難度蹦疑,但是如果你嘗試在跳轉(zhuǎn)到的落地頁(yè)activity中點(diǎn)擊導(dǎo)航欄的back鍵蚓挤,就可能會(huì)有點(diǎn)迷糊或者困惑商源。
下面代碼是MainActivity绊含,布局中有一個(gè)按鈕點(diǎn)擊它進(jìn)入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是配置了導(dǎo)航欄的層次結(jié)構(gòu)。在這種情況下炊汹,點(diǎn)擊導(dǎo)航欄的back按鈕將返回到MainActivity躬充。這樣做是一個(gè)很好的習(xí)慣,而且以后還會(huì)很有用讨便。
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)
}
}
創(chuàng)建通知欄的代碼:
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)
}
應(yīng)用會(huì)有一下情況:
- 應(yīng)用未啟動(dòng):點(diǎn)擊通知欄會(huì)啟動(dòng)SecondActivity充甚,重復(fù)點(diǎn)擊通知欄也只會(huì)有這一個(gè)SecondActivity,點(diǎn)擊導(dǎo)航欄的back按鈕會(huì)直接返回到桌面而不是MainActivity
- 應(yīng)用啟動(dòng)了:點(diǎn)擊通知欄會(huì)啟動(dòng)SecondActivity霸褒,重復(fù)點(diǎn)擊通知欄也會(huì)重復(fù)創(chuàng)建SecondActivity伴找,點(diǎn)擊導(dǎo)航欄的back按鈕會(huì)返回上一個(gè)SecondActivity,直到返回MainActivity
對(duì)于第二個(gè)情況废菱,你有沒有感到迷惑呢技矮?你可能會(huì)呵呵一下抖誉,認(rèn)為很簡(jiǎn)單,只需要在方法getContentIntentOld()里面加上一行代碼this.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
就能不再重復(fù)創(chuàng)建SecondActivity衰倦。事實(shí)上是錯(cuò)的袒炉,加上那一行代碼不起任何作用。設(shè)置singleTask的模式才能避免那個(gè)問(wèn)題樊零,即在方法getContentIntentOld()里面加上這行代碼:this.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
這樣的代碼點(diǎn)擊導(dǎo)航欄的back按鈕總是返回上一個(gè)的activity頁(yè)面我磁,而不能返回某個(gè)指定的activity頁(yè)面。有人可能會(huì)想出這樣的解決方案:PendingIntent跳轉(zhuǎn)到MainActivity并設(shè)置MainActivity為singleTask驻襟,在MainActivity的onNewIntent中再指定跳轉(zhuǎn)頁(yè)面夺艰。這樣的確可行,但是我們來(lái)研究下TaskStackBuilder沉衣。
TaskStackBuilder在API 16中被引入郁副,在v4 core utils support library包中也能使用它,點(diǎn)擊導(dǎo)航欄的back按鈕可以返回到指定的activity頁(yè)面豌习。
使用下面的方法代替老的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()方法會(huì)讀取它的參數(shù)Activity在AndroidManifest.xml中設(shè)置的android:parentActivityName屬性并將這個(gè)屬性值作為點(diǎn)擊導(dǎo)航欄back按鈕的返回的指定的落地activity頁(yè)面霞势。
這樣的話有下面的效果:
- 應(yīng)用未啟動(dòng):點(diǎn)擊通知欄會(huì)啟動(dòng)SecondActivity,重復(fù)點(diǎn)擊通知欄也會(huì)重復(fù)創(chuàng)建SecondActivity斑鸦,點(diǎn)擊導(dǎo)航欄的back按鈕會(huì)直接返回到android:parentActivityName指定的MainActivity而不是SecondActivity也不是桌面愕贡,注意,這個(gè)MainActivity是重新創(chuàng)建的并執(zhí)行了onCreate方法
- 應(yīng)用啟動(dòng)了:同上
雖然設(shè)置了SingleTop巷屿,但是PendingIntent每次都是新建一個(gè)SecondActivity固以。那么按照之前說(shuō)過(guò)的方法,把singleTop改為singleTask即把方法createIntent()里的flags這行代碼改為flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
嘱巾,結(jié)果還是不行憨琳。這是TaskStackBuilder的特性導(dǎo)致的,TaskStackBuilder總是會(huì)重置當(dāng)前的task旬昭,清空當(dāng)前task的所有activity并重新創(chuàng)建自己指定的新的activity篙螟。
TaskStackBuilder主要結(jié)合android:parentActivityName使用來(lái)處理點(diǎn)擊導(dǎo)航欄的back按鈕返回事件。