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

最近,在做一个项目。当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 省略$3:
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 省略$3:
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:
com.redacted.redacted.PreferenceListFragment$3
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 = Fragment.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为什么需要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.
时间: 2024-10-15 17:52:27

浅析Fragment为什么需要Public的empty constructor的相关文章

Android广播错误.MainActivity$MyReceiver; no empty constructor

广播的定义,如果是内部类,必须为静态类 http://blog.csdn.net/chdjj/article/details/19496567 下面总结一下作为内部类的广播接收者在注册的时候需要注意的地方: 1.清单文件注册广播接收者时,广播接收者的名字格式需要注意.因为是内部类,所以需要在内部类所在的类与内部类之间加上$符号: android:name="com.example.brocastdemo.MainActivity$MyReceiver" 2.内部类在声明时一定要写成静态

android官方侧滑菜单DrawerLayout详解

drawerLayout是Support Library包中实现了侧滑菜单效果的控件,可以说drawerLayout是因为第三方控件如MenuDrawer等的出现之后,google借鉴而出现的产物.drawerLayout分为侧边菜单和主内容区两部分,侧边菜单可以根据手势展开与隐藏(drawerLayout自身特性),主内容区的内容可以随着菜单的点击而变化(这需要使用者自己实现). drawerLayout的使用很方便,使用drawerLayout的要点如下: 1.drawerLayout其实是

【转】android官方侧滑菜单DrawerLayout详解

原文网址:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/0925/1713.html drawerLayout是Support Library包中实现了侧滑菜单效果的控件,可以说drawerLayout是因为第三方控件如MenuDrawer等的出现之后,google借鉴而出现的产物.drawerLayout分为侧边菜单和主内容区两部分,侧边菜单可以根据手势展开与隐藏(drawerLayout自身特性),主内容区的内容可以随着

Android实际开发中的bug总结与解决方法(一)

                                                                             Android实际开发中的bug总结与解决方法(一) Android开发中有很多bug,我们是完全可以在线下避免的,不要等到线上报的BUG的再去修复.下面是我在实际开发中遇到过的bug和解决方法. BUG 1: java.lang.RuntimeException: Unable to start activity ComponentInfo

ViewDragHelper实践之仿Android官方侧滑菜单NavigationDrawer效果

相信经常使用移动应用的用户都很熟悉侧滑菜单栏, 下拉, 下弹, 上弹等应用场景, 几乎主流的移动应用无论IOS 还是Android都能看到. 2.3以前的时候, 很多第三方比如SlidingMenu, MenuDrawer, ActionbarSherlock等等都很大程度的丰富和深化了这种交互理念.能让小小的屏幕, 容纳更多的交互接口. 也是这种趋势, Android官方在v4终于推出了DrawerLayout. 表示对侧滑的重视与肯定. 唠叨到这了. 去看了DrawerLayout的源码和官

Android DrawerLayout Plus 增强版抽屉菜单

DrawerLayout是官方提供的侧滑菜单,相比SliddingMenu,它更加轻量级.默认情况下,DrawerLayout可以设置左侧或者右侧滑出菜单.如下, xml布局: [html] view plain copy print? <!-- <!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to cons

Android DrawerLayout抽屉效果

官网guide:http://developer.android.com/training/implementing-navigation/nav-drawer.html 官网示例:NavigationDrawer.zip android.support.v4.widget.DrawerLayout 抽屉layout.该widget只能实现从左向右.从右向左 openDrawer(), closeDrawer(), isDrawerOpen() 下面贴一下示例的主要的布局文件 和 activit

android studio 使用checkstyle全攻略

首先你需要了解什么是代码规范,为什么需要这样 当你看完这篇文章,咱们接下来就该学习怎么配置checkstyle 步骤: 1.https://github.com/android/platform_development/blob/master/ide/intellij/codestyles/AndroidStyle.xml 2.将AndroidStyle.xml复制到~/.AndroidStudio/config/codestyles/(C:\Users\Administrator.Androi

g-api notes

目录 Q: What is GOrigin? What the meaning of parameters GMat(const GNode &n, std::size_t out) Q: how does cv::Mat convert to cv::gapi::own::Mat? how memory is handled? Q: Why not compile in GComputation ctor, but in apply()? Q: Why compile inputs is in