应用异常导致重新启动后Fragment布局发生重叠

人生并不像火车要通过每个站似的经过每一个生活阶段。人生总是直向前行走,从不留下什么。 —— 刘易斯

      

     最近接手了一个以前开发者编码的半成品安卓工程。大概是时间仓促,代码很多地方可以看到了凌乱与重复,更别说做了什么优化工作了。随手点击App出现了异常,白屏一会应用又重新启动了主界面,这是布局重叠现象发生了...........

一、重叠现象

二、分析问题

(1)从Application分析开始:

Application中添加了应用异常捕获并实现自动启动主界面的功能,CrashHandler用于异常的全局捕获:

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
/**
 * Created by dingchao on 2018/3/23.
 */

/*处理崩溃重叠*/
public class UnCeHandler implements Thread.UncaughtExceptionHandler {
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    public static final String TAG = "CatchExcep";
    MyApplication application;

    public UnCeHandler(MyApplication application){
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        this.application = application;
    }

    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if(!handleException(ex) && mDefaultHandler != null){
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        }else{
            try{
                Thread.sleep(2000);
            }catch (InterruptedException e){
                Log.e(TAG, "error : ", e);
            }
            Intent intent = new Intent(application.getApplicationContext(), FraOverActivity.class);
            PendingIntent restartIntent = PendingIntent.getActivity(application.getApplicationContext(), 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);
            //退出程序
            AlarmManager mgr = (AlarmManager)application.getSystemService(Context.ALARM_SERVICE);
            mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 1000, restartIntent); // 1秒钟后重启应用
            application.finishActivity();
        }
    }

    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     *
     * @param ex
     * @return true:如果处理了该异常信息;否则没有处理返回false.
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        //使用Toast来显示异常信息
        new Thread(){
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(application.getApplicationContext(), "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_SHORT).show();
                Looper.loop();
            }
        }.start();
        return true;
    }



}

界面发生重叠后输出后的日志信息:

//用于获取Activity存在的Fragment的数量  
List<Fragment> list=getSupportFragmentManager().getFragments();
  System.out.println("Activity Fragment数量......"+list.size());

应用发生异常后又重新启动,从正常显示Activity到崩溃后重新启动onCreate执行了2次:

三、如果解决问题呢?

  • 我是这样想的,是否可以在执行Activity的onStop()或onDestroy()函数中执行Fragment销毁操作。
public void removeFragment(){
        mFragmentTransaction = getSupportFragmentManager().beginTransaction();
        if (mHomeFragment != null) {
            mFragmentTransaction.remove(mHomeFragment);
        }
        if (mOtherFragment != null) {
            mFragmentTransaction.remove(mOtherFragment);
        }
        if (mThirdFragment != null) {
            mFragmentTransaction.remove(mThirdFragment);
        }
        mFragmentTransaction.commit();
    }

如果在onDestroy()中执行Fragment销毁操作,但是出现异常后onDestroy并未执行,太失望了,如果重新启动又会出现Fragment重叠现象。

如果在onStop()中执行Fragment销毁操作,可以随便看看现象:

当栈顶Activity执行onCreate()那么栈顶下面的一个Activity会执行onPause()或onStop()函数,那么看看在onStop()销毁Fragment会有什么结果........................还没有执行抛出异常操作就因为销毁Fragment而抛出异常,然后重新启动。这样是不可行的额,下面考虑其他方案。。。。。。

  • 在onSaveInstanceState()执行Fragment的保存,官方文档可查看Activity生命周期

onSaveInstanceState(Bundle outState)函数中做存储实例状态:
invoked when the activity may be temporarily destroyed, save the instance state here 当Activity面临被销毁的情况下,进行存储一些对象的状态

onCreate(Bundle savedInstanceState)函数恢复实例状态:recovering the instance state 恢复实例状态

构思了..............查看谷歌官方文档后有点启发了,我们可以在onSaveInstanceState()中保存应用将要奔溃时,最后一次显示的Fragment的标志或者Tag进行保存,在内存重启后,可以通过标志或者Tag进行恢复。

阅读了以上部分,你会感觉有点迷茫,ragment如何在Activity里面进行呈现,一开始就没有讲述,下面贴出代码了:

如何在Activity中,添加多个Fragment,我选择了操作Fragment的事务管理类(FragmentTransaction)并调用了add()、show()、hide()相关函数:

第一步:gradle文件中添加TabLayout组件的依赖库,方便进行Fragment的添加或者切换

dependencies {
   
     ...................

    //TabLayout依赖
    implementation 'com.android.support:design:29.0.0-alphal'

    ...................
}

第二步:创建Fragment导航和Fragment容器FragmentLayout的布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/frameLayout"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1.0" />

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/mytab"
        android:layout_width="match_parent"
        android:layout_height="60dp" />

</LinearLayout>

第三步:进行Fragment的添加或者切换操作

分为:开始事务、隐藏所有Fragment、添加或者显示Fragment、提交事务

 private String HOME_TAG = "0", OTHER_TAG = "1", THIRD_TAG = "2";

    private void switchFragment(int index) {
        //第一步:开始事务
        mFragmentTransaction = getSupportFragmentManager().beginTransaction();
        //第二步:隐藏所有Fragment
        hideFragment(mFragmentTransaction);
        //第三步:添加或者隐藏Fragment
        switch (index) {
            case 0:
                if (mHomeFragment == null) {
                    mHomeFragment = new HomeFragment();
                    mFragmentTransaction.add(R.id.frameLayout, mHomeFragment, HOME_TAG);
                } else {
                    mFragmentTransaction.show(mHomeFragment);
                }
                break;
            case 1:
                if (mOtherFragment == null) {
                    mOtherFragment = new OtherFragment();
                    mFragmentTransaction.add(R.id.frameLayout, mOtherFragment, OTHER_TAG);
                } else {
                    mFragmentTransaction.show(mOtherFragment);
                }
                break;
            case 2:
                if (mThirdFragment == null) {
                    mThirdFragment = new ThirdFragment();
                    mFragmentTransaction.add(R.id.frameLayout, mThirdFragment, THIRD_TAG);
                } else {
                    mFragmentTransaction.show(mThirdFragment);
                }
                break;
        }
        //第四步:提交事务
        mFragmentTransaction.commit();
    }

隐藏所有Fragment:

 public void hideFragment(FragmentTransaction mFragmentTransaction) {
        if (mHomeFragment != null) {
            mFragmentTransaction.hide(mHomeFragment);
        }
        if (mOtherFragment != null) {
            mFragmentTransaction.hide(mOtherFragment);
        }
        if (mThirdFragment != null) {
            mFragmentTransaction.hide(mThirdFragment);
        }
    }

 第四步:添加Fragment导航的选项卡:

 public class FraOverActivity extends BaseActivity implements 
        TabLayout.BaseOnTabSelectedListener {


  @Override
    public void initView() {
        mTabLayout = findViewById(R.id.mytab);
        mTabLayout.addTab(mTabLayout.newTab().setText("选项卡   一").setIcon(R.mipmap.ic_launcher));
        mTabLayout.addTab(mTabLayout.newTab().setText("选项卡二").setIcon(R.mipmap.ic_launcher));
        mTabLayout.addTab(mTabLayout.newTab().setText("选项卡三").setIcon(R.mipmap.ic_launcher));
        mTabLayout.addOnTabSelectedListener(this);

       
    }


     @Override
    public void onTabSelected(TabLayout.Tab tab) {
        
        //此处进行Fragment的切换操作
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {

    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {

    }

}



如果在Activity中展示Fragment并实现切换已经实现,接下来还得解决出现异常后发生重叠的问题:

当我们看到奔溃后,我们肯定要知道当前显示的Fragment:

    //得到当前Activity显示的fragment
    private Fragment getVisibleFragment()
    {
        Fragment lastFragment = null;
        fragmentManager = getSupportFragmentManager();
        fragmentList = fragmentManager.getFragments();
        for(Fragment fragment : fragmentList)
        {
            if (fragment != null && fragment.isVisible())
            {
                lastFragment = fragment;
                break;
            }
        }
        return lastFragment;
    }

在onSaveInstanceState()中存储最后一次显示的Fragment的标志或者Tag进行保存,在内存重启后,可以通过标志或者Tag进行恢复:

  @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        System.out.println(TAG + "onSaveInstanceState");
        Fragment lastVisibleFragment = getVisibleFragment();
        if (lastVisibleFragment != null) {
            outState.putString("lastVisibleFragment", lastVisibleFragment.getTag());
        }
    }

在onCreate()函数中进行Fragment的恢复:

 @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String tag = "0";
        if (savedInstanceState != null) {
            mHomeFragment = (HomeFragment) getSupportFragmentManager().findFragmentByTag(HOME_TAG);
            mOtherFragment = (OtherFragment) getSupportFragmentManager().findFragmentByTag(OTHER_TAG);
            mThirdFragment = (ThirdFragment) getSupportFragmentManager().findFragmentByTag(THIRD_TAG);
            tag = savedInstanceState.getString("lastVisibleFragment");
        }
        switchFragment(tag);
    }

请看结果:

总结:当应用发生异常时,若重新启动应用导致Fragment未销毁,我们需要对Fragment最后一次显示标志位做onSaveInstanceState()保存,方便在onCreate()函数中进行恢复。

在进行安卓开发之前应该都学习过Java的一些基础,实例的创建若没有被销毁,在栈中就会创建多个实例,若实例未被销毁可以重复利用。

发布了22 篇原创文章 · 获赞 17 · 访问量 6932

猜你喜欢

转载自blog.csdn.net/u013491829/article/details/94410664