Using Timer within an Android Service - android

I have a service that is going to act as an internal clock as part of another app, and it contains a Timer which runs every one second to update the time.
Problem is, when I close and re-open the app, the Service binds itself and all the code runs again in the service and then there are two Timers per second, and so on.
I've tried following the Local Service example, but the same thing happens http://developer.android.com/reference/android/app/Service.html#LocalServiceSample
Is it something to do with the bind/unbind of the service?
I'd like for just one process to run, so I'm looking for a way really to see if the process is running already, and if not then start it.
LocalService
`public class LocalService extends Service {
// This is the object that receives interactions from clients.
private final IBinder mBinder = new LocalBinder();
private long dateOfLastSync;
private long timeNow;
private final Handler handler = new Handler();
private static Timer timer = new Timer();
private Context ctx;
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
// The LocalBinder provides the getService() method for clients to retrieve the current instance of LocalService.
LocalService getService() {
Log.d("INTERNALCLOCK", "getService");
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
#Override
public void onCreate() {
Log.d("INTERNALCLOCK", "onCreate");
ctx = this;
startService();
}
#Override
public void onDestroy() {
Log.d("INTERNALCLOCK", "onDestroy");
Toast.makeText(this, "Service Stopped ...", Toast.LENGTH_SHORT).show();
}
private void startService()
{
timer.scheduleAtFixedRate(new mainTask(), 0, 1000);
}
private class mainTask extends TimerTask
{
public void run()
{
toastHandler.sendEmptyMessage(0);
}
}
public void getTimeFromServer() {
FetchServerTime fetchTime = new FetchServerTime();
fetchTime.execute();
}
#Override
public IBinder onBind(Intent intent) {
Log.d("INTERNALCLOCK", "onBind");
getTimeFromServer();
return mBinder;
}
/** method for clients */
public long getTimeNow() {
Log.d("INTERNALCLOCK", "getTimeNow");
return timeNow;
}
public void startTimeCounter() {
Log.d("INTERNALCLOCK", "startTimeCounter");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("INTERNALCLOCK", "onStartCommand");
return START_STICKY;
}
private final Handler toastHandler = new Handler()
{
#Override
public void handleMessage(Message msg)
{
timeNow += 1000;
Log.d("INTERNALCLOCK", "time is " + timeNow);
Toast.makeText(getApplicationContext(), "test", Toast.LENGTH_SHORT).show();
}
};
class FetchServerTime extends AsyncTask<Integer, String, Long> {
private String mUrl;
private Date date;
private AlarmManager mAlarmManager;
public FetchServerTime() {
mUrl = "XXX";
}
#Override
protected Long doInBackground(Integer... params) {
(get server time code)
}
#Override
protected void onPostExecute(Long result) {
super.onPostExecute(result);
Log.d("INTERNALCLOCK", "Server time on post execute: " + result);
timeNow = result;
dateOfLastSync = result;
}
}
`
InternalClockActivity
public class InternalClockActivity extends Activity implements CanUpdateTime {
private TextView timeLabel;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.d("INTERNALCLOCK", "onCreate - main");
timeLabel = (TextView) findViewById(R.id.time);
doBindService();
}
private LocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((LocalService.LocalBinder)service).getService();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
}
};
private boolean mIsBound;
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(InternalClockActivity.this,
LocalService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
#Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
public void updateTime(String time){
timeLabel.setText(time);
}
}

I just created some static variables to see if the timer was running, i.e.
private static boolean isDownloadTaskRunning = false;
private void startService()
{
if (!isDownloadTaskRunning) {
// Timers are not running already
// start timer to download date at intervals
timerToDownloadNewData.scheduleAtFixedRate(new DownloadDateTask(), 0, TIMES.FIFTEEN_MINS.getSeconds());
// set running to true
isDownloadTaskRunning = true;
}
}

Related

Resume music using Service when we Resume the application

I'm developing an application, which plays music in the background by using service.
Music stops when we hit back app will be paused and but, music is not resuming when I get back to the application.
public class backService extends Service implements ComponentCallbacks2 {
private MediaPlayer mp;
SharedPreferences sharedpreferences;
public Boolean musicSwitch;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
if (mp != null){
mp.stop();
mp.release();
}
}
#Override
public void onCreate() {
super.onCreate();
sharedpreferences = getSharedPreferences(mypreference,
Context.MODE_PRIVATE);
musicSwitch = sharedpreferences.getBoolean("music", true);
if(musicSwitch){
mp = MediaPlayer.create(this, R.raw.all);
mp.setLooping(true);
mp.start();
}
}
#Override
public void onTrimMemory(final int level) {
if (level == ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
if(mp != null){
mp.pause();
}
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
I want the application to resume music when we get back to the application, I have tried using onResume method, but there is no onResume method in services.
TIA
1) You need to create foreground service to prevent it from killing by OS
How can we prevent a Service from being killed by OS?
2) You can bind service (bindService(serviceIntent)) and use Binder interface
https://developer.android.com/guide/components/bound-services.html#Binder
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
Activity:
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
unbindService(mConnection);
mBound = false;
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
}
}
3) Then, in your activity onResume you can call method from your Binder to control music

service got stopped when the app goes background

Hi in project I'm using service for chat communication using SignalR. Chat communication is working fine but when the app goes to background the service got stopped I need to run the services fully till my app get deleted
Here is me service code
public class SignalRService extends Service {
private HubConnection mHubConnection;
private HubProxy mHubProxy;
private Handler mHandler; // to display Toast message
private final IBinder mBinder = new LocalBinder(); // Binder given to clients
public SignalRService() {
}
#Override
public void onCreate() {
super.onCreate();
mHandler = new Handler(Looper.getMainLooper());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
int result = super.onStartCommand(intent, flags, startId);
startSignalR();
return result;
}
#Override
public void onDestroy() {
Log.i("onDestroy","onDestroy");
mHubConnection.stop();
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
// Return the communication channel to the service.
startSignalR();
return mBinder;
}
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public SignalRService getService() {
// Return this instance of SignalRService so clients can call public methods
return SignalRService.this;
}
}
/**
* method for clients (activities)
*/
public void sendMessage(String message) {
String SERVER_METHOD_SEND = "Send";
mHubProxy.invoke(SERVER_METHOD_SEND, message);
}
/**
* method for clients (activities)
*/
public void sendMessage_To(String receiverName, String message) {
String SERVER_METHOD_SEND_TO = "SendChatMessage";
mHubProxy.invoke(SERVER_METHOD_SEND_TO, receiverName, message);
}
private void startSignalR() {
Platform.loadPlatformComponent(new AndroidPlatformComponent());
Credentials credentials = new Credentials() {
#Override
public void prepareRequest(Request request) {
request.addHeader("User-Name", "BNK");
}
};
String serverUrl = "http://10.10.10.180/signalr/hubs";
mHubConnection = new HubConnection(serverUrl);
mHubConnection.setCredentials(credentials);
String SERVER_HUB_CHAT = "ChatHub";
mHubProxy = mHubConnection.createHubProxy(SERVER_HUB_CHAT);
ClientTransport clientTransport = new ServerSentEventsTransport(mHubConnection.getLogger());
SignalRFuture<Void> signalRFuture = mHubConnection.start(clientTransport);
try {
signalRFuture.get();
} catch (InterruptedException | ExecutionException e) {
Log.e("SimpleSignalR", e.toString());
return;
}
sendMessage("Hello from BNK!");
String CLIENT_METHOD_BROADAST_MESSAGE = "broadcastMessage";
mHubProxy.on(CLIENT_METHOD_BROADAST_MESSAGE,
new SubscriptionHandler1<CustomMessage>() {
#Override
public void run(final CustomMessage msg) {
final String finalMsg = msg.UserName + " says " + msg.Message;
// display Toast message
mHandler.post(new Runnable() {
#Override
public void run() {
Log.i("message","message: "+finalMsg);
Toast.makeText(getApplicationContext(), finalMsg, Toast.LENGTH_SHORT).show();
}
});
}
}
, CustomMessage.class);
}}
And here is the activity code
public class MainActivity extends AppCompatActivity {
private final Context mContext = this;
private SignalRService mService;
private boolean mBound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent();
intent.setClass(mContext, SignalRService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
// Unbind from the service
Log.i("onStop","onStop");
if (mBound) {
unbindService(mConnection);
mBound = false;
}
super.onStop();
}
public void sendMessage(View view) {
if (mBound) {
// Call a method from the SignalRService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
EditText editText = (EditText) findViewById(R.id.edit_message);
EditText editText_Receiver = (EditText) findViewById(R.id.edit_receiver);
if (editText != null && editText.getText().length() > 0) {
String receiver = editText_Receiver.getText().toString();
String message = editText.getText().toString();
mService.sendMessage_To(receiver, message);
mService.sendMessage(message);
}
}
}
/**
* Defines callbacks for service binding, passed to bindService()
*/
private final ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to SignalRService, cast the IBinder and get SignalRService instance
SignalRService.LocalBinder binder = (SignalRService.LocalBinder) service;
mService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
Log.i("onServiceDisconnected","onServiceDisconnected");
mBound = false;
}
};}
My manifest code for service
<service
android:name=".SignalRService"
android:enabled="true"
android:exported="true" >
</service>
Plese help me on this
If you bind the service with any component the system will automatically destroy the service if no other client is bound with it.
If you want to run a service independently then you have to start a service rather than bind. But you can't communicate with a service if you start it with startService()
For more details you can see the documentation here
You can BOTH start AND bind the service.
In this way, even if multiple components bind to the service at once, then ALL of them unbind, the service will NOT be destroyed. Refer to A service can essentially take two forms: Bound
your service can work both ways: it can be started (to run indefinitely) and also allow binding. It's simply a matter of whether you implement a couple callback methods: onStartCommand() to allow components to start it and onBind() to allow binding.
// onBind method just return the IBinder, to allow clients to get service.
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// onStartCommand just return START_STICKY to let system to
// try to re-create the service if the servcie's process is killed.
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
// and make startSignalR public to allow client to call this method.
public void startSignalR() {
}
In your clients, no need to keep a boolean mBound.
Just bind service when onCreate, unbind service when onDestroy. DO NOT unbind when onStop. Since onStop may called many times, for example dialog popup will invoke onStop, but your activity is still on foreground, this will cause your service destroyed.
Refer to my answer for question: Pass Service From one Activity to Another for sample code.

Calling activity class method from Service class

I have seen many posts in SO regarding this but could not get the exact and most easy way to call an activity method from service class. Is broadcast receiver only the option? No easy way out ? I just need to call the following method in Activity class after the media player is prepared in Service class .
Activity class:
public void updateProgress() {
// set Progress bar values
songProgressBar.setProgress(0);
songProgressBar.setMax(100);
// Updating progress bar
updateProgressBar();
}
Service class:
#Override
public IBinder onBind(Intent intent) {
Log.d(this.getClass().getName(), "BIND");
return musicBind;
}
#Override
public boolean onUnbind(Intent intent) {
return false;
}
#Override
public void onPrepared(MediaPlayer mp) {
try {
mp.start();
} catch (IllegalStateException e) {
e.printStackTrace();
}
// updateProgress();// Need to call the Activity method here
}
Define an interface your Service will use to communicate events:
public interface ServiceCallbacks {
void doSomething();
}
Write your Service class. Your Activity will bind to this service, so follow the sample shown here. In addition, we will add a method to set the ServiceCallbacks.
public class MyService extends Service {
// Binder given to clients
private final IBinder binder = new LocalBinder();
// Registered callbacks
private ServiceCallbacks serviceCallbacks;
// Class used for the client Binder.
public class LocalBinder extends Binder {
MyService getService() {
// Return this instance of MyService so clients can call public methods
return MyService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return binder;
}
public void setCallbacks(ServiceCallbacks callbacks) {
serviceCallbacks = callbacks;
}
}
Write your Activity class following the same guide, but also make it implement your ServiceCallbacks interface. When you bind/unbind from the Service, you will register/unregister it by calling setCallbacks on the Service.
public class MyActivity extends Activity implements ServiceCallbacks {
private MyService myService;
private boolean bound = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(...);
}
#Override
protected void onStart() {
super.onStart();
// bind to Service
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from service
if (bound) {
myService.setCallbacks(null); // unregister
unbindService(serviceConnection);
bound = false;
}
}
/** Callbacks for service binding, passed to bindService() */
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
// cast the IBinder and get MyService instance
LocalBinder binder = (LocalBinder) service;
myService = binder.getService();
bound = true;
myService.setCallbacks(MyActivity.this); // register
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
bound = false;
}
};
/* Defined by ServiceCallbacks interface */
#Override
public void doSomething() {
...
}
}
Now when your service wants to communicate back to the activity, just call one of the interface methods from earlier. Inside your service:
if (serviceCallbacks != null) {
serviceCallbacks.doSomething();
}
Use Broadcast receiver with service for updating your view from the service class.
For example:
In my activity class
public class ServiceDemoActivity extends Activity {
Intent intent;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final TextView notification = (TextView) findViewById(R.id.notification);
if (CheckIfServiceIsRunning()) {
} else {
startService(new Intent(this, MyService.class));
}
}
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
updateDate(intent);
}
};
private void updateDate(Intent intent) {
String time = intent.getStringExtra("time");
Toast.makeText(getApplicationContext(), "Yea!!! Service called", Toast.LENGTH_SHORT).show();
TextView date = (TextView) findViewById(R.id.date);
date.setText(time);
}
#Override
public void onResume() {
super.onResume();
registerReceiver(broadcastReceiver, new IntentFilter(
MyService.BROADCAST_ACTION));
}
}
And in my service class I am calling my update ui after a few interval of time which updates my UI.
public class MyService extends Service {
public static final String
BROADCAST_ACTION = "com.mukesh.service";
private final Handler handler = new Handler();
#Override
public void onCreate() {
intent = new Intent(BROADCAST_ACTION);
}
#Override
public void onDestroy() {
stopService(intent);
}
#Override
public void onStart(Intent intent, int startid) {
int i = 0;
while (i <= 2) {
if (i > 1) {
i++;
this.onDestroy();
} else {
counter = i;
i++;
handler.removeCallbacks(sendUpdatesToUI);
handler.postDelayed(sendUpdatesToUI, 1 * 1000); // 1 sec
}
}
}
private Runnable sendUpdatesToUI = new Runnable() {
public void run() {
DisplayLoggingInfo();
handler.postDelayed(this, 7 * 1000); // 7 sec
}
};
private void DisplayLoggingInfo() {
intent.putExtra("time", new Date().toLocaleString());
intent.putExtra("counter", String.valueOf(counter));
sendBroadcast(intent);
stopService(intent);
}
}
For complete code check this link
I created a general class called Delegate (it's not a special name, you can name it John) and passed MainActivity class into it as a static field. Then I can access it from the service since its global now. I am not sure if it is cost-effective but it solved the problem for me simple.
My service:
package com.some.package;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.FirebaseInstanceIdService;
public class FirebaseInstanceIDService extends FirebaseInstanceIdService {
#Override
public void onTokenRefresh() {
String token = FirebaseInstanceId.getInstance().getToken();
Delegate.theMainActivity.onDeviceTokenChange(token);
}
}
Delegate class:
package com.some.package;
public class Delegate {
static MainActivity theMainActivity;
}
What I did in MainActivity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Delegate.theMainActivity = this;
//rest of the code...
}
public void onDeviceTokenChange(String token){
Log.e("updated token:", token);
}
You can't call your sevices method direcly from your activity or vise versa. There are 3 ways to communicate with a service; using broadcasters and receivers, using Messenger or binding to the service. For further information look at http://developer.android.com/guide/components/bound-services.html
You can call from your service
getContentResolver().notifyChange(uri, null);
and in your activity you set up a
getContentResolver().registerContentObserver(uri, false, new ContentObserver(getHandler())
{
public void onChange(boolean selfChange)
{
updateProgress()
}
};
the onChange method will ba called on the UI thread
You can call a method of activity from service by implementing your own listener like this
https://stackoverflow.com/a/18585247/5361964
You might consider running your activity method in runOnUiThread like this:
// method will be called from service
override fun callback(activity: Activity, result: String) {
runOnUiThread{
Toast.makeText(activity, result, Toast.LENGTH_LONG).show()
}
}
I would prefer to use some very easy and cleaner solution provided by
EventBus

Update service running on background

I need to implement such a procedure:
Start a background service
Update the service with parameters (from UI - user input)
After activity ended the service should keep on running and preform requests to HTTP server every minute. in this stage i still need the parameters I updated in the second stage - I send them to the server.
The service should store the server last response and compere each with the last. if there is a change, notify the user.
Finally, when the activity starts again, the service should update UI with latest the server response.
What I tried:
BroadcastReciver - The problem is after onRecive ended all the arguments which aren't declared as final will wipe out, as well as I didn't found a way to update the Intent being sent automatically every minute.
Service - Using startService() - The problem is when the activity ended the service like stops and starts , flushing all it's arguments. and once again I didn't figured out how to update the arguments after the service is already started.
So how to handle such a situation?
Thanks.
It sounds like what you need to do is to be able to "bind" to your service. What I have posted below is a simple template of how to do that. For your purposes you will need to store variables in your Service class and create getters so that when you re-launch your activity you can get the most up to date variables. Also - please note that I start and stop the Service example below in onResume and onPause. You will no doubt want to do this differently.
//Activity
//Bind to Service Example
public class ExampleActivity extends Activity implements OnClickListener {
// UI
private Button binderButton;
// service
private MyService myService;
private Intent serviceIntent;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.your_layout);
// binder button
binderButton = (Button) findViewById(R.id.button1);
binderButton.setOnClickListener(this);
binderButton.setText("start");
serviceIntent = new Intent(this, MyService.class);
}
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
myService = ((MyService.MyBinder) service).getService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
myService = null;
}
};
#Override
protected void onResume() {
super.onResume();
// start the service
startService(serviceIntent);
// bind to the service
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
// call method within the service
myService.doServiceStuff();
break;
}
}
#Override
protected void onPause() {
super.onPause();
stopService(serviceIntent);
unbindService(serviceConnection);
}
}
//Service
public class MyService extends Service {
private final IBinder binder = new MyBinder();
#Override
public IBinder onBind(Intent arg0) {
return binder;
}
public void doServiceStuff() {
task.execute();
}
// create an inner Binder class
public class MyBinder extends Binder {
public MyService getService() {
return MyService.this;
}
}
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
Log.d("yourTag", "long running service task");
return null;
}
};
}
Thanks javaJoe, although your answer didn't solved my problem it gave me some a good ideas.
What I did:
in the Activity onCreate, check if my service is running, if so bind it else, create new one and bind it.
Transferring arguments between the Service and the Activity using setters and getters.
in the Activity onDestroy (the problem was that the service calls self Destory) the Activity sends the final arguments through Intent to a Broadcastreciver. The Broadcastreciver than starts the Service again, initiating it with the correct arguments.
I don't know if this architecture is ideal, i'd like to get some feedback.
Here is the code:
Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Set Service Intent
serviceIntent = new Intent(this, UpdateService.class);
if (isMyServiceRunning()) {
//Bind to the service
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}else{
updateService=new UpdateService();
//Start the service
startService(serviceIntent);
//Bind to the service
bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}
}
private boolean isMyServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (UpdateService.class.getName().equals(service.service.getClassName())) {
return true;
}
}
return false;
}
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
updateService = ((UpdateService.MyBinder) service).getService();
//Set Initial Args
updateService.setParams(int arg0);
}
#Override
public void onServiceDisconnected(ComponentName name) {
updateService = null;
}
};
#Override
protected void onDestroy() {
//UnBind from service
unbindService(serviceConnection);
//Stop Service
stopService(serviceIntent);
//Prepare intent to broadcast reciver
Intent intent = new Intent(MainActivity.this,ServiceRunnerBCR.class);
intent.setAction(ServiceRunnerBCR.ACTION_SET_UpdateService);
intent.putExtra(ServiceRunnerBCR.keyVal_arg0, arg0);
intent.putExtra(ServiceRunnerBCR.keyVal_arg1, arg1);
//Send broadcast to start UpdateService after the activity ended
sendBroadcast(intent);
super.onStop();
}
Broadcastreciver:
public class ServiceRunnerBCR extends BroadcastReceiver {
public static final String ACTION_SET_UpdateService = "ACTION_ALARM";
public static final String keyVal_arg0="ARG0";
public static final String keyVal_arg1="ARG1";
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_SET_UpdateService)){
updateIntent(context, intent.getDoubleExtra(keyVal_arg0, 0.02), intent.getStringExtra(keyVal_arg1));
}
}
private void updateIntent(Context context, double arg0, String arg1){
Intent intent = new Intent(context,UpdateService.class);
intent.setAction(ACTION_SET_UpdateService);
intent.putExtra(keyVal_arg0, arg0);
intent.putExtra(keyVal_arg1, arg1);
synchronized (this){
try {
this.wait(6000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
context.startService(intent);
Log.d("OREN","ServiceRunner");
}
}
Service:
public class UpdateService extends Service {
private final IBinder binder = new MyBinder();
public static final String keyVal_arg0="ARG0";
public static final String keyVal_arg1="ARG1";
private Timer timer;
private HTTPHandler http = new HTTPHandler();
private int test=0;
double arg0=0;
String arg1= "";
private TimerTask updateTask = new TimerTask() {
#Override
public void run() {
test++;
Log.d("OREN", "Timer task doing work " + test + " arg0: " + arg0);
//Do some work here
}
};
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent!=null){
arg0=intent.getDoubleExtra(keyVal_arg0, 0.002);
arg1=intent.getStringExtra(keyVal_arg1);
timer = new Timer("UpdateTimer");
timer.schedule(updateTask, 1000L, 10 * 1000L);
Log.d("OREN", "ServiceStarted" + test);
}
return super.onStartCommand(intent, flags, startId);
}
#Override
public IBinder onBind(Intent intent) {
Log.d("OREN", "OnBind" + test);
return binder;
}
public void setArg0(double d){
arg0=d;
}
// create an inner Binder class
public class MyBinder extends Binder {
public UpdateService getService() {
return UpdateService.this;
}
}
#Override
public void onDestroy() {
Log.d("OREN", "OnDestroy" + test);
super.onDestroy();
}
#Override
public boolean onUnbind(Intent intent) {
Log.d("OREN", "OnUnBind" + test);
return super.onUnbind(intent);
}
}

ServiceConnection.onServiceConnected() never called after binding to started service

In a game application I have the following scenario:
From the main game Activity, the player starts several game tasks that run in the background with varying duration.
The player should be able to view the progress of the running game tasks in a separate View.
To do this, I created two Activitys and a Service, defined as follows:
Service ProgressService handles several ProgressBars running simultaneously on parallel threads.
Activity WorkScreen2 creates a game task, starts the Service with startService() with task parameters passed in a Bundle.
Activity ProgressScreen binds to the Service to get and display the ProgressBars of the running tasks.
Both activities run under separate TabHosts of one TabActivity.
The problem I'm having is that the ServiceConnection.onServiceConnected() method is never called. I get a Java.lang.NullPointerException because I try to call a method of the Service object that should be assigned in this method. See code below.
I use getApplicationContext().bindService() to bind the Activity to the Service because TabSpec cannot bind to Services. This method returns true. Therefore, binding is successful.
Here is the Service:
public class ProgressService extends Service implements GameConstants {
public static final String BROADCAST_PROGRESS = "com.mycompany.android.mygame.progressbroadcast";
private static final long UPDATE_INTERVAL = 500;
private IBinder mBinder;
private List<ProgressBar> mProgressBarList;
private List<String> mStaffNameList;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
#Override
public void handleMessage(Message msg) {
ProgressBar progressBar = new ProgressBar(ProgressService.this);
mProgressBarList.add(progressBar);
Bundle bundle = msg.getData();
String staffName = bundle.getString(WorkScreen2.STAFF_NAME);
mStaffNameList.add(staffName);
int taskDurationMillis = bundle.getInt(WorkScreen2.TASK_DURATION) * 1000;
progressBar.setMax(taskDurationMillis / 1000);
long startTimeMillis = SystemClock.uptimeMillis();
long elapsedTimeMillis = SystemClock.uptimeMillis()
- startTimeMillis;
Intent intent = new Intent();
intent.setAction(BROADCAST_PROGRESS);
while (elapsedTimeMillis < taskDurationMillis) {
try {
Thread.sleep(UPDATE_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
elapsedTimeMillis = SystemClock.uptimeMillis()
- startTimeMillis;
int elapsedTimeSeconds = (int) elapsedTimeMillis / 1000;
progressBar.setProgress(elapsedTimeSeconds);
sendBroadcast(intent);
}
progressBar.setVisibility(View.GONE);
mProgressBarList.remove(progressBar);
mStaffNameList.remove(staffName);
sendBroadcast(intent);
if (mProgressBarList.isEmpty()) {
stopSelf(msg.arg1);
}
}
}
#Override
public void onCreate() {
super.onCreate();
mBinder = new ProgressServiceBinder();
mProgressBarList = Collections
.synchronizedList(new ArrayList<ProgressBar>());
mStaffNameList = Collections.synchronizedList(new ArrayList<String>());
}
/*
* Creates a thread for each game task with parameters passed in
* <code>intent</code>
*/
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "starting service", Toast.LENGTH_LONG).show();
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
Handler serviceHandler = new ServiceHandler(thread.getLooper());
Message msg = serviceHandler.obtainMessage();
msg.arg1 = startId;
msg.setData(intent.getExtras());
serviceHandler.sendMessage(msg);
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public class ProgressServiceBinder extends Binder {
ProgressService getService() {
return ProgressService.this;
}
}
public List<ProgressBar> getProgressBarList() {
return mProgressBarList;
}
public List<String> getStaffNameList() {
return mStaffNameList;
}
#Override
public void onDestroy() {
Toast.makeText(this, "Service done", Toast.LENGTH_SHORT).show();
}
}
And this is the Activity that binds to it:
public class ProgressScreen extends ListActivity {
private final String TAG = "ProgressScreen";
private ProgressScreenAdapter mAdapter;
private ProgressService mProgressService;
private List<ProgressBar> mProgressBarList;
private List<String> mStaffNameList;
#Override
public void onCreate(Bundle bundle) {
Log.i(TAG, "ProgressScreen oncreate");
super.onCreate(bundle);
setContentView(R.layout.progress_screen_layout);
IntentFilter filter = new IntentFilter();
filter.addAction(ProgressService.BROADCAST_PROGRESS);
registerReceiver(receiver, filter);
doBindService();
mAdapter = new ProgressScreenAdapter(this, mStaffNameList, mProgressBarList);
setListAdapter(mAdapter); // Returns true
/*
* This is where I get the NullPointerException
* mProgressService is null here
*/
mProgressBarList = mProgressService.getProgressBarList();
mStaffNameList = mProgressService.getStaffNameList();
}
#Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(ProgressService.BROADCAST_PROGRESS);
registerReceiver(receiver, filter);
}
#Override
protected void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
boolean doBindService() {
return getApplicationContext().bindService(new Intent(this, ProgressService.class), mConnection, Context.BIND_AUTO_CREATE);
}
void doUnbindService() {
getApplicationContext().unbindService(mConnection);
}
ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
mProgressService = ((ProgressService.ProgressServiceBinder) binder).getService();
Toast.makeText(ProgressScreen.this, "Connected to ProgressService", Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName name) {
mProgressService = null;
}
};
private BroadcastReceiver receiver = new BroadcastReceiver () {
#Override
public void onReceive(Context context, Intent intent) {
mAdapter.notifyDataSetChanged();
}
};
}
And the Service is started from the main Activity as follows:
Intent intent = new Intent(WorkScreen2.this, ProgressService.class);
intent.putExtra(TASK_DURATION, task.getDuration());
intent.putExtra(STAFF_NAME, staff.getName());
startService(intent);
The AndroidManifest.xml contains
<service
android:name=".ProgressService"
android:label="#string/progress_service">
</service>
ServiceConnection's onServiceConnected() is called, but nobody guarantees that it will be called before onCreate continues execution. So, what happens here - you successfuly bind to the service (that's why onBind returns true), but you're not fully connected - onServiceConnected() has not yet been called, so your local mProgressService object is not yet initalized, and therefore you get the NullPointerException.
Solution:
Move these two lines:
mProgressBarList = mProgressService.getProgressBarList();
mStaffNameList = mProgressService.getStaffNameList();
from onCreate() to onServiceConnected() function (use the service object after it is initialized in onServiceConnected()).
Check AndroidManifest.xml of yours and add service that you tried to bind.
You have to return your Binder inner class from
private final IBinder mBinder = new ServiceBinder();
public class ServiceBinder extends Binder {
public PlayerActivity getService() {
return PlayerActivity.this;
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}

Categories

Resources