文章目录

  • 一、前言
  • 二、TaskStackBuilder
  • 三、广播
  • 四、服务
  • 五、收起通知栏
  • 六、参考链接


一、前言

该问题不存在,正常去写代码不会出现这个问题,之所以出现两个Activity,是因为同时使用了
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);

如果应用内有个功能页面,可以通过正常流程使用,也可以通过通知栏进入,那么就会发现当这个页面存在的时候,同时通过通知栏进入,会出现两个实例。之所以出现这个问题是因为使用了PendingIntent.getActivity函数的原因。另外需要注意的是使用TaskStackBuilder也不能解决该问题,不过这里会把相关代码保留,作为参考。需要注意的是使用android:launchMode="singleInstance"也可以解决,但是这种方式可能会在桌面出现两个图标。目前来说较为正确的解决方式是通过广播或者服务作为中转,然后跳转到Activity才可以。为什么会出现两个实例,据官网所说是会创建一个新的Activity栈。所以才会导致启动模式失效。不过实际测试的话并没有出现新的栈。本文只是解决了问题,但是对于为什么会出现问题倒是不明白。
正常来说启动一个页面,然后再通过通知栏启动这个页面,走的是启动模式设置的流程,普通的就创建,单例的就共用,但是假如,这时候第一个页面也就是ManActivity使用的是单例singleTask,然后进入第二个页面。通知栏有个功能要进入第二个页面,由于某些业务问题,需要先进入MainActivity,再进入该页面。处于某些原因,这个页面不能同时存在两个,比如有一个查询数据库的功能。否则会出问题,那么这个页面设置singleTasksingleTop会出现一个问题。就是会有一个时间同时存在两个页面,然后稍后第一个页面会销毁。所以里面的内容需要延迟加载。为什么会有实效的问题,这就要结合生命周期和启动模式来解释了,只有在一个页面onPause的时候,另外一个页面才会onCreate,当这个页面onResume时候,之前的页面才会onStop()->onDestory(),所以就会有这个时间段段问题。其实之所以这样也可以理解,毕竟不能另外一个页面还没有开始显示,这个页面就要销毁掉,也不合理。
这里重新说下启动模式,standard就不说了,这里记录下singleTopsingleTasksingleTop必须是在栈顶时候启动才会是同一个实例,不在栈顶就会创建新的。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的源码中会有提示,然后根据源码进行修改即可,不过这种方式需要权限。对权限要求严格的话可能无法使用