前言

Fragment,简称碎片,是 Android 3.0(API 11)提出的,为了兼容低版本,support-v4 库中也开发了一套 Fragment API,最低兼容 Android 1.6。

过去 support-v4 库是一个 jar 包,24.2.0 版本开始,将 support-v4 库模块化为多个 jar 包,包含:support-fragment, support-ui, support-media-compat 等,这么做是为了减少 APK 包大小,你需要用哪个模块就引入哪个模块。

如果想引入整个 support-v4 库,则 compile ‘com.android.support:support-v4:24.2.1’,如果只想引入 support-fragment 库,则 com.android.support:support-fragment:24.2.1。

因为 support 库是不断更新的,因此建议使用 support 库中的 android.support.v4.app.
Fragment,而不要用系统自带的 android.app.Fragment。而如果要使用 support 库的 Fragment,Activity 必须要继承 FragmentActivity(AppCompatActivity 是 FragmentActivity 的子类)。

问题归纳

Could not find Fragment constructor

本次排查 Bug 的 fragment 基于 AndroidX1.1.0
线上 bugly 报了一个 Could not find Fragment constructor,先看一下 Fragment 的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* Constructor used by the default {@link FragmentFactory}. You must
* {@link FragmentManager#setFragmentFactory(FragmentFactory) set a custom FragmentFactory}
* if you want to use a non-default constructor to ensure that your constructor
* is called when the fragment is re-instantiated.
*
* <p>It is strongly recommended to supply arguments with {@link #setArguments}
* and later retrieved by the Fragment with {@link #getArguments}. These arguments
* are automatically saved and restored alongside the Fragment.
*
* <p>Applications should generally not implement a constructor. Prefer
* {@link #onAttach(Context)} instead. It is the first place application code can run where
* the fragment is ready to be used - the point where the fragment is actually associated with
* its context. Some applications may also want to implement {@link #onInflate} to retrieve
* attributes from a layout resource, although note this happens when the fragment is attached.
*/
public Fragment() {
initLifecycle();
}

主要看一下注释说明,如果你想要的使用非默认的构造函数,需要自己实现一个 FragmentFactory 去初始化,然后强烈推荐使用 setArguments 和 getArguments 存取参数。看了一下报错的类,可以基本确定是使用了带参数的构造函数

然后看一下报错的位置,在 Fragment 里搜索 could not find Fragment constructor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static Fragment instantiate(@NonNull Context context, @NonNull String fname,
@Nullable Bundle args) {
try {
Class<? extends Fragment> clazz = FragmentFactory.loadFragmentClass(
context.getClassLoader(), fname);
Fragment f = clazz.getConstructor().newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.setArguments(args);
}
return f;
} 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 (NoSuchMethodException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": could not find Fragment constructor", e);
} catch (InvocationTargetException e) {
throw new InstantiationException("Unable to instantiate fragment " + fname
+ ": calling Fragment constructor caused an exception", e);
}
}

可以看到这里通过反射,调用了空的构造函数

1
Fragment f = clazz.getConstructor().newInstance();

但是对应的类没有空的构造函数,所以抛出了这个异常。然后看一下初始的地方,是 FragmentActivity 在 onCreate 中调用的 mFragments.restoreSaveState(p);

解决方案:
Fragment 必须有一个无参 public 的构造函数,否则在数据恢复的时候,会报 crash
ps:手动测试发现私有的构造函数无法通过该 Constructor.newInstance 方法调用


to be continued…