在通知单击时启动 Fragment 而不会丢失状态

Posted

技术标签:

【中文标题】在通知单击时启动 Fragment 而不会丢失状态【英文标题】:Launch Fragment on notification click without losing state 【发布时间】:2018-06-15 20:53:55 【问题描述】:

我有聊天应用程序,也可以通过点击通知直接启动。聊天片段也可以通过在应用程序内单击来手动启动。

如果用户在聊天片段上点击主页按钮然后点击通知,它应该以最后状态启动并且不调用活动的onDestroy 然后onCreate

像这样通过Activity 中的通知启动Fragment

((AppCompatActivity)context).getFragmentManager().beginTransaction().replace(R.id.Navigation_Main_Layout, screenFragment,"Chat").commit();

我正在处理来自FirebaseMessagingService的通知

public class FireBase_Messaging_Service extends FirebaseMessagingService 

    public static final String TAG="###FireBase MSG###";
    public static final int NOTIFICATION=5;
    String UserName;
    String ID;
    String Msg;

    Map<String,String> data;
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) 
        super.onMessageReceived(remoteMessage);
        Log.d(TAG,"From "+remoteMessage.getFrom());
        if (remoteMessage.getData().size()>0)
            data = remoteMessage.getData();
            Log.d(TAG,"Message Data "+remoteMessage.getData());
            data = remoteMessage.getData();

            UserName = data.get("name");
            ID = data.get("ID");
            Msg = data.get("Message");

            showNotification(Msg,ID,UserName);
        

        if (remoteMessage.getNotification()!=null)
            Log.d(TAG,"Message Notification Body "+remoteMessage.getNotification().getBody());
           // Toast.makeText(this, "Notification "+remoteMessage.getNotification().getBody(), Toast.LENGTH_LONG).show();
        
    

    private void showNotification(String Message,String ID,String UserName) 
        Log.d(TAG,"Show Notification "+Message+" "+ID);
        Intent intent=new Intent(this, Navigation_Drawer.class);
        intent.putExtra("Type","Text");
        //intent.putExtra("Type",MsgType);
        intent.putExtra("ID",ID);
        intent.putExtra("uname",UserName);
        intent.putExtra("Message",Msg);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        PendingIntent pendingIntent=PendingIntent.getActivity(this,NOTIFICATION,intent,PendingIntent.FLAG_UPDATE_CURRENT);
        int color = getResources().getColor(R.color.black);
        String ChannelID = "Message";
        notificationChannel(ChannelID,"Chat");
        NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(),ChannelID)
                .setSmallIcon(R.drawable.default_x)
                .setColor(color)
                .setContentTitle(UserName)
                .setContentText(Message)
                .setChannelId(ChannelID)
                .setTicker("My App")
                .setDefaults(Notification.DEFAULT_VIBRATE | Notification.DEFAULT_SOUND | Notification.FLAG_SHOW_LIGHTS)
                .setLights(0xff00ff00, 1000, 500) // To change Light Colors
                .setStyle(new NotificationCompat.BigTextStyle().bigText(Message))//For Expandable View
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setContentIntent(pendingIntent)
                .setAutoCancel(true);

        NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
        managerCompat.notify(NOTIFICATION,builder.build());
    

    @Override
    public void onDeletedMessages() 
        super.onDeletedMessages();
    

    private void notificationChannel (String ChannelID, String channelName) 
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) 
            NotificationChannel  channel = new NotificationChannel(ChannelID,channelName, NotificationManager.IMPORTANCE_DEFAULT);
            channel.setLightColor(Color.GREEN);
            NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
        
    

在上面的代码中,我尝试Intent 使用不同的标志,例如Intent.FLAG_ACTIVITY_NEW_TASK,IntentFLAG_ACTIVITY_SINGLE_TOPFLAG_ACTIVITY_BROUGHT_TO_FRONTFLAG_ACTIVITY_CLEAR_TOP 等。但是,它总是首先调用活动的(导航抽屉)onDestroy,然后然后onCreate。然后它从头启动片段。

如何避免应用重新创建ActivityFragment

【问题讨论】:

【参考方案1】:

据我所知,Android Activity 的预期行为与 Activity 和 Fragment 的 Android 生命周期相冲突。当用户按下返回或主页按钮时,活动或片段将按照该活动或片段实例的生命周期经历onPauseonDestroy。除非您在活动中调用 finish 以避免在活动中调用 onDestroy 函数,否则您无法避免它。但是,您不想完成该活动,您想使用相同的活动并且不希望重新创建该活动。

所以我在想一种不同的方法来解决你的问题。大多数情况下,重新创建活动或片段的问题直接指示要获取的资源以及在初始化活动或片段时要获取的大量资源是开销。因此,当资源在保存的实例状态下已经可用时,我们可能会避免获取要在活动或片段中使用的资源。

例如,当您在活动的onCreate 函数中获取保存在 SQLite 数据库中的一些数据时,您可能不想在活动的方向更改时再次获取它,这会强制重新创建活动。在这种情况下,您可能需要选择一个在 Activity 重新创建后仍然存在的加载程序(我说的是 CursorLoaderLoaderCallbacks 的实现)。如果已经获取,加载器的实现将不会再次从 SQLite 数据库获取数据,并将在重新创建活动时提供数据。

我想推荐的另一个漂亮的东西是使用ViewModel。您可能会发现 documentation here 解释了实现。 ViewModel 在 Activity 重新创建后仍然存在,您可以使用 ViewModel 保存实例状态,这将减少加载时间。

重点是,您可能无法欺骗生命周期函数,但您可以选择使用能够在您的活动和片段重新创建后幸存下来的数据模型。

【讨论】:

谢谢,我会努力实现的。我想告诉你 onDestroy 在我点击通知然后 oncreate 后调用。 我认为这是为了那些你试图设置的意图标志。一些意图标志将重新创建活动,破坏活动的任何先前实例。 我想你可以考虑看看here。 实际上我们的主要问题是***.com/questions/50842745/…。我认为失去状态是导致问题的原因 我的荣幸。我也在研究这个问题。【参考方案2】:

我在第一次启动时加载了活动和片段,没有任何标志。当我收到通知时,我正在添加一个标志,该标志使活动首先调用onDestroy,然后调用onCreate

每当活动首次创建时,我都会在活动标签下的清单文件中添加android:launchMode="singleTask"。它可以帮助我避免在 Activity 中重新创建 Fragment。

 <activity
            android:name=".Navigation_Drawer"
            android:launchMode="singleTask"
            android:theme="@style/AppTheme"/>

【讨论】:

以上是关于在通知单击时启动 Fragment 而不会丢失状态的主要内容,如果未能解决你的问题,请参考以下文章

避免由于启动画面或错误的控制器处于活动状态而丢失小部件发送的通知

手机操作系统中显示的 Bluemix 推送通知在单击时不会启动应用程序

来自相机的 Android 丢失片段视图

如何在按钮单击时关闭 uwp 中的吐司

单击 fcm 通知而不是打开启动器活动时打开浏览器

从 android 推送通知单击启动时,意图数据未在活动中更新