横竖屏切换引起的fragment恢复问题
monkey测试时出现的问题log:
AndroidRuntime: FATAL EXCEPTION: main
AndroidRuntime: Process: com.android.settings, PID: 11889
AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.android.settings/com.android.settings.SubSettings}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment com.unisoc.settings.smartcontrols.SmartPickUpAnimation: could not find Fragment constructor
AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3500)
AndroidRuntime: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3678)
AndroidRuntime: at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:5623)
AndroidRuntime: at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:5486)
AndroidRuntime: at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:69)
AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2106)
AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
AndroidRuntime: at android.os.Looper.loop(Looper.java:223)
AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7943)
AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:603)
AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
AndroidRuntime: Caused by: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment com.unisoc.settings.smartcontrols.SmartPickUpAnimation: could not find Fragment constructor
AndroidRuntime: at androidx.fragment.app.Fragment.instantiate(Fragment.java:619)
AndroidRuntime: at androidx.fragment.app.FragmentContainer.instantiate(FragmentContainer.java:57)
AndroidRuntime: at androidx.fragment.app.FragmentManager$3.instantiate(FragmentManager.java:455)
AndroidRuntime: at androidx.fragment.app.FragmentStateManager.<init>(FragmentStateManager.java:88)
AndroidRuntime: at androidx.fragment.app.FragmentManager.restoreSaveState(FragmentManager.java:2655)
AndroidRuntime: at androidx.fragment.app.FragmentController.restoreSaveState(FragmentController.java:198)
AndroidRuntime: at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:237)
AndroidRuntime: at com.android.settings.core.SettingsBaseActivity.onCreate(SettingsBaseActivity.java:65)
AndroidRuntime: at com.android.settings.SettingsActivity.onCreate(SettingsActivity.java:254)
AndroidRuntime: at android.app.Activity.performCreate(Activity.java:8079)
AndroidRuntime: at android.app.Activity.performCreate(Activity.java:8062)
AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3470)
AndroidRuntime: ... 13 more
AndroidRuntime: Caused by: java.lang.NoSuchMethodException: com.unisoc.settings.smartcontrols.SmartPickUpAnimation.<init> []
AndroidRuntime: at java.lang.Class.getConstructor0(Class.java:2332)
AndroidRuntime: at java.lang.Class.getConstructor(Class.java:1728)
AndroidRuntime: at androidx.fragment.app.Fragment.instantiate(Fragment.java:604)
AndroidRuntime: ... 25 more
想办法定位到Exception的原因(Unable to instantiate fragment com.unisoc.settings.smartcontrols.SmartPickUpAnimation: could not find Fragment constructor),找到以下方法:即对fragment重建时会调用无参构造方法,而源码中没有则会抛出异常
public static Fragment instantiate(Context context, String fname, @Nullable 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);
if (!Fragment.class.isAssignableFrom(clazz)) {
throw new InstantiationException("Trying to instantiate a class " + fname
+ " that is not a Fragment", new ClassCastException());
}
sClassMap.put(fname, clazz);
}
Fragment f = (Fragment) clazz.getConstructor().newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.setArguments(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 (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);
}
}
定位到问题点后再看看代码中是如何写的:(0.0 根本没有无参构造函数,所以出现以上错误)
public class MuteIncomingCallsAnimation extends BaseAnimation {
public static final String TAG = "MuteIncomingCallsAnimation";
public MuteIncomingCallsAnimation(SmartSwitchPreference preference) {
super(preference);
mLayoutId = R.layout.mute_incoming_calls;
mImageViewId = R.id.mute_incoming_calls_display;
mAnimResId = R.drawable.mute_incoming_calls_anim;
mDatabaseKey = MUTE_INCOMING_CALLS;
}
}
进行修改:添加static的newInstance方法其中new一个无参的实例,并保存参数,然后返回实例,然后就能通过保存的参数进行恢复了
public class MuteIncomingCallsAnimation extends DialogFragment {
public static final String TAG = "MuteIncomingCallsAnimation";
private static SmartSwitchPreference mPreference;
public static MuteIncomingCallsAnimation newInstance(SmartSwitchPreference preference) {
final MuteIncomingCallsAnimation muteIncomingCallsDialog = new MuteIncomingCallsAnimation();
mPreference = preference;
return muteIncomingCallsDialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return Utils.createAnimationDialog(getActivity(), mPreference, R.layout.mute_incoming_calls,
R.id.mute_incoming_calls_display, R.drawable.mute_incoming_calls_anim, MUTE_INCOMING_CALLS);
}
}
Utils工具类,弹出一个dialog:
public static Dialog createAnimationDialog(final Context context, final SmartSwitchPreference preference,
int layoutId, int imageViewId, int animationId, final String databaseKey) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
final View customView = inflater.inflate(layoutId, null);
ImageView imageView = (ImageView) customView.findViewById(imageViewId);
imageView.setImageResource(animationId);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getDrawable();
animationDrawable.start();
AlertDialog.Builder animationDialog = new AlertDialog.Builder(context);
animationDialog.setView(customView);
animationDialog.setPositiveButton(R.string.smart_ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
boolean turnOn = which == DialogInterface.BUTTON_POSITIVE;
if (preference != null) {
preference.setChecked(turnOn);
ContentResolver resolver = context.getContentResolver();
if (DOZE_PICK_UP_GESTURE.equals(databaseKey) || WAKE_GESTURE_ENABLED.equals(databaseKey)) {
Settings.Secure.putInt(resolver, databaseKey, turnOn ? 1 : 0);
} else if (SmartMotionFragment.isSmartMotionEnabled(context)) {
Settings.Global.putInt(resolver, databaseKey, turnOn ? 1 : 0);
}
}
}
});
return animationDialog.create();
}
}
总结:
fragment进行横竖屏切换恢复的时候,会调用无参构造方法,所以需要定义或调用无参构造方法才不会出现异常,恢复所需的参数可以通过Bundle或者全局变量之类的进行保存,方便获取,从而到达恢复的目的.