App Actions OPEN_APP_FEATURE热启动参数读取异常问题
我正在学习Google官方Codelab教程《Extend an Android app to the Google Assistant with App Actions (Level 2)》,当前进度到「Add the Open app feature BII capability」步骤,已经按照教程要求完成shortcuts.xml的相关配置。
我分别在两类设备上完成测试:
- 真机:运行Android 11、内存3664M的Motorola Moto G Power
- 虚拟设备:运行Android 12(API 31)的Pixel 5,分别测试1536MB、4096MB两种内存配置
测试中观察到的现象如下:
- App Action测试工具内的功能验证全部正常
- 首次通过Google Assistant唤起应用时,可以正确根据传入的feature参数展示对应内容
- 返回Android主屏幕后再次通过Assistant唤起、传入不同feature参数时,应用会被拉起到前台,但经常展示首次唤起时的旧内容;只有手动在最近任务列表划掉应用关闭进程后,唤起才能100%正常;仅返回主屏幕不杀进程的场景下,功能偶发正常、偶发异常。
初步判断异常原因是应用未被销毁时TasksActivity.onCreate()不会被调用:我给onCreate()、onStart()、onResume()、onPause()、onStop()几个生命周期方法加了日志之后验证了这个猜想——原有逻辑写在onCreate()里构建包含TasksFragment的布局,且TasksFragment.onCreateView()只有在TasksActivity.onCreate()触发时才会执行,代码通过activity?.intent?.extras获取feature参数。
在做Android开发的朋友建议下,我新增了TasksFragment.onResume()逻辑,在视图重新展示时再次读取activity?.intent?.extras获取feature参数。日志验证显示热启动场景下onResume()确实会触发(此时onCreate()/onCreateView()都不执行),但读取到的feature参数还是旧值,判断是Activity持有的Intent对象没有更新为新唤起的Intent。
不管是应用冷启动,还是应用已经在后台运行的热启动场景,怎么才能正确捕获最新的Intent和对应参数?是否需要调整Android生命周期的处理位置,把参数处理逻辑从onCreate()迁移到其他生命周期方法?
附:调试日志与代码片段
首次冷启动唤起日志
首次使用指令「show my completed tasks using AppName」唤起应用时的日志,注意日志中触发了onCreateView和onCreate,且feature=completed_tasks:
2022-07-12 20:07:55.912 18392-18392/com.addventure.appactions.common D/TasksFragment: >> onCreateView 2022-07-12 20:07:55.912 18392-18392/com.addventure.appactions.common D/TasksFragment: [feature=completed_tasks] 2022-07-12 20:07:55.912 18392-18392/com.addventure.appactions.common D/TasksFragment: [com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID=2132082905] 2022-07-12 20:07:55.912 18392-18392/com.addventure.appactions.common D/TasksFragment: [extra_accl_intent=true] 2022-07-12 20:07:55.987 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onCreate 2022-07-12 20:07:55.988 18392-18392/com.addventure.appactions.common D/TasksActivity: [feature=completed_tasks] 2022-07-12 20:07:55.988 18392-18392/com.addventure.appactions.common D/TasksActivity: [com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID=2132082905] 2022-07-12 20:07:55.988 18392-18392/com.addventure.appactions.common D/TasksActivity: [extra_accl_intent=true] 2022-07-12 20:07:56.012 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onStart 2022-07-12 20:07:56.018 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onResume 2022-07-12 20:07:56.020 18392-18392/com.addventure.appactions.common D/TasksFragment: >> onResume 2022-07-12 20:07:56.020 18392-18392/com.addventure.appactions.common D/TasksFragment: [feature=completed_tasks] 2022-07-12 20:07:56.020 18392-18392/com.addventure.appactions.common D/TasksFragment: [com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID=2132082905] 2022-07-12 20:07:56.020 18392-18392/com.addventure.appactions.common D/TasksFragment: [extra_accl_intent=true]
返回主屏幕日志
2022-07-12 20:09:20.073 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onPause 2022-07-12 20:09:20.124 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onStop
热启动唤起日志
返回主屏幕后使用指令「show my open tasks using AppName」重新唤起应用时的日志,注意日志中未打印onCreate和onCreateView,且feature=completed_tasks,和本次唤起应该传入的active_tasks参数不符:
2022-07-12 20:09:22.130 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onStart 2022-07-12 20:09:22.132 18392-18392/com.addventure.appactions.common D/TasksActivity: >> onResume 2022-07-12 20:09:22.132 18392-18392/com.addventure.appactions.common D/TasksFragment: >> onResume 2022-07-12 20:09:22.133 18392-18392/com.addventure.appactions.common D/TasksFragment: [feature=completed_tasks] 2022-07-12 20:09:22.133 18392-18392/com.addventure.appactions.common D/TasksFragment: [com.google.android.apps.gsa.shared.util.starter.IntentStarter.ERROR_TOAST_ID=2132082905] 2022-07-12 20:09:22.133 18392-18392/com.addventure.appactions.common D/TasksFragment: [extra_accl_intent=true]
shortcuts.xml配置
<!-- OPEN APP FEATURE with inline inventory 注意:请将com.addventure.appactions.common替换为你在app/build.gradle中配置的application ID --> <!-- Add Get Thing BII shortcuts --> <shortcut android:shortcutId="active_tasks" android:shortcutShortLabel="@string/label_active" android:enabled="false"> <capability-binding android:key="actions.intent.OPEN_APP_FEATURE"> <parameter-binding android:key="feature" android:value="@array/active_tasks_synonyms" /> </capability-binding> </shortcut> <shortcut android:shortcutId="completed_tasks" android:shortcutShortLabel="@string/label_completed" android:enabled="false"> <capability-binding android:key="actions.intent.OPEN_APP_FEATURE"> <parameter-binding android:key="feature" android:value="@array/completed_tasks_synonyms" /> </capability-binding> </shortcut> <!-- Add Open App Feature BII capability --> <capability android:name="actions.intent.OPEN_APP_FEATURE"> <intent android:action="android.intent.action.VIEW" android:targetPackage="com.addventure.appactions.common" android:targetClass="com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity"> <parameter android:name="feature" android:key="feature"/> </intent> </capability>
TasksActivity.kt代码
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.tasks_act) setupNavigationDrawer() setSupportActionBar(findViewById(R.id.toolbar)) // 问题排查用日志 log(TAG, "onCreate", intent) val navController: NavController = findNavController(R.id.nav_host_fragment) appBarConfiguration = AppBarConfiguration.Builder(R.id.tasks_fragment_dest, R.id.statistics_fragment_dest) .setOpenableLayout(drawerLayout) .build() setupActionBarWithNavController(navController, appBarConfiguration) findViewById<NavigationView>(R.id.nav_view) .setupWithNavController(navController) } override fun onStart() { super.onStart() log(TAG, "onStart") } override fun onResume() { super.onResume() log( TAG, "onResume") } override fun onPause() { super.onPause() log( TAG, "onPause" ) } override fun onStop() { super.onStop() log( TAG, "onStop" ) } override fun onDestroy(){ super.onDestroy() log( TAG, "onDestroy" ) } companion object { fun log( tag: String, f: String ){ log( tag, f, null ) } fun log(tag: String, f: String, intent: Intent?) { Log.d(tag, ">> $f") val bundle: Bundle = intent?.extras ?: return bundle.keySet().forEach { key -> Log.d(tag, "[$key=${bundle.get(key)}]"); } } }
TasksFragment.kt代码
override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { viewDataBinding = TasksFragBinding.inflate(inflater, container, false).apply { viewmodel = viewModel } setHasOptionsMenu(true) // 设置FAB点击监听 setupFab() setFiltering( "onCreateView" ) return viewDataBinding.root } override fun onResume() { super.onResume() setFiltering( "onResume" ) } private fun setFiltering(f: String){ TasksActivity.log( TAG, f, activity?.intent ) viewModel.setFiltering(TasksFilterType.find(activity?.intent?.extras?.getString(OPEN_APP_FEATURE))) viewModel.setFiltering(activity?.intent?.extras?.getString(GET_THING)) }
热启动场景下Activity的intent字段默认不会自动更新,新的唤起Intent会通过onNewIntent()回调传递,按以下步骤修改即可覆盖冷、热启动全场景:
- 第一步:修改
AndroidManifest.xml中TasksActivity的启动模式,添加android:launchMode="singleTop"属性,避免栈顶已存在的Activity实例被重复创建,保证新唤起Intent走onNewIntent回调。 - 第二步:在
TasksActivity中重写onNewIntent()方法,手动调用setIntent()更新Activity持有的Intent对象,同时通知关联的Fragment处理新参数,参考代码:
override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) // 手动更新Intent实例,否则后续调用getIntent()只会返回旧值 setIntent(intent) log(TAG, "onNewIntent", intent) // 通知TasksFragment处理新参数 val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) navHostFragment?.childFragmentManager?.fragments?.forEach { fragment -> if (fragment is TasksFragment) fragment.handleIntentParams(intent) } }
- 第三步:重构
TasksFragment的参数处理逻辑,把原来散落在onCreateView、onResume里的参数解析、过滤条件更新逻辑抽成统一的handleIntentParams方法,冷启动初始化、热启动收到新Intent时都调用这个方法。注意不要在onResume里无条件读取Intent刷新UI,避免普通前后台切换触发不必要的重绘,可以加个布尔标记位,只有onNewIntent收到新参数时才在onResume里触发刷新。
内容的提问来源于stack exchange,提问作者Prisoner

