---------------------------------------------------------------------
解决在Fragment中启动Activity时传递请求码的问题:
首先需要指出的是Fragment有startActivityForResult方法,而Activity中也有startActivityForResult方法:
从官方的解释中可以看出Fragment A中的startActivityForResult方法是调用的是Activity 中的,而FragmentActivity A中的startActivityForResult方法是对Activity中的startActivityForResult进行了重写。所以我们调用Fragment A的startActivityForResult方法,当启动的Activity返回后,在FragmentActivity A的onActivityResult方法会被调用但无法获取到正确的请求码。如果调用FragmentActivity A中的startActivityForResult方法,当启动的Activity返回后Fragment的onActivityResult方法是不会被调用的。
总结:
如果要在Fragment中启动Activity并且要求返回结果,有两种结果方案:
第一种:调用Fragment A的startActivityForResult方法,然后在Fragment A的onActivityResult的方法中处理返回的请求。(如果FragmentActivity A重写了onActivityResult方法中,必须向下传递Result,即调用 super.onActivityResult(requestCode, resultCode, data) ,否则Fragment A收不到Result。)
@Override//FragmentActivity A
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);// 传递给Fragment A
if ( requestCode == TAKE_PICTURE && resultCode == RESULT_OK) {
}
}
第二种:在Fragment中通过getActivity()方法获取到Fragment所在的FragmentActivity对象,调用activity对象的startActivityForResult方法启动Activity,然后在FragmentActivity的onActivityResult的方法中处理返回的请求。
2解决Fragment A 中startActivityForResult调转Activity B时, Fragment A 中的onActivityResult先执行的问题
问题出现的情况:
当Fragment A跳转到下一个Activity B时,onActivityResult总是提前就接收到结果。发现,是调用的时候Activity B添加了new task标识(Activity B设置android:launchMode="singleTask" 也同样是启动了新Task),等是一个新的任务,所以每当跳转Activity B的时候Fragment A onActivityResult就会有结果。去掉new task标识就可以了。
在Fragment中调用startActivityForResult方法可以在onActivityResult方法中收到回应。
Fragment中startActivityForResult部分源码:
/**
* Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's
* containing Activity.
*/
public void startActivityForResult(Intent intent, int requestCode) {
if (mActivity == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
mActivity.startActivityFromFragment(this, intent, requestCode);
}
FragmentActivity中startActivityFromFragment部分源码: /**
* Called by Fragment.startActivityForResult() to implement its behavior.
*/
public void startActivityFromFragment(Fragment fragment, Intent intent,
int requestCode) {
if (requestCode == -1) {
super.startActivityForResult(intent, -1);
return;
}
if ((requestCode&0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
}
super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));
}
注:<<操作符表示当前值数的二进制左移16位,低位补0。对16进制来说,就是左移4位,低位补0。
方法中判断符合条件以后,在最后一句代码将Fragment在activity的index+1放入一个16进制int值的高四位,将requestCode截取低四位(已经限定requestCode不能大于0xffff)放入16进制int值的低四位,组成一个requestCode参数使用。FragmentActivity中startActivityFromFragment部分源码:
/**
* Dispatch incoming result to the correct fragment.
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mFragments.noteStateNotSaved();
int index = requestCode>>16;
if (index != 0) {
index--;
if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) {
Log.w(TAG, "Activity result fragment index out of range: 0x"
+ Integer.toHexString(requestCode));
return;
}
Fragment frag = mFragments.mActive.get(index);
if (frag == null) {
Log.w(TAG, "Activity result no fragment exists for index: 0x"
+ Integer.toHexString(requestCode));
} else {
frag.onActivityResult(requestCode&0xffff, resultCode, data);
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
首先将回调的requestCode值16进制右移四位取高四位值,如果不是0,表示这是Fragment调用startActivityForResult方法的回调,需要回调Fragment的onActivityResult方法。如果是0,则是当前activity调用的startActivityForResult方法(在此方法中已经限定requestCode不能大于0xffff),直接使用super.onActivityResult(requestCode, resultCode, data);交给父类处理。
在if语句块内,将requestCode拆分,16进制高四位表示Fragment的index+1,低四位是Fragment的requestCode,回调Fragment的onActivityResult方法。
流程结束。
另:当activity B设置启动模式为singleInstance时,调用startActivityForResult方法会直接回调onActivityResult方法,所以此时FragmentA调用startActivityFroResult方法也不好使。
api说明:Note that this method should only be used with Intent protocols that are defined to return a result. In other protocols (such asIntent.ACTION_MAIN or Intent.ACTION_VIEW), you may not get the result when you expect. For example, if the activity you are launching uses the singleTask launch mode, it will not run in your task and thus you will immediately receive a cancel result.
自从Android3.0引入了Fragment之后,使用Activity去嵌套一些Fragment的做法也变得更加流行,这确实是Fragment带来的一些优点,比如说:Fragment可以使你能够将activity分离成多个可重用的组件,每个都有它自己的生命周期和UI,更重要的是Fragment解决了Activity间的切换不流畅,实现了一种轻量及的切换,但是在官方提供的android.support.v4包中,Fragment还是或多或少的存在一些BUG,今天就与大家分享一下这些BUG和解决方法。
Case 1:当使用Fragment去嵌套另外一些子Fragment的时候,我们需要去管理子Fragment,这时候需要调用ChildFragmentManager去管理这些子Fragment,由此可能产生的Exception主要是:
java.lang.IllegalStateException: No activity
首先我们来分析一下Exception出现的原因:
通过DEBUG发现,当第一次从一个Activity启动Fragment,然后再去启动子Fragment的时候,存在指向Activity的变量,但当退出这些Fragment之后回到Activity,然后再进入Fragment的时候,这个变量变成null,这就很容易明了为什么抛出的异常是No activity
这个Exception是由什么原因造成的呢?如果想知道造成异常的原因,那就必须去看Fragment的相关代码,发现Fragment在detached之后都会被reset掉,但是它并没有对ChildFragmentManager做reset,所以会造成ChildFragmentManager的状态错误。
找到异常出现的原因后就可以很容易的去解决问题了,我们需要在Fragment被detached的时候去重置ChildFragmentManager,即:
@Override
public void onDetach() {
super.onDetach();
try {
Field childFragmentManager = Fragment.class
.getDeclaredField("mChildFragmentManager");
childFragmentManager.setAccessible(true);
childFragmentManager.set(this, null);
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
Case 2:当我们从一个Activity启动了一个Fragment,然后在这个Fragment中又去实例化了一些子Fragment,在子Fragment中去有返回的启动了另外一个Activity,即通过startActivityForResult方式去启动,这时候造成的现象会是,子Fragment接收不到OnActivityResult,如果在子Fragment中是以getActivity.startActivityForResult方式启动,那么只有Activity会接收到OnActivityResult,如果是以getParentFragment.startActivityForResult方式启动,那么只有父Fragment能接收(此时Activity也能接收),但无论如何子Fragment接收不到OnActivityResult。
这是一个非常奇怪的现象,按理说,应该是让子Fragment接收到OnActivityResult才对,究竟是什么造成的呢?这是由于某位写代码的员工抱怨没发奖金,稍稍偷懒了,少写了一部分代码,没有考虑到Fragment再去嵌套Fragment的情况。
我们来看看FragmentActivity中的代码:
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
this.mFragments.noteStateNotSaved();
int index = requestCode >> 16;
if (index != 0) {
index--;
if ((this.mFragments.mActive == null) || (index < 0) || (index >= this.mFragments.mActive.size())) {
Log.w("FragmentActivity", "Activity result fragment index out of range: 0x" + Integer.toHexString(requestCode));
return;
}
Fragment frag = (Fragment)this.mFragments.mActive.get(index);
if (frag == null) {
Log.w("FragmentActivity", "Activity result no fragment exists for index: 0x" + Integer.toHexString(requestCode));
}
else {
frag.onActivityResult(requestCode & 0xFFFF, resultCode, data);
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
很显然,设计者把Fragment的下标+1左移16位来标记这个request是不是Fragment的,拿到result再解码出下标,直接取对应的Fragment,这样并没有去考虑对Fragment嵌套Fragment做一个Map映射,所以出现了这种BUG。
但是如果我们需要在OnActivityResult的时候处理一些事情的话,我们可以通过在子Fragment中以getParentFragment.startActivityForResult的方式来启动,然后在父Fragment中去接收数据,我们需要在子Fragment中提供一个方法,如:getResultData(Object obj),通过父Fragment中的子Fragment实例去调用这个方法,把相应的数据传过去,然后去更新子Fragment。
以上是在使用Fragment去嵌套Fragment的时候可能会遇到的BUG,了解了BUG存在的原因之后,就可以完美的解决问题。