Hope you can help me, because I am almost sure that it can be done... but I don't seem to be able to do it.
I'm doing a task that can take very long (varies from a few seconds to a couple of hours), so I though of a service to make the task and an activity to start/stop/monitor. Organically similar to a MP3 player, which is why I think this to be possible.
However, when I rotate the screen, the activity is destroyed/created (this is not a problem), and so it is the service! (this is a problem, because the new service is a different service, with a different state). I want my service to survive the destruction of the activity (by the way, the current reproducible problem is rotation, but it may happen in any other moment, such as presing BACK in the activity or so).
1) Am I asking for somethi开发者_Python百科ng impossible? It is hard for me to believe it, because what I want is exactly what a music player wants...
2) if (impossible) { Any other idea? }
3) else { Any idea of what I am doing wrong? }
I would also apreciate something like "what you are doing is right", since that would tell me that the problem is in the code, and not in the concept.
Here is my pseudocode (android 2.2, api 8):
EDIT1: added "return START_NOT_STICKY", since it may be important...
// THE SERVICE
class MyService extends Service {
private MyInterface.Stub binder = new MyInterface.Stub() {
public int getProgress() {...}
public void doStop() {...}
}
onCreate() {
Log("MyService created");
}
onDestroy() {
Log("MyService destroyed");
doStop();
}
onBind() {
Log("MyService bound");
return binder;
}
onStartCommand() {
(new Thread() {
void run() {
while (!stop) {
//Do long task
}
stopSelf();
}
}).start();
return START_NOT_STICKY;
}
}
// THE ACTIVITY
class MyActivity extends Activity {
MyInterface _service;
ServiceConnection conn = new ServiceConnection() {
onServiceConnected(s) {
_service = MyInterface.Stub.asInterface(s);
}
onServiceDisconnected() {
_service = null;
}
}
onCreate() {
Log("MyActivity created");
bindService(MyService, conn);
}
onDestroy() {
Log("MyActivity destroyed");
unbindService(conn);
}
clickStart() {
startService(new Intent(this, MyService));
}
clickStop() {
_service.doStop();
}
clickProgress() {
_service.getProgress();
}
}
// THE MANIFEST
<application>
<activity name=".MyActivity">
<service name=".MyService" android:process=":remote" />
</application>
Edit2: More information. My pseudocode says:
class MyService extends Service {
...
}
However, it is an oversimplification, since it actually is:
class MyService extends MyBaseService {
...
}
class MyBaseService extends Service {
...
}
MyBaseService is in a different package and even in a different project (a "library project").
Then, if I do not start/bindto MyService, but MyBaseService (and change manifest acordingly), everything works as expected.
It is enough for me, since the behaviour implemented in MyService (in relation to MyBaseService) is easily coded into MyBaseService, and controlled via putExtra to the Intent which launches the service (although it breaks OOP, I don't mind).
However, I still can't find why this one works but the other does not. I will report if I progress further.
Edit3: fixed pseudocode (duplicated onCreate). But real code does not have it.
Edit4: a notice on the procedure:
1) Everything starts as expected.
2) The user clicks Start, so click_start() is run, the service is serviceStart()'ed.
3) The user changes orientation. MyActivyty.onDestroy() is called, causing unbindService().
4) The service is destroyed; MyService.onDestroy() is called.
5) The activity is re-created. MyActivity.onCreate() is called, causing bindService().
6) The service is re-created; MyService.onCreate() is called. State has been lost.
My problem is step 4 (6 is just a consecuence of 4).
Of course it's not impossible. As you already said, the music application also uses a service but doesn't get killed when rotating the phone.
The problem you encounter is the following. Your service is bound to your activity. Every time the screen orientation changes, Android destroys the current activity and recreates it immediately. This is the standard behavior of Android when it comes to configuration changes like the screen orientation. Obviously you service is tied to much to the activity so it is also destroyed.
You got multiple options to solve this problem.
You can prohibit orientation changes by setting the activities orientation to a fix value.
android:screenOrientation="portrait"
You can define that you will handle configuration changes for this activity yourself by adding the
android:configChanges="orientation"
attribute to the activity in the application manifest. When you do this you activity won't be destroyed an recreated, instead the method onConfigurationChanged(Configuration) will be called and you have to handle configuration changes yourself if you need to.Another way might be to store the connection to the service anywhere outside of the activity. I think you have to use the same service connection every time you communicate with your service. So if your activity is recreated after an orientation change then you also create a new connection and then you get an new service I guess. But I'm not 100% sure I only know this behavior from AsyncTasks.
Summary:
Based on the idea that the using the base service did work (see Edit2), I've rewriten it from scratch (watching the old code, but not copy-pasting), and now it works. My guesses:
1) The general concept was right. The pseudocode was right. But I did something different in the first and second writting. I'm still looking for the difference (maybe something that is made in a different order, or so; you can imagine that the real code is much more complicated that this simplified pseudocode). The difference must be there. Why I know it? Because I can test the new rewritting once and again, and it works everytime; however, if I test the old rewritting (which I carefully store in my HD), it fails once and again, everytime.
2) All of the ideas proposed by Flo and Nick were probably right: using getApplicationContext(), using a remote service (in a differente process) and using startForeground() will all of them tell the OS that this service is important, so it shouldn't be killed unless under severe lack of resources. However, I guess that the same mistake that I have made in my first writting makes their ideas fail as well.
Thanks to everybody for the help. I'll report back if I can find the difference.
You need to start it with startService
instead of bindService
(which you're doing), and also use Service.startForeground
to create an ongoing notification. You'll probably want a notification anyway to show details of the currently playing audio. As long as this notification is around, it keeps the service in the foreground and so isn't a candidate for killing.
精彩评论