2012年5月23日水曜日

Android Support Package の Fragment から startActivityForResult() を使うときの注意点

今回は Support Package で Fragment を使う場合の注意点です。

まず、FragmentActivity で startActivityForResult() を使う場合、requestCode は 16bit 以下にしなければなりません。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/support/v4/java/android/support/v4/app/FragmentActivity.java#654
  1. 654     /** 
  2. 655      * Modifies the standard behavior to allow results to be delivered to fragments. 
  3. 656      * This imposes a restriction that requestCode be <= 0xffff. 
  4. 657      */  
  5. 658     @Override  
  6. 659     public void startActivityForResult(Intent intent, int requestCode) {  
  7. 660         if (requestCode != -1 && (requestCode&0xffff0000) != 0) {  
  8. 661             throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");  
  9. 662         }  
  10. 663         super.startActivityForResult(intent, requestCode);  
  11. 664     }  
このように、16bit 以上の場合は IllegalArgumentException が発行されるようになっています。

なぜこのような処理をしているかというと、requestCode を使って Activity から呼ばれた場合と、Fragment から呼ばれた場合を区別するようにしているからです。

requestCode が 0x0000ffff より小さい場合 → Activity から呼ばれた
requestCode が 0x0000ffff より大きい場合 → Fragment から呼ばれた

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/support/v4/java/android/support/v4/app/FragmentActivity.java#666
  1. 666     /** 
  2. 667      * Called by Fragment.startActivityForResult() to implement its behavior. 
  3. 668      */  
  4. 669     public void startActivityFromFragment(Fragment fragment, Intent intent,  
  5. 670             int requestCode) {  
  6. 671         if (requestCode == -1) {  
  7. 672             super.startActivityForResult(intent, -1);  
  8. 673             return;  
  9. 674         }  
  10. 675         if ((requestCode&0xffff0000) != 0) {  
  11. 676             throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");  
  12. 677         }  
  13. 678         super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));  
  14. 679     }  
さらに、どの Fragment から呼ばれたかも requestCode に反映されます。

ポイントは
  1. super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));  
ですね。

FragmentActivity では、自身が持っている Fragment にそれぞれ index が振られます。それが mIndex です。
つまり、Fragment から Fragment#startActivityForResult() を呼んだ場合、最終的に Activity#startActivityForResult() に渡される requestCode は、上位16ビットがその Fragment の index、下位16ビットが Fragment#startActivityForResult() に渡した requestCode になります。


で、どこではまるかというと、Fragment#startActivityForResult() から呼んだ Intent を FragmentActivity#onActivityResult() で受ける場合です。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/support/v4/java/android/support/v4/app/FragmentActivity.java#128
  1. 128     /** 
  2. 129      * Dispatch incoming result to the correct fragment. 
  3. 130      */  
  4. 131     @Override  
  5. 132     protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
  6. 133         int index = requestCode>>16;  
  7. 134         if (index != 0) {  
  8. 135             index--;  
  9. 136             if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) {  
  10. 137                 Log.w(TAG, "Activity result fragment index out of range: 0x"  
  11. 138                         + Integer.toHexString(requestCode));  
  12. 139                 return;  
  13. 140             }  
  14. 141             Fragment frag = mFragments.mActive.get(index);  
  15. 142             if (frag == null) {  
  16. 143                 Log.w(TAG, "Activity result no fragment exists for index: 0x"  
  17. 144                         + Integer.toHexString(requestCode));  
  18. 145             }  
  19. 146             frag.onActivityResult(requestCode&0xffff, resultCode, data);  
  20. 147             return;  
  21. 148         }  
  22. 149   
  23. 150         super.onActivityResult(requestCode, resultCode, data);  
  24. 151     }  
このように FragmentActivity#onActivityResult() では、requestCode の値を見て、それが Fragment から呼ばれたものなら、Fragment の onActivityResult() を呼んでいます。このときちゃんと requestCode の下位16ビットだけ渡しています。

Fragment#startActivityForResult() で呼んだ Intent を同じ Fragment の onActivityResult() で受け取っている場合には特に問題はないのですが、 Fragment#startActivityForResult() で呼んだ Intent を FragmentActivity の onActivityResult() で処理する場合には、渡された requestCode の下位16ビットだけを比較対象にする必要があります。

具体的に書くと、

  1. public class ActivityA extends FragmentActivity {  
  2.   
  3.     public static final int REQUEST_CODE_3 = 3;  
  4.     FragmentA mFragment;  
  5.       
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.   
  10.         mFragment = new FragmentA();  
  11.         getSupportFragmentManager().beginTransaction().add(R.id.container, mFragment).commit();  
  12.     }  
  13.       
  14.     @Override  
  15.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
  16.         super.onActivityResult(requestCode, resultCode, data);  
  17.           
  18.         // ここでは requestCode は 3 ではないので、下位16ビットを比較  
  19.         if(resultCode == RESULT_OK && (requestCode & 0xffff) == REQUEST_CODE_3) {  
  20.             // do something  
  21.         }  
  22.     }  
  23.   
  24.     class FragmentA extends Fragment {  
  25.         ...  
  26.   
  27.         private void openNewActivity() {  
  28.             Intent intent = new Intent(getActivity(), ActivityB.class);  
  29.             startActivityForResult(intent, ActivityA.REQUEST_CODE_3);  
  30.         }  
  31.   
  32.         @Override  
  33.         public void onActivityResult(int requestCode, int resultCode, Intent intent) {  
  34.             super.onActivityResult(requestCode, resultCode, intent);  
  35.             // ここでの requestCode は 3 = REQUEST_CODE_3  
  36.         }  
  37.     }  
  38. }  


このように、 ActivityA の中の FragmentA から requestCode = 3 で startActivityForResult() を呼んだ場合、FragmentA の onActivityResult() で渡される requestCode は 3 ですが、ActivityA の onActivityResult() で渡される requestCode は例えば 665539 のような値になります。

なので、Fragment から startActivityForResult() で呼んだ Intent を FragmentActivity の onActivityResult() で処理する場合は、下位16ビットだけで比較しましょう。




0 件のコメント:

コメントを投稿