onActivityResult中创建Fragment
一个异常的分析
A activity跳转到B activity,然后又返回到A activity,在A activity的onActivityResult中,给A添加一个Fragment,由于在Fragment的onCreateView方法中有个异常,导致了崩溃
07-15 15:48:44.699 3430 3430 D AndroidRuntime: Shutting down VM
07-15 15:48:44.702 3430 3430 E AndroidRuntime: FATAL EXCEPTION: main
07-15 15:48:44.702 3430 3430 E AndroidRuntime: Process: com.sohu.sohuvideo, PID: 3430
07-15 15:48:44.702 3430 3430 E AndroidRuntime: java.lang.RuntimeException: Unable to resume activity {com.sohu.sohuvideo/com.sohu.sohuvideo.ui.BuyVipActivity}: java.lang.NullPointerException: uriString
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3992)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4024)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:51)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:145)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.os.Looper.loop(Looper.java:214)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6990)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: Caused by: java.lang.NullPointerException: uriString
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.net.Uri$StringUri.<init>(Uri.java:490)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.net.Uri$StringUri.<init>(Uri.java:480)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.net.Uri.parse(Uri.java:452)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at com.sohu.sohuvideo.paysdk.ui.dialog.ActivityCommodityPayResultDialogFragment.onCreateView(ActivityCommodityPayResultDialogFragment.java:159)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:447)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2169)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1992)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1947)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1849)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentController.execPendingActions(FragmentController.java:447)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at androidx.fragment.app.FragmentActivity.onResume(FragmentActivity.java:458)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at com.sohu.sohuvideo.ui.BaseActivity.onResume(BaseActivity.java:285)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at com.sohu.sohuvideo.ui.BuyVipActivity.onResume(BuyVipActivity.java:316)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1412)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.Activity.performResume(Activity.java:7611)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3984)
07-15 15:48:44.702 3430 3430 E AndroidRuntime: ... 11 more
只是单纯解决这个异常很简单。不过,这边文章想说的是,我们可以从堆栈信息中了解一些知识。
从B Activity返回到A activity是先执行A的onActivityResult然后是onResume方法,在onActivityResult中把Fragment添加到A activity中的。
那为啥Fragment的onCreateView的执行过程是从Activity的onResume方法开始的呢?
这个就要从我们使用的Fragment commit方式说起了,我们采用的是commitAllowingStateLoss,commitAllowingStateLoss和commit方式都不是及时的,是主线程异步的,主线程异步指的是不在当前消息的执行体内,而是在Looper的下一个消息执行体内,而且会把之前所有提交还没执行的commit都执行。这个可以从源码中知道
/**
* Schedules the execution when one hasn't been scheduled already. This should happen
* the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when
* a postponed transaction has been started with
* {@link Fragment#startPostponedEnterTransition()}
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
void scheduleCommit() {
synchronized (mPendingActions) {
boolean postponeReady =
mPostponedTransactions != null && !mPostponedTransactions.isEmpty();
boolean pendingReady = mPendingActions.size() == 1;
if (postponeReady || pendingReady) {
mHost.getHandler().removeCallbacks(mExecCommit);
mHost.getHandler().post(mExecCommit);
updateOnBackPressedCallbackEnabled();
}
}
}
private Runnable mExecCommit = new Runnable() {
@Override
public void run() {
execPendingActions(true);
}
};
/**
* Only call from main thread!
*/
boolean execPendingActions(boolean allowStateLoss) {
ensureExecReady(allowStateLoss);
boolean didSomething = false;
while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) {
mExecutingActions = true;
try {
removeRedundantOperationsAndExecute(mTmpRecords, mTmpIsPop);
} finally {
cleanupExec();
}
didSomething = true;
}
updateOnBackPressedCallbackEnabled();
doPendingDeferredStart();
mFragmentStore.burpActive();
return didSomething;
}
如果按照真是按照上面讲的逻辑执行的话,执行逻辑应该是Handler的callback方法,mHost.getHandler()是new出来的一个Handler,不是ActivityThread$H这个handler,大家想想,平时new一个handler执行一个runnable是一个怎么样的调用堆栈。
可是,实际情况走的却是onResume,这个就需要看看onResume源码了
/**
* Dispatch onResume() to fragments. Note that for better inter-operation
* with older versions of the platform, at the point of this call the
* fragments attached to the activity are <em>not</em> resumed.
*/
@Override
protected void onResume() {
super.onResume();
mResumed = true;
mFragments.noteStateNotSaved();
mFragments.execPendingActions();
}
看到没,也调用了execPendingActions方法。同时也说明了一个问题,onActivityResult和onResume是在一个消息的执行体内,因为如果不在,就很难保证是先执行onResume还是先执行mExecCommit。而且我们也知道commit是主线程异步,也不只是绝对的。
把commitAllowingStateLoss换成commitNowAllowingStateLoss,让Fragment立即执行
07-15 16:22:23.291 6176 6176 E AndroidRuntime: FATAL EXCEPTION: main
07-15 16:22:23.291 6176 6176 E AndroidRuntime: Process: com.sohu.sohuvideo, PID: 6176
07-15 16:22:23.291 6176 6176 E AndroidRuntime: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { (has extras) }} to activity {com.sohu.sohuvideo/com.sohu.sohuvideo.ui.BuyVipActivity}: java.lang.NullPointerException: uriString
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.app.ActivityThread.deliverResults(ActivityThread.java:4588)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.app.ActivityThread.handleSendResult(ActivityThread.java:4630)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:49)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1926)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.os.Looper.loop(Looper.java:214)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:6990)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: Caused by: java.lang.NullPointerException: uriString
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.net.Uri$StringUri.<init>(Uri.java:490)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.net.Uri$StringUri.<init>(Uri.java:480)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.net.Uri.parse(Uri.java:452)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at com.sohu.sohuvideo.paysdk.ui.dialog.ActivityCommodityPayResultDialogFragment.onCreateView(ActivityCommodityPayResultDialogFragment.java:159)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:320)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:447)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2169)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1992)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1947)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1818)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:303)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at com.sohu.sohuvideo.paysdk.ui.dialog.ActivityCommodityPayResultDialogFragment$Builder.create(ActivityCommodityPayResultDialogFragment.java:109)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at com.sohu.sohuvideo.ui.BuyVipActivity.activityCommodityPaySuccess(BuyVipActivity.java:2055)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at com.sohu.sohuvideo.ui.BuyVipActivity.onActivityResult(BuyVipActivity.java:1246)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.app.Activity.dispatchActivityResult(Activity.java:7801)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: at android.app.ActivityThread.deliverResults(ActivityThread.java:4581)
07-15 16:22:23.291 6176 6176 E AndroidRuntime: ... 11 more
这个堆栈一看,就知道Fragment的操作在onActivityResult的方法内就直接执行了。这个很好理解
参考
你真的懂 Fragment 吗?—— AndroidX Fragment 核心原理分析
Fragment 的过去、现在和将来
【背上Jetpack之Fragment】从源码角度看 Fragment 生命周期 AndroidX Fragment1.2.2源码分析
正确创建Fragment的时机
这个章节涉及的问题是在onActivityResult中创建Fragment,有个问题是,在onActivityResult(或onResume)时机的时候,activity的state还没有恢复(activity可能被系统销毁了),commit Fragment就会报错。
下面这两篇文章就讨论了这个问题
"Failure Delivering Result " - onActivityForResult
Fragment Transactions & Activity State Loss
Fragment State Loss
fragment state loss这个问题,在使用fragment的时候,总是会时不时遇到。让你不再俱怕Fragment State Loss进行了深入的分析
onBackPressed不能乱用
onBackPressed是响应到返回键的时候调用的,里面有对Fragment的退栈处理。
所以,在实际使用中,得明确需求,你是想模拟back键呢还是想关闭activity,如果只是想关闭activity,直接调finish。自己开发中,有在网络接口回调中,想关闭activity,结果调用的是onBackPressed,结果出现两类错误,一类错误是,java.lang.IllegalStateException Can not perform this action after onSaveInstanceState
这是因为,在低版本上,onBackPressed做退栈处理的时候,没有想检查activity的state导致的,二类错误是java.lang.NullPointerException Attempt to invoke virtual method 'android.os.Handler android.app.FragmentHostCallback.getHandler()' on a null object reference
根本问题是,应该调用finish,却调用onBackPressed