タブ・アクティビティはSectionsPagerAdapterを利用したタブによるページ移動を指しています。
移動する各ページを個別のFragmentにし、そのうちの1のFragmentでSupportMapFragment(GoogleMap)を利用したのですが、SupportMapFragment#getMapAsyncを呼び出すタイミングとページ移動時にエラーが発生する事象にハマりました。
上記を回避するための方法を記載します。(これで合っているのかなーという気がしますが...)
まずはタブ・アクティビティでページごとに個別のfragmentを表示する
プロジェクト作成時にタブ・アクティビティを選択すると、MainActivityはAppCompatActivityを継承して作成されます。
Activityのinner classとして定義されるSectionsPagerAdapter#getItemで表示する画面のfragmentを生成しますが、このうちの1つでSupportMapFragmentを利用することにしました。
※UserMapFragmentとします。
public class MainActivity extends AppCompatActivity
・
・
・
中略
・
・
public class SectionsPagerAdapter extends FragmentPagerAdapter {
@Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a TestResultFragment (defined as a static inner class below).
Fragment fragment = null;
// ここで選択されたTabの番号に合わせてFragmentを生成
switch (position) {
case 0:
fragment = UserMapFragment.newInstance(); // Mapを利用するFragment
break;
case 1:
fragment = YyyyFragment.newInstance();
break;
default:
fragment = ZzzzzFragment.newInstance();
break;
}
return fragment;
}
fragmentのonStartでSupportMapFragment#getMapAsyncを実行する。
UserMapFragmentのonStartでgetMapAsyncします。
SupportMapFragment#getMapAsyncするにはActivityが必要になるので、getActivityで取得して渡します。(これでいいのか...その1)
public class UserMapFragment extends Fragment
@Override
public void onStart() {
// Activityの取得とMapの生成
MainActivity activity = (MainActivity) this.getActivity();
if (activity.mMap == null) {
// SupportMapFragmentを取得
SupportMapFragment mapFragment = (SupportMapFragment) this.getChildFragmentManager().findFragmentById(R.id.fragment_map);
mapFragment.getMapAsync(activity);
}
super.onStart();
}
そしてgetMapAsyncのcallbackとListenerはActivity側に実装します。
OnMapReadyCallbackはFragmantにimplements出来ない為です。
(これでいいのか...その2)
// implements OnMapReadyCallback, GoogleMap.OnCameraIdleListenerを追加
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback, GoogleMap.OnCameraIdleListener {
// Mapの定義
public GoogleMap mMap;
/**
* SupportMapFragment#getMapAsyncのcallback
*
* @param googleMap
*/
@Override
public void onMapReady(GoogleMap googleMap) {
// Mapの生成
}
@Override
public void onCameraIdle() {
// Mapを移動したときの処理を実装
}
ページ移動した際にExceptionが発生するのを防ぐ
これでタブ移動したページにGoogleMapが表示されるのですが、何回かページを移動しているとGoogleMapのあるFragmentのonCreateViewでExceptionが発生します。
厳密なエラーの発生理由は調べていないのですが、どうもページ移動に伴いSupportMapFragment#getMapAsyncが2回以上実行されるとExceptionが発生するようです。
getMapAsyncを複数回実行させないことで対応したいのですが、タブを利用したページ移動は、表示されていない隣のページを同時に生成し、隣のページ以外を破棄してるようです。
SupportMapFragmentを持つFragmentがページ移動により破棄され、再度キャッシュされるときにエラーが発生するようです。
これはページの移動時にSectionsPagerAdapter#getItemが2回実行されるので確認できます。
Tab position:0 ← ここを表示(Fragmentを生成)すると、position:1 もFragmentを生成する
Tab position:1
Tab position:2 ← ここを表示(Fragmentを生成)すると、position:3 のFragmentを生成し、position:0のFragmentを破棄する。
Tab position:3
Fragmentが破棄されないようにすることで回避しました。(これでいいのか...その3)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ----------- Tabの初期化
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
// setOffscreenPageLimitに3(Tabの総数)を設定
mViewPager.setOffscreenPageLimit(3);
setOffscreenPageLimitでキャッシュするページ数が決められるので、3=全てのページをキャッシュすることでページの破棄が発生しなくなりました。