As you may know, google released the support package, which exposed the new APIs only available for higher API level such as Fragment to make it also available for older platforms back to API level 4.
Using the support package, android.support.v4.app.FragmentManager, which is an android.app.FragmentManager compat, is available via FragmentActivity. This means, you must override FragmentActivity to use Fragment APIs.
These APIs which force the user to override some specific class has a disadvantage: You need some tricks to combine two APIs which both require overriding different classes unless the language you choose allow multiple inheritance. In such cases, perhaps you can avoid multiple inheritance by writing mixin style.
In case of using MapView in Fragment, you must override both MapActivity and FragmentActivity, which is impossible in Java.
A simple solution is: make FragmentActivity extend MapActivity.(android-support-v4-googlemaps does this.) This is a simple and safe fix in that - the diff from original is very small. But this means FragmentActivity also has restrictions which MapActivity has.
As documented here: Only one MapActivity is supported per process. So if you are to plan using multiple FragmentActivities, this restriction is really bad. Or otherwise, you can make app with a single FragmentActivity and implement all screens not with Activities but with Fragments.
For whom noticed the caveats in javadoc, this section may be useless. But I thought this would be useful for who wondering this weird error message:
E/ActivityThread( 1543): Activity com.example.android.apis.view.MapViewDemo has leaked IntentReceiver com.google.android.maps.NetworkConnectivityListener$ConnectivityBroadcastReceiver@333aa670 that was originally registered here. Are you missing a call to unregisterReceiver()?
E/ActivityThread( 1543): android.app.IntentReceiverLeaked: Activity com.example.android.apis.view.MapViewDemo has leaked IntentReceiver com.google.android.maps.NetworkConnectivityListener$ConnectivityBroadcastReceiver@333aa670 that was originally registered here. Are you missing a call to unregisterReceiver()?
E/ActivityThread( 1543): at android.app.ActivityThread$PackageInfo$ReceiverDispatcher.(ActivityThread.java:953)
E/ActivityThread( 1543): at android.app.ActivityThread$PackageInfo.getReceiverDispatcher(ActivityThread.java:748)
E/ActivityThread( 1543): at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:791)
E/ActivityThread( 1543): at android.app.ContextImpl.registerReceiver(ContextImpl.java:778)
E/ActivityThread( 1543): at android.app.ContextImpl.registerReceiver(ContextImpl.java:772)
E/ActivityThread( 1543): at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:318)
E/ActivityThread( 1543): at com.google.android.maps.NetworkConnectivityListener.startListening(MapActivity.java:163)
E/ActivityThread( 1543): at com.google.android.maps.MapActivity.onResume(MapActivity.java:431)
E/ActivityThread( 1543): at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1149)
E/ActivityThread( 1543): at android.app.Activity.performResume(Activity.java:3823)
E/ActivityThread( 1543): at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3211)
E/ActivityThread( 1543): at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3236)
E/ActivityThread( 1543): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2093)
E/ActivityThread( 1543): at android.os.Handler.dispatchMessage(Handler.java:99)
E/ActivityThread( 1543): at android.os.Looper.loop(Looper.java:123)
E/ActivityThread( 1543): at android.app.ActivityThread.main(ActivityThread.java:4735)
E/ActivityThread( 1543): at java.lang.reflect.Method.invokeNative(Native Method)
E/ActivityThread( 1543): at java.lang.reflect.Method.invoke(Method.java:521)
E/ActivityThread( 1543): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
E/ActivityThread( 1543): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:625)
E/ActivityThread( 1543): at dalvik.system.NativeStart.main(Native Method)
I made a branch experimental-fragment-activity-feature-impl to the android-support-v4-googlemaps project.
First, I categorized methods of FragmentActivity into two categories. One is inherited methods, and the other is original methods. For the original methods, I made a new interface FragmentActivityFeature and categorized as the features which are expected by other classes. The fields accessed by other classes are also included as their getter methods in the interface. Here is the list:
/*** * methods from the original FragmentActivity ***/ Object onRetainCustomNonConfigurationInstance(); Object getLastCustomNonConfigurationInstance(); void supportInvalidateOptionsMenu(); void onAttachFragment(Fragment fragment); FragmentManager getSupportFragmentManager(); void startActivityFromFragment(Fragment fragment, Intent intent, int requestCode); void invalidateSupportFragmentIndex(int index); LoaderManager getSupportLoaderManager(); LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create);/*** * getter for the fields of original FragmentActivity * which are accessed internally in the package. ***/ FragmentManagerImpl<?> getFragmentManagerImpl(); boolean isRetaining(); Handler getHandler();
Second, I replaced all the occurrence of FragmentActivity with FragmentActivityFeature and if there is an another requirement: the type also should be an Activity, I repleace the occurrence as parametrized type <FragmentActivityImpl extends Activity & FragmentActivityFeature>. This change made both FragmentManagerImpl and Fragment be a parametrized type.
But making public class like Fragment as a parameterized type is really ugly and uncomfortable, since it will change the usage and you cannot use it like a real one (android.app.Fragment) any more.
Fragment<?> fragment = new Fragment<?>();
It's sure that you can remove the wildcard type parameter here but you'll be warned by the compiler.
So I stopped using parameterized type <FragmentActivityImpl extends Activity & FragmentActivityFeature> and declaired the field mActivity in the class Fragment as an Activity. To keep the type safety, I make the field private and replaced all assignments with setter methods with the signature:
<FragmentActivityImpl extends Activity & FragmentActivityFeature> void setActivity(FragmentActivityImpl activity) { mActivity = activity; }
This will keep the type safety and you can always cast like
((FragmentActivityFeature)mActivity).getSupportLoaderManager();
safely.
For the getter, I declaired
@SuppressWarnings("unchecked") final public <FragmentActivityImpl extends Activity & FragmentActivityFeature> FragmentActivityImpl getActivity() { return (FragmentActivityImpl) mActivity; }
But you cannot call this method in older versions of JDK 6 as described here.
You can compile this without any problem with eclipse and JDK 7. So I think this is just a bug.
So I also made a patch, which makes the getter have more simple signature
Activity getActivity() { return mActivity; }
To use the features from FragmentActivityFeature, you can use the same cast,
((FragmentActivityFeature)getActivity()).getSupportLoaderManager();
which is used for the field mActivity internally as described above.
Here, I admit there will be some arguments - make the patched version as default and leave the experimental feature as the patch.
I did not make a proxy class to implement the ActivityFragmentFeature. Instead, I just copied FragmentActivity as FragmentMapActivity and made it extend MapActivity.
Although this may not seems to be prefereble, writing a proxy class is really hard in that the order of calling super method may affect its behaviour.
/** * Perform initialization of all fragments and loaders. */ @Override protected void onCreate(Bundle savedInstanceState) { mFragments.attachActivity(this); // Old versions of the platform didn't do this! if (getLayoutInflater().getFactory() == null) { getLayoutInflater().setFactory(this); }<b><font color="#0000FF">super</font></b><font color="#990000">.</font><b><font color="#000000">onCreate</font></b><font color="#990000">(</font>savedInstanceState<font color="#990000">);</font> <font color="#008080">NonConfigurationInstances</font> nc <font color="#990000">=</font> <font color="#990000">(</font>NonConfigurationInstances<font color="#990000">)</font> <b><font color="#000000">getLastNonConfigurationInstance</font></b><font color="#990000">();</font> <b><font color="#0000FF">if</font></b> <font color="#990000">(</font>nc <font color="#990000">!=</font> <b><font color="#0000FF">null</font></b><font color="#990000">)</font> <font color="#FF0000">{</font> mAllLoaderManagers <font color="#990000">=</font> nc<font color="#990000">.</font>loaders<font color="#990000">;</font> <font color="#FF0000">}</font> <b><font color="#0000FF">if</font></b> <font color="#990000">(</font>savedInstanceState <font color="#990000">!=</font> <b><font color="#0000FF">null</font></b><font color="#990000">)</font> <font color="#FF0000">{</font> <font color="#008080">Parcelable</font> p <font color="#990000">=</font> savedInstanceState<font color="#990000">.</font><b><font color="#000000">getParcelable</font></b><font color="#990000">(</font>FRAGMENTS_TAG<font color="#990000">);</font> mFragments<font color="#990000">.</font><b><font color="#000000">restoreAllState</font></b><font color="#990000">(</font>p<font color="#990000">,</font> nc <font color="#990000">!=</font> <b><font color="#0000FF">null</font></b> <font color="#990000">?</font> nc<font color="#990000">.</font>fragments <font color="#990000">:</font> <b><font color="#0000FF">null</font></b><font color="#990000">);</font> <font color="#FF0000">}</font> mFragments<font color="#990000">.</font><b><font color="#000000">dispatchCreate</font></b><font color="#990000">();</font>
}
This code is taken from FragmentActivity. Is it safe to move the line super.onCreate(savedInstanceState) to the first or to the end of the method? Should the code like
@Override protected void onCreate(Bundle savedInstanceState) { mActivityFragmentFeatureProxy.onCreate(savedInstanceState); super.onCreate(savedInstanceState); }
or
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mActivityFragmentFeatureProxy.onCreate(savedInstanceState); }
would work? In general, it's not. So I preferred the more simple way.
This means, MapView should not created more than once in a MapActivity. Do not create new instance at Fragment's onCreateView. So If you once create the instance, hold its reference and re-use it until the Activity's onDestroy.
ViewPager intercepts TouchEvents by overriding ViewGroup.onInterceptTouchEvent. So if you want to dispatch TouchEvents to the MapView, override this method.
@Override public boolean onInterceptTouchEvent(MotionEvent event) { if (shouldDispatchTouchEventsToTheMapView()) { return false; } return super.onInterceptTouchEvent(event); }