應用異常導致重新啓動後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的一些基礎,實例的創建若沒有被銷燬,在棧中就會創建多個實例,若實例未被銷燬可以重複利用。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章