I’m trying to build a simple MusicPlayer and I am having trouble with orientation changes. I am using a service that handle the MediaPlayer so it plays in the background (even if the Activity is not in the foreground) like Google PlayMusic. The service is bound to my unique and single MainActivity. The problem is when the orientation changes for example, it destroys both the activity and the service (which is the expected behavior I guess).
I am not sure how to handle lifecycle effectively. When the orientations changes, it binds the service to the activity again. My MainActivity uses a ViewPager and a FragmentPagerAdapter to handle tabs and to update my views on events.
When it creates the activity again (from orientation changes), it binds the service again, perform "load" then try to update my view, but the fragment in pager adapter is null for some reasons. I don't know why. Ideally, I think I should load all my tracks once in the beginning of the application. Should I do that in the Activity or the service (because it is related to service)?
I am not sure how should I handle the life cycle of my service, activity and pager adapter.
I made a sample application that reproduce my errors:
public class MyActivity extends Activity {
private MediaPlayerService mediaPlayerService; private BroadcastReceiver broadcastReceiver; private MyPagerAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_my); ViewPager viewPager = (ViewPager) findViewById(R.id.pager); adapter = new MyPagerAdapter(getFragmentManager()); viewPager.setAdapter(adapter); broadcastReceiver = buildBroadcastReceiver(); Intent intent = new Intent(this, MediaPlayerService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } @Override protected void onDestroy() { if ( serviceBound ) { unbindService(connection); serviceBound = false; } super.onDestroy(); } @Override protected void onResume() { registerReceiver(broadcastReceiver, new IntentFilter(MediaPlayerService.BROADCAST)); super.onResume(); } @Override protected void onPause() { unregisterReceiver(broadcastReceiver); super.onPause(); } private boolean serviceBound = false; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { mediaPlayerService = ((MediaPlayerServiceBinder) iBinder).getService(); serviceBound = true; mediaPlayerService.load(); } @Override public void onServiceDisconnected(ComponentName componentName) { serviceBound = false; } }; private BroadcastReceiver buildBroadcastReceiver() { return new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int action = intent.getIntExtra(MediaPlayerService.ACTION, 0); if ( action == MediaPlayerService.ACTION_UPDATE_PLAYLIST ) { List<String> cities = Arrays.asList("New York", "Toronto", "Tokyo"); //Data would be load from Database here ListFragment fragment = adapter.getFragment(); fragment.updatePlayList(cities); // Fragment is NULL on Orientation changes!? } } }; } }
.
public class ListFragment extends Fragment {
private ArrayAdapter<String> adapter; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View inflatedView = inflater.inflate(R.layout.playlist, container, false); adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, new ArrayList<String>()); ListView listView = (ListView) inflatedView.findViewById(R.id.list_view); listView.setAdapter(adapter); return inflatedView; } public void updatePlayList(List<String> items) { adapter.clear(); adapter.addAll(items); adapter.notifyDataSetChanged(); } }
.
public class MediaPlayerService extends Service { public static final String BROADCAST = "example.com.mediaplayersample.Broadcast"; public static final int ACTION_UPDATE_PLAYLIST = 3; public static final String ACTION = "ACTION"; private MediaPlayer mediaPlayer; public void load() { //Load... Intent intent = new Intent(MediaPlayerService.BROADCAST); intent.putExtra(MediaPlayerService.ACTION, ACTION_UPDATE_PLAYLIST); sendBroadcast(intent); } @Override public IBinder onBind(Intent intent) { return new MediaPlayerServiceBinder(); } public class MediaPlayerServiceBinder extends Binder { public MediaPlayerService getService() { return MediaPlayerService.this; } }
}
.
public class MyPagerAdapter extends FragmentPagerAdapter {
private ListFragment listFragment; public MyPagerAdapter(FragmentManager fragmentManager) { super(fragmentManager); }; @Override public Fragment getItem(int i) { if (i == 0) { if (listFragment == null) { listFragment = new ListFragment(); return listFragment; } } throw new IllegalStateException("Wrong index " + i); } @Override public int getCount() { return 1; } public ListFragment getFragment() { return listFragment; } }
EDIT: I understand the approach to add a setting in the Manifest file as suggested:
<activity> android:name=.... android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" </activity>
However, it seems to be not recommanded. I planned to have several languages and different layouts and I don't think that refreshing all my views is the best approach.