人生並不像火車要通過每個站似的經過每一個生活階段。人生總是直向前行走,從不留下什麼。 —— 劉易斯
最近接手了一個以前開發者編碼的半成品安卓工程。大概是時間倉促,代碼很多地方可以看到了凌亂與重複,更別說做了什麼優化工作了。隨手點擊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的一些基礎,實例的創建若沒有被銷燬,在棧中就會創建多個實例,若實例未被銷燬可以重複利用。