Reputation: 1117
I have an Activity and an Intent Service communicating with each other. The Service generates 100 random floats and sends them to the Activity via a Messenger with a 1 second delay. It also updates a notification with % progress.
This is working beautifully until the phone is rotated. When the phone is rotated the UI no longer updates (The newest random float isn't displayed). I have debugged and found that random floats are still being generated and sent to the Activity. The Activity is even calling tv.setText("New random number: " + random); but the new random number is not being display.
Any ideas?
My Service
package ie.cathalcoffey.android.test;
import java.util.Random;
import com.jakewharton.notificationcompat2.NotificationCompat2;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.NotificationCompat;
import android.widget.Toast;
public class MyService extends IntentService
{
boolean stop = false;
// Used to receive messages from the Activity
final Messenger inMessenger = new Messenger(new IncomingHandler());
// Use to send message to the Activity
private Messenger outMessenger;
class IncomingHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
Bundle data = msg.getData();
stop = data.getBoolean("stop", true);
}
}
public MyService ()
{
super("MyServerOrWhatever");
}
public MyService(String name)
{
super(name);
}
NotificationManager mNotificationManager;
Notification notification;
Random r;
@Override
public IBinder onBind(Intent intent)
{
Bundle extras = intent.getExtras();
if (extras != null)
{
outMessenger = (Messenger) extras.get("messenger");
}
return inMessenger.getBinder();
}
@Override
public void onCreate()
{
super.onCreate();
Toast.makeText(this, "My Service Created", Toast.LENGTH_LONG).show();
r = new Random();
mNotificationManager = (NotificationManager) getSystemService(getApplicationContext().NOTIFICATION_SERVICE);
NotificationCompat2.Builder mBuilder =
new NotificationCompat2.Builder(this)
.setSmallIcon(R.drawable.ic_launcher)
.setContentTitle("My notification")
.setContentText("Hello World!")
.setProgress(100, 0, false)
.setContentIntent(PendingIntent.getActivity(getApplicationContext(), 0, new Intent(getApplicationContext(), TestServiceActivity.class), PendingIntent.FLAG_ONE_SHOT));
notification = mBuilder.build();
startForeground( 42, notification );
}
@Override
public void onDestroy()
{
super.onDestroy();
Toast.makeText(this, "My Service Stopped", Toast.LENGTH_LONG).show();
}
@Override
public void onStart(Intent intent, int startid)
{
super.onStart(intent, startid);
stop = false;
Toast.makeText(this, "My Service Started", Toast.LENGTH_LONG).show();
}
@Override
protected void onHandleIntent(Intent intent)
{
for(int i = 1; i < 100; i++)
{
if(stop)
{
break;
}
notification.contentView.setProgressBar(android.R.id.progress, 100, i, false);
mNotificationManager.notify(42, notification);
try
{
Message backMsg = Message.obtain();
Bundle bundle = new Bundle();
bundle.putFloat("randomFloat", r.nextFloat());
backMsg.setData(bundle);
outMessenger.send(backMsg);
Thread.sleep(1000);
}
catch (Exception e)
{
}
}
}
}
My Activity
package ie.cathalcoffey.android.test;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class TestServiceActivity extends Activity implements OnClickListener
{
Messenger messenger = null;
private Handler handler = new Handler()
{
public void handleMessage(Message message)
{
Bundle data = message.getData();
float random = data.getFloat("randomFloat");
TextView tv = (TextView)findViewById(R.id.textView1);
tv.setText("New random number: " + random);
}
};
private ServiceConnection conn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
messenger = new Messenger(binder);
}
public void onServiceDisconnected(ComponentName className) {
messenger = null;
}
};
private static final String TAG = "ServicesDemo";
Button buttonStart, buttonStop;
@Override
protected void onResume()
{
// TODO Auto-generated method stub
super.onResume();
Intent intent = null;
intent = new Intent(this, MyService.class);
// Create a new Messenger for the communication back
// From the Service to the Activity
Messenger messenger = new Messenger(handler);
intent.putExtra("messenger", messenger);
bindService(intent, conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onPause()
{
super.onPause();
unbindService(conn);
}
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
buttonStart = (Button) findViewById(R.id.buttonStart);
buttonStop = (Button) findViewById(R.id.buttonStop);
buttonStart.setOnClickListener(this);
buttonStop.setOnClickListener(this);
}
@Override
public void onClick(View src)
{
switch (src.getId())
{
case R.id.buttonStart:
startService(new Intent(this, MyService.class));
break;
case R.id.buttonStop:
stopService(new Intent(this, MyService.class));
Message msg = Message.obtain();
try
{
Bundle bundle = new Bundle();
bundle.putBoolean("stop", true);
msg.setData(bundle);
messenger.send(msg);
}
catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}
Upvotes: 1
Views: 3544
Reputation: 1365
Your IntentService class doesn't need a Handler class...it inherits its own handler method called
protected void onHandleIntent(Intent arg0)
Upvotes: 1
Reputation: 6715
Ok, so this was pretty instructive.
It turns out that as long as that Service stays around, its onBind method is not called a second time, even though you unbind and rebind. Instead, if you return true from unbind, rebind gets called.
Notice that if you wait until the service has stopped flinging randoms, rotate it, and then start it, it works just fine. That's because onBind is called after onDestroy/onCreate.
I got far enough so that I see what's going on: I didn't actually solve the problem. I think, though, that if you implement onRebind, you might get this to work.
Btw, I would change a couple of other things in this code: I would make those Handlers static, if possible. If it were me, I'd also make the Activity the ServiceConnection, too.
Good luck!
Edited to add:
I couldn't stand it. I checked. Alas, the intent delivered in onRebind is NOT the one sent in the second call to bindService. I don't know how to make this work.
Edited to add
This is still pretty much a hack, but I think it does what the original code intends:
https://github.com/bmeike/WeirdServer.git
Upvotes: 1
Reputation: 41
The original Activity which started the IntentService off gets killed/recreated when the screen rotates. So the service is busy sending floats to the old zombie Activity, not the new instance which is the rotated one on your screen. That's also a memory leak of the zombie.
IntentService doesn't have an easy way around this issue - I might suggest shifting to a Bound or vanilla Started Service with the Activity binding/unbinding to the service in it's onCreate/onDestroy calls.
If Gingerbread support isn't necessary, a Fragment allows you to call setRetainInstance to keep itself alive even if the surrounding Activity gets killed/recreated. That seems an easier change with only some GUI refactoring to do.
Upvotes: 1