一、前言
该问题不存在,正常去写代码不会出现这个问题,之所以出现两个Activity,是因为同时使用了 。
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
如果应用内有个功能页面,可以通过正常流程使用,也可以通过通知栏进入,那么就会发现当这个页面存在的时候,同时通过通知栏进入,会出现两个实例。之所以出现这个问题是因为使用了PendingIntent.getActivity
函数的原因。另外需要注意的是使用TaskStackBuilder
也不能解决该问题,不过这里会把相关代码保留,作为参考。需要注意的是使用android:launchMode="singleInstance"
也可以解决,但是这种方式可能会在桌面出现两个图标。目前来说较为正确的解决方式是通过广播或者服务作为中转,然后跳转到Activity
才可以。为什么会出现两个实例,据官网所说是会创建一个新的Activity
栈。所以才会导致启动模式失效。不过实际测试的话并没有出现新的栈。本文只是解决了问题,但是对于为什么会出现问题倒是不明白。
正常来说启动一个页面,然后再通过通知栏启动这个页面,走的是启动模式设置的流程,普通的就创建,单例的就共用,但是假如,这时候第一个页面也就是ManActivity使用的是单例singleTask
,然后进入第二个页面。通知栏有个功能要进入第二个页面,由于某些业务问题,需要先进入MainActivity
,再进入该页面。处于某些原因,这个页面不能同时存在两个,比如有一个查询数据库的功能。否则会出问题,那么这个页面设置singleTask
和singleTop
会出现一个问题。就是会有一个时间同时存在两个页面,然后稍后第一个页面会销毁。所以里面的内容需要延迟加载。为什么会有实效的问题,这就要结合生命周期和启动模式来解释了,只有在一个页面onPause的时候,另外一个页面才会onCreate,当这个页面onResume时候,之前的页面才会onStop()->onDestory(),所以就会有这个时间段段问题。其实之所以这样也可以理解,毕竟不能另外一个页面还没有开始显示,这个页面就要销毁掉,也不合理。
这里重新说下启动模式,standard
就不说了,这里记录下singleTop
和singleTask
。singleTop
必须是在栈顶时候启动才会是同一个实例,不在栈顶就会创建新的。singleTask
则是不管在哪里,只要是在同一个栈里面,只要存在,那么启动就会将其调到最上层,在此之上的页面会被销毁,即使其是singleTask
。所以倘若有A->B->C->三个页面都为singleTask
,这时候启动B,就会变成A->B。C被移除掉。
二、TaskStackBuilder
相关代码
<activity
...
android:exported="false"
android:launchMode="singleTask"
android:parentActivityName=".function.main.MainActivity"
.../>
Intent intent = new Intent(context, GarbageCleanActivity.class);
//有启动处不走冷却期逻辑
intent.putExtra(KEY_FLAG, FLAG_RECALL_NEED);
intent.putExtra(KEY_ENABLE_FLAG, enable);
intent.putExtra(KEY_NOTIFY_ID, notifyId);
//创建返回栈
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
//添加Activity到返回栈
stackBuilder.addParentStack(GarbageCleanActivity.class);//这一行可有可无
stackBuilder.addNextIntentWithParentStack(intent);
//添加Intent到栈顶
stackBuilder.addNextIntent(intent);
int flag = 0;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S){
flag = PendingIntent.FLAG_IMMUTABLE;
}else{
flag = PendingIntent.FLAG_UPDATE_CURRENT;
}
PendingIntent pendingIntent = stackBuilder.getPendingIntent(0, flag);
//拿到PendingIntent后绑定通知栏即可
三、广播
广播分为静态广播、动态广播。这里使用静态广播
<receiver
android:name=".function.util.NotificationReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="${notifictionReceiver}"/>
</intent-filter>
</receiver>
class NotificationReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val activityIntent = Intent(Intent.ACTION_VIEW)
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
activityIntent.setClass(context!!, MainActivity::class.java)
context.startActivity(activityIntent)
}
}
private fun getContentIntent(cxt: Context, type: Function): PendingIntent {
val intent = Intent(cxt, NotificationReceiver::class.java)
intent.action = BuildConfig.NOTIFICTION_RECEIVER
return PendingIntent.getBroadcast(cxt, UUID.randomUUID().hashCode(), intent, PendingIntent.FLAG_CANCEL_CURRENT)
为了保证是Activity
是一个实例,需要 android:launchMode="singleTask"
。
四、服务
参考Android 点击通知栏消息打开activity,并判断app是否运行
五、收起通知栏
上述代码会导致另外一个问题,就是通知栏无法自动收起,可以使用以下代码关闭通知栏
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS))
但是这个代码在Android12上会有问题,建议使用另外一种方式,在Intent.ACTION_CLOSE_SYSTEM_DIALOGS
的源码中会有提示,然后根据源码进行修改即可,不过这种方式需要权限。对权限要求严格的话可能无法使用