浅析Fragment为什么需要Public的empty constructor

原文地址:http://cache.baiducontent.com/c?m=9d78d513d99c12f60ffa940f4c5197310e54f1326284915468d4e214c03b0c564717b4f17c635645c4c50d7001de5e5d9aab7765377262f0cb96d31bcabbe33f2ff93035004fda1705d368f5980732c151c306feaf68b0e5b274d8b9d2a28e090f8b15453dd1b6d61a1714bb29aa4777f4a6e813544b57fab33f3fb91f3568882233ab5aa9be6f3b1081818c1d1a9f3dd0164dc0e928fa7c4ce244a6181e2059f30cf753007867fd1970b917665ad3f65be6287e4527b749&p=81759a46d7c15df106b6c7710f51a5&newp=8377c64ad4934eac59ef8c6d1b42cb231610db2151d7d51e6b82c825d7331b001c3bbfb423251a02d2c6766d05aa4c58e8f03578350923a3dda5c91d9fb4c57479&user=baidu&fm=sc&query=make+sure+class+name+exists%2C+is+public%2C+and+has+an+empty+constructor+that+is+public&qid=d9cc0cd300005f91&p1=1

  • Fragment报错:08-03 15:18:10.521: E/AndroidRuntime(13245): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.dami.student/com.dami.student.ui.campus.activity.CampusStudentActivity}: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.dami.student.ui.chatui.ui.fragment.StudentChatFragment: make sure class name exists, is public, and has an empty constructor that is public
  • 浅析Fragment为什么需要Public的empty constructor

    2015年01月23日 16:40:20

    阅读数:9483

    最近在做一个项目。当app启动后然后使其进入后台进程(按home键)接着使用其它app(用其它app的目的是为了让系统内存不足然后让系统将我们的app杀死)。当我们的app被系统杀死后这时候通过任务管理点击我们的app进入应用。这时候问题出现了app崩溃了为了不暴露项目一些项目包名或者类名的信息就省略了下面就是异常的关键信息:

    java.lang.RuntimeException: Unable to start activity 
    ComponentInfo{省略}: 
    android.support.v4.app.Fragment$InstantiationException: 
    Unable to instantiate fragment 省略:
    make sure class name exists, is public, and has an empty constructor that is
    public
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1750)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1766)
    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:2960)
    at android.app.ActivityThread.access$1600(ActivityThread.java:127)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:945)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:130)
    at android.app.ActivityThread.main(ActivityThread.java:3818)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:875)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:633)
    at dalvik.system.NativeStart.main(Native Method)
    Caused by: android.support.v4.app.Fragment$InstantiationException: 
    Unable to instantiate fragment 省略: 
    make sure class name exists, is public, and has an empty constructor that
    is public
    at android.support.v4.app.Fragment.instantiate(Fragment.java:399)
    at android.support.v4.app.FragmentState.instantiate(Fragment.java:97)
    at android.support.v4.app.FragmentManagerImpl.restoreAllState(FragmentManager.java:1760)
    at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:200)
    at 省略
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1710)
    ... 12 more
    Caused by: java.lang.InstantiationException: 
    <pre code_snippet_id="587294" snippet_file_name="blog_20150123_1_3548387" name="code" class="plain">省略

    at java.lang.Class.newInstanceImpl(Native Method)at java.lang.Class.newInstance(Class.java:1409)at android.support.v4.app.Fragment.instantiate(Fragment.java:388)... 18 more

    从上面的关键信息可以看出异常的原因就是因为使用的fragment没有public的empty constructor。事实也确实如此我的那个fragment有一个带参数的constructor但是i没有Public的empty constructor。

    那么问题来了为什么Fragment必须要empty constructor?

    首先看到这个异常栈就能发现问题是由于fragment在还原状态中调用FragmentState#instantitae()->Fragment#instantitae()抛出异常那么就顺着这个调用点进入源代码。

    首先看下FragmentState对应的关键代码片

     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);
            
            if (mSavedFragmentState != null) {
                mSavedFragmentState.setClassLoader(activity.getClassLoader());
                mInstance.mSavedFragmentState = mSavedFragmentState;
            }
            mInstance.setIndex(mIndex, parent);
            mInstance.mFromLayout = mFromLayout;
            mInstance.mRestored = true;
            mInstance.mFragmentId = mFragmentId;
            mInstance.mContainerId = mContainerId;
            mInstance.mTag = mTag;
            mInstance.mRetainInstance = mRetainInstance;
            mInstance.mDetached = mDetached;
            mInstance.mFragmentManager = activity.mFragments;
            if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
                    "Instantiated fragment " + mInstance);
            return mInstance;
        }

    很明显在上述代码片中一眼就找到了方法调用mInstance =�0�2Fragment.instantiate(activity, mClassName, mArguments)你猜的没错它就是fragment的实例对象赋值。顺着这个点进入Fragment#instantiate()

     

     

     /**
         * Create a new instance of a Fragment with the given class name.  This is
         * the same as calling its empty constructor.
         *
         * @param context The calling context being used to instantiate the fragment.
         * This is currently just used to get its ClassLoader.
         * @param fname The class name of the fragment to instantiate.
         * @param args Bundle of arguments to supply to the fragment, which it
         * can retrieve with {@link #getArguments()}.  May be null.
         * @return Returns a new fragment instance.
         * @throws InstantiationException If there is a failure in instantiating
         * the given fragment class.  This is a runtime exception; it is not
         * normally expected to happen.
         */
        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);
            }
        }

    从它的catch语句块也能看出是不是与之前说的异常信息一模一样。异常抛出确实是来源于此上述代码片的关键其实就是通过java的反射机制进行实例化Fragment。好即使对Java反射机制不太了解也没什么关系顺着思路来。其中Fragment f = (Fragment)clazz.newInstance()就是关键中的关键先来看下Class#newInstance()的java doc吧

    public T newInstance()
                  throws InstantiationException,
                         IllegalAccessException
           Creates a new instance of the class represented by this Class object. The class is instantiated as if by a new expression with an empty argument list. The class is initialized if it has not already been initialized.
           Note that this method propagates any exception thrown by the nullary constructor, including a checked exception. Use of this method effectively bypasses the compile-time exception checking that would otherwise be performed by the compiler. The Constructor.newInstance method avoids this problem by wrapping any exception thrown by the constructor in a (checked) InvocationTargetException.
    
    Returns:
           a newly allocated instance of the class represented by this object.
    Throws:
           IllegalAccessException - if the class or its nullary constructor is not accessible.
           InstantiationException - if this Class represents an abstract class, an interface, an array class, a primitive type, or void; or if the class has no nullary constructor; or if the instantiation fails for some other reason.
           ExceptionInInitializerError - if the initialization provoked by this method fails.
           SecurityException - If a security manager, s, is present and any of the following conditions is met:
    invocation of s.checkMemberAccess(this, Member.PUBLIC) denies creation of new instances of this class
    the caller's class loader is not the same as or an ancestor of the class loader for the current class and invocation of s.checkPackageAccess() denies access to the package of this class

    主要看下InstantiationException和IllegalAccessException对于我们来说其关键信息就是:

  • InstantiationException如果类没有empty constructor该异常就会抛出。
  • IllegalAccessException如果类没有public的empty constructor该异常就会抛出。
  • 为了佐证上述分析的正确性找到了Fragment API文档上的一段描述:

    public Fragment ()
    
    Default constructor. Every fragment must have an empty constructor, so it can be instantiated when restoring its activity's state. It is strongly recommended that subclasses do not have other constructors with parameters, since these constructors will not be called when the fragment is re-instantiated; instead, arguments can be supplied by the caller with setArguments(Bundle) and later retrieved by the Fragment with getArguments().
    
    Applications should generally not implement a constructor. The first place application code an run where the fragment is ready to be used is in onAttach(Activity), the point where the fragment is actually associated with its activity. Some applications may also want to implement onInflate(Activity, AttributeSet, Bundle) to retrieve attributes from a layout resource, though should take care here because this happens for the fragment is attached to its activity.

     

    总结:当系统因为内存紧张杀死非前台进程(并非真正的杀死)然后我们将被系统杀掉的非前台app带回前台如果这个时候有UI是呈现在Fragment中那么会因为restore造成fragment需要通过反射实例对象从而将之前save的状态还原而这个反射实例对象就是fragment需要Public的empty constructor的关键所在。

阅读更多
换一批

没有更多推荐了,返回首页