0

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.

MiniW
  • 193
  • 13

1 Answers1

2

You may try to add on configChanges into your manifest to be defined in the activity<> it might solve your problem:

<activity>
android:name=.....
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize" />
</activity>

if it didn't work you may try to implement onConfigurationChanged() method to your activity.

AaoIi
  • 8,288
  • 6
  • 45
  • 87
  • I've read that this "fix" is a workaround, but is not suggested. There are plenty of reasons that makes an Activity restarts, such as a language change. I would have the same issues. I planned to handle several languages. http://stackoverflow.com/questions/7818717/why-not-use-always-androidconfigchanges-keyboardhiddenorientation – MiniW Sep 03 '14 at 16:10
  • @MiniW , Well ! you may add to android:configChanges="locale" to handle language change.i think this recourse might be helping : http://stackoverflow.com/questions/2644377/changing-locale-force-activity-to-reload-resources – AaoIi Sep 03 '14 at 16:28
  • If I have different layouts, how should I handle it? I guess it's in onConfigurationChanged(), but I'm not sure how to handle it effectively. – MiniW Sep 03 '14 at 16:39
  • @MiniW , hopefully someone more expert could help you dear ! thats what i can do for now..but i'll look for a solution :) – AaoIi Sep 03 '14 at 19:26