android任务堆栈Task Stack

通常我们在开发中会有这样的需要:让用户跳转到特定的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代码:




  

  
    
      
        
        
      
    

    
      
    
  


添加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)
}

应用会有一下情况:

  1. 应用未启动:点击通知栏会启动SecondActivity,重复点击通知栏也只会有这一个SecondActivity,点击导航栏的back按钮会直接返回到桌面而不是MainActivity
  2. 应用启动了:点击通知栏会启动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页面。
这样的话有下面的效果:

  1. 应用未启动:点击通知栏会启动SecondActivity,重复点击通知栏也会重复创建SecondActivity,点击导航栏的back按钮会直接返回到android:parentActivityName指定的MainActivity而不是SecondActivity也不是桌面,注意,这个MainActivity是重新创建的并执行了onCreate方法
  2. 应用启动了:同上

虽然设置了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按钮返回事件。

原文链接
代码地址

你可能感兴趣的:(android任务堆栈Task Stack)