Android "Unable to instantiate fragment"解决方案

但凡项目中有用到Fragment的,基本上都会遇到RT问题。笔者在网上查了很多相关资料,都没有发现对出现这个问题的原因进行深入分析的。所以打算在本文结合源码来分析下这个问题的产生,并提供解决方案。下面进入正题,直接上报错信息:

Caused by: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.ckjr.context.VoteActivity$VoteFragment: make sure class name exists, is public, and has an empty constructor that is public


可以看到是初始化fragment失败。第一反应:禁止系统修改导致的Activity销毁重建,于是在Androidmanifest.xml加上如下代码:


然而并没有太大软用,在手机上运行一段时间后,内存不够,应用被回收掉。再次进入依然需要重新创建,crash。

其实原因很简单。当切换到其他应用时,会调用FragmentActivity的onSaveInstanceState方法,再次进入,Activity重新创建,onCreate方法中会拿到savedInstanceState并re-instantiate。

我们先进入onSaveInstanceState():

/**
     * Save all appropriate fragment state.
     */
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Parcelable p = mFragments.saveAllState();
        if (p != null) {
            outState.putParcelable(FRAGMENTS_TAG, p);
        }
    }
Parcelable saveAllState() {
        .......
   FragmentManagerState fms = new FragmentManagerState();
        fms.mActive = active;
        fms.mAdded = added;
        fms.mBackStack = backStack;
        return fms;
    }
非常简单,将Fragment的状态保存到outState,而outState就是onCreate方法中的savedInstanceState。fms.Added,是一个数组,每当调用addFragment或attachFragment就会往里添加;fms.Active,在addFragment()中调用makeActive()将Fragment往其中添加;fms.mBackStack,Fragment栈,用来存储BackStackRecord的一个列表,而BackStackRecord则是用于记录Fragment的一些操作。

注意,所有Fragment状态保存在Bundle中,key为FRAGMENTS_TAG。接下来我们看看onCreate中的super.onCreate(savedInstanceState)中做了些什么。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        mFragments.attachActivity(this, mContainer, null);
        // Old versions of the platform didn't do this!
        if (getLayoutInflater().getFactory() == null) {
            getLayoutInflater().setFactory(this);
        }
        
        super.onCreate(savedInstanceState);
        
        NonConfigurationInstances nc = (NonConfigurationInstances)
                getLastNonConfigurationInstance();
        if (nc != null) {
            mAllLoaderManagers = nc.loaders;
        }
        if (savedInstanceState != null) {
            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
        }
        mFragments.dispatchCreate();
    }
拿到savedInstanceState,如果不为空,从中取出key FRAGMENTS_TAG的值,然后执行恢复操作。我们进入restoreAllState()

void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) {
        // If there is no saved state at all, then there can not be
        // any nonConfig fragments either, so that is that.
        if (state == null) return;
        FragmentManagerState fms = (FragmentManagerState)state;
        .........
        for (int i=0; i<fms.mActive.length; i++) {
            FragmentState fs = fms.mActive[i];
            if (fs != null) {
                Fragment f = fs.instantiate(mActivity, mParent);
                if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
                mActive.add(f);
                // Now that the fragment is instantiated (or came from being
                // retained above), clear mInstance in case we end up re-restoring
                // from this FragmentState again.
                fs.mInstance = null;
            } else {
                mActive.add(null);
                if (mAvailIndices == null) {
                    mAvailIndices = new ArrayList<Integer>();
                }
                if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i);
                mAvailIndices.add(i);
            }
        }
        .........
}

不用多说读者也能猜到这里大概做些什么,将saveAllState()中保存的 fms. Added、fms.Active和fms.mBackStack一一恢复。我们聚焦到这行代码上

Fragment f = fs.instantiate(mActivity, mParent);
就是在这里,进行了对Fragment的re-instantiate。点进去

public Fragment instantiate(FragmentActivity activity, Fragment parent) {
        if (mInstance != null) {
            return mInstance;
        }
        
        if (mArguments != null) {
            mArguments.setClassLoader(activity.getClassLoader());
        }
        
        mInstance = Fragment.instantiate(activity, mClassName, mArguments);
        ........
        return mInstance;
}
public static Fragment instantiate(Context context, String fname, Bundle args) {
        try {
            Class<?> clazz = sClassMap.get(fname);
            if (clazz == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = context.getClassLoader().loadClass(fname);
                sClassMap.put(fname, clazz);
            }
            Fragment f = (Fragment)clazz.newInstance();
            if (args != null) {
                args.setClassLoader(f.getClass().getClassLoader());
                f.mArguments = args;
            }
            return f;
        } catch (ClassNotFoundException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (java.lang.InstantiationException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        }
    }
看到我们熟悉的报错信息,想必这里就是问题的终点了。没错,就是Fragment f = (Fragment)clazz.newInstance()引起的。

报错原因:反射创建失败!

接下来,我们模拟一个小实验来得出最终的解决方案!

创建这么一个类,FragmentA模拟单独的一个fragment class;FragmentB模拟内部类Fragment。

public class FragmentA {
	
	public FragmentA(String str) {
	}

	
	
	public class FragmentB {
		
	}
}

先来验证B,在Test中执行如下代码:


很明显,直接报异常。这样调用class.newInstance反射构造FragmentB肯定失败。因此,我们修改FragmentB为

public static class FragmentB {
}

再此运行,结果Success,通过。

接下来验证FragmentA,依然异常


我们给其添加一个空的构造方法。

public class FragmentA {

	public FragmentA() {
		
	}
	
	public FragmentA(String str) {
	}

}
再次运行,Success。

由此我们得出结论,class.newInstance反射失败有2个原因:

1、Fragment无空构造方法;

2、Fragment是内部类,没有加static。


----------------------------------------------------------分割线------------------------------------------------------

最后提供一个比较暴力,且不建议这么使用的一个解决方法。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(null);
}


第一次写Blog,有不对的地方欢迎评论指正。


  • 19
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
这个异常通常是由于Fragment类没有默认的构造函数而引起的。当你在创建Fragment实例时,系统会调用Fragment的默认构造函数进行实例化。如果你的Fragment类中没有默认构造函数,就会抛出上述异常。 为了解决这个问题,有以下两种方法: 1. 在Fragment类中添加默认构造函数 你可以在Fragment类中手动添加一个无参构造函数,如下所示: ```kotlin class MainFragment : Fragment() { // 添加一个无参构造函数 constructor() // 其他代码 } ``` 这样就可以保证系统在创建Fragment实例时能够正常地进行实例化。 2. 使用newInstance方法传递参数 如果你的Fragment需要传递参数,可以使用静态的newInstance方法来创建Fragment实例,并在newInstance方法中传递参数。例如: ```kotlin class MainFragment : Fragment() { companion object { fun newInstance(param1: String, param2: Int): MainFragment { val fragment = MainFragment() val args = Bundle() args.putString("param1", param1) args.putInt("param2", param2) fragment.arguments = args return fragment } } // 其他代码 } ``` 在上述代码中,我们添加了一个静态的newInstance方法,该方法接收两个参数,将这些参数存储在Bundle中,并将Bundle设置为Fragment的arguments属性。在创建Fragment实例时,我们可以使用该静态方法来传递参数,例如: ```kotlin val fragment = MainFragment.newInstance("Hello", 123) ``` 这样就可以保证系统在创建Fragment实例时能够正常地进行实例化,并且能够传递参数。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值