Low Memory状态下app状态的恢复

当Android应用在低内存状态下被系统回收并重新打开时,发现原本的Fragment显示为空白,没有响应。经过调试,发现在低内存情况下,系统尝试恢复Fragment,但并未按预期进行正常初始化。解决方案是在Fragment的onAttach()方法中主动关联到Activity,但这种恢复方式可能造成复杂初始化的不稳定性。文章探讨了是否需要系统在低内存时恢复应用状态,并提出了期望系统提供自定义恢复选项的需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

app结构
     activity带有ViewPager
     ViewPager上带有4个fragment


这是非常典型的结构


操作过程
     点icon打开app
     点击home键隐藏app
     再次点击icon唤回app
     点击home键隐藏app
     开启多个其他app,要很耗费内存那种
     点击home键隐藏这些app
     再次点击icon唤回app
     app虽然唤回,但是上面的fragment已经不听招呼,空白,无反应


仔细debug,发现按键能够操作fragment,并且fragment能否反馈,但屏幕上还是显示一个空白fragment以及一个初始状态的变量值
所谓初始状态的变量值,是指静态代码设置的值,比如如下的字符串:

     public class FragmentListview extends Fragment {
         String currentPath = "static_ini_code_null";


在静态代码之后的初始化过程中这个值应该被改变,但是却没有,可见这是一个仅仅静态代码初始化的fragment

困扰了很久。在activity和fragment的所有生命周期函数处添加了打印,终于发现端倪

以下是第一次唤回app的流程,打印了activity,以及0和1两个fragment

lifecycle: DirPlayerActivity.onRestart()
lifecycle: FragmentListview.onStart() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onStart() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onStart()
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: FragmentListview.setListviewAdapter() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.setListviewAdapter() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: DirPlayerActivity.updateFileInfor()! FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0} [Lcom.maxproj.android.dirplayer.MyArrayAdapter;@41c2c018
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: FragmentListview.setListviewAdapter() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.setListviewAdapter() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.updateFileInfor()! FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1} [Lcom.maxproj.android.dirplayer.MyArrayAdapter;@41c2c018
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: DirPlayerActivity.onResume()
lifecycle: FragmentListview.onResume() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onResume() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onResumeFragments()
lifecycle: FragmentListview.onPause() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onPause() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onPause()
lifecycle: FragmentListview.onStop() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onStop() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onStop()
lifecycle: FragmentListview.onDestroy() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onDetach() 0:/mnt/sdcard FragmentListview{41c2c1a0 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onDestroy() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.onDetach() 1:/mnt/sdcard FragmentListview{41c3f848 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onDestroy()


可见唤回时是从onRestart()开始,且没有重新生成fragment的操作
这是第二次唤回app的流程,仍然打印了activity,以及0和1两个fragment

lifecycle: FragmentListview.newInstance() 0 FragmentListview{41c40198}
lifecycle: DirPlayerActivity.StaticCodeIniFragment{} 0 FragmentListview{41c40198}
lifecycle: FragmentListview.newInstance() 1 FragmentListview{41c4e3e8}
lifecycle: DirPlayerActivity.StaticCodeIniFragment{} 1 FragmentListview{41c4e3e8}
lifecycle: FragmentListview.onAttach() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onCreate() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onAttach() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.onCreate() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onCreate()
lifecycle: FragmentListview.onCreateView() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onActivityCreated() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onCreateView() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.onActivityCreated() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: FragmentListview.onStart() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onStart() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onStart()
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: FragmentListview.setListviewAdapter() 0:static_ini_code_null FragmentListview{41c40198}
lifecycle: FragmentListview.setListviewAdapter() 0:/mnt/sdcard FragmentListview{41c40198}
lifecycle: DirPlayerActivity.updateFileInfor()! FragmentListview{41c40198} [Lcom.maxproj.android.dirplayer.MyArrayAdapter;@41c3f260
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: FragmentListview.setListviewAdapter() 1:static_ini_code_null FragmentListview{41c4e3e8}
lifecycle: FragmentListview.setListviewAdapter() 1:/mnt/sdcard FragmentListview{41c4e3e8}
lifecycle: DirPlayerActivity.updateFileInfor()! FragmentListview{41c4e3e8} [Lcom.maxproj.android.dirplayer.MyArrayAdapter;@41c3f260
lifecycle: MyArrayAdapter.MyArrayAdapter()
lifecycle: DirPlayerActivity.onResume()
lifecycle: FragmentListview.onResume() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onResume() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onResumeFragments()
lifecycle: FragmentListview.onPause() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onPause() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onPause()
lifecycle: FragmentListview.onSaveInstanceState() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onSaveInstanceState() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onSaveInstanceState()
lifecycle: FragmentListview.onStop() 0:static_ini_code_null FragmentListview{41c5ee80 #0 id=0x7f0a0001 android:switcher:2131361793:0}
lifecycle: FragmentListview.onStop() 0:static_ini_code_null FragmentListview{41c5f0c8 #1 id=0x7f0a0001 android:switcher:2131361793:1}
lifecycle: DirPlayerActivity.onStop()


可见,app不是从onRestart()开始,而是完全从activity静态代码开始初始化activity并且到达onCreate()
并且看到来历不明的两个fragment被onAttach()过来,这并不是activity静态代码生成的fragment


一个诀窍:可以将一个fragment或其他object当成字符串打印,会打印出该fragment的内存地址(估计是)
通过这个我们可以看出每次操作的是哪个fragment或其他object,尽管它们可能是同一类对象


如果高亮上面被打印出的各个fragment地址,就会发现activity静态代码生成的两个fragment被后续的activity代码初始化了
比如被setListviewAdapter()设置了adapter,但是这两个fragment却走不到onCreateView()和onResume()


被attach过来的fragment未经历activity代码的初始化,但是却走到了onCreateView()和onResume()
也即,app显示了被attach过来的fragment,仅仅被该fragment的静态代码初始化过
这正可以解释操作时看到的现象,被attach过来的应该是系统帮忙恢复的fragment


在官网上找到了low memory下系统可能的恢复操作,其中包括帮忙恢复fragment
但是,这个恢复的fragment怎样接收?怎样关联回activity?官网上却找不到具体的处理方法


在stackoverflow上找到若干类似的问题,其中之一和我预想的方法一致
也即,在fragment的onAttach()中将自己关联给activity,具体关键代码如下

//fragment代码
public class FragmentListview extends Fragment {
    int tab = -1; // attach时如果为-1,说明是系统帮忙恢复的。正常初始化后会被设置为0和1

    @Override
    public void onAttach(Activity activity){
        super.onAttach(activity);
        
        if (tab == -1){ // 系统帮忙创建的,更新到activity
         tab = ((DirPlayerActivity) getActivity()).sysAttachFragment; // 这里计数被系统attach了多少个fragment
         fragmentListviewInterface.sysAttachFragmentListviewLowMem(
         tab, 
         this);
         ((DirPlayerActivity) getActivity()).sysAttachFragment++;
        }
    }

}
//activity代码
public class DirPlayerActivity extends FragmentActivity implements
    FragmentListview.FragmentListviewInterface
    {
        int sysAttachFragment = 0;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_dir_player);
            for (int i = 0; i < LocalConst.tabCount; i++) {
                if(sysAttachFragment == 0){ // 正常启动,而不是系统帮忙恢复
                    fragmentListview[i] = FragmentListview.newInstance(i);
                }
        }
    }

    public void sysAttachFragmentListviewLowMem(int tab, FragmentListview fragment){
               fragmentListview[tab] = fragment;
    }

}

简单测试了一下,似乎可行

但是,感觉很不好
因为fragment也许和activity有很多关联,有很复杂的初始化过程
系统帮忙恢复的操作却非常的粗鲁
直接生成一个未正常初始化的fragment就强塞过来,并占领了对应的位置
这时候千头万绪,关联和恢复处理相当麻烦不可靠


在low memory的情况下恢复app的状态有必要吗?值得吗?
至少有一些app不会有这样的需求
所以,系统应该提供一个选项,让app自己选择要不要恢复原来的状态
有这样的选项吗?我没找到。或有,只是我不知道而已?


在我的这个项目中我就不希望系统帮忙恢复fragment
让app代码自己处理,结果会干净清爽得多,安心得多

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值