I call this in an onClickListener
AsyncTaskBindService asyncTaskBindService = new AsyncTaskBindService(position, songList);
asyncTaskBindService.execute();
Asynctask class
public class AsyncTaskBindService extends AsyncTask<Void, Void, Void>{
private int songIndex;
private ArrayList<Song> songList;
public AsyncTaskBindService(int songIndex, ArrayList<Song> songList){
this.songIndex = songIndex;
this.songList = songList;
}
#Override
protected Void doInBackground(Void... voids) {
try {
if (getActivity() != null && !Main.mServiceIsBound) {
Main.bindMusicService(getActivity().getApplicationContext());
Log.i(TAG,"binding service!");
}
}catch (Exception e){
Log.e(TAG,"error binding Service!");
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
if (getActivity() != null && Main.mServiceIsBound) {
Main.mediaPlayerService.startActionPlay(getActivity().getApplicationContext(), songList, songIndex);
Log.i(TAG,"start song!");
}
}
Problem is in my doInBackground:
This line:
Main.bindMusicService(getActivity().getApplicationContext());
This method calls bindService in another class which then returns an instance to my Service.
But the problem is that Asynctask finished way to fast and when onPostExecute gets called it returns an NPE because mediaPlayerService is still null because onServiceConnected is slower than my Asynctask.
How to solve this?
Now i have to click a song 2 times before it starts playing.
EDIT
First i tried this, but my second if never gets called because it takes some time before onServiceConnected() is finished.
How can i immediately execute my second method if bindService is finished and my service is bound?
onClick Song
if (!Main.mServiceIsBound && getActivity() != null) {
Main.bindMusicService(getActivity().getApplicationContext());
Log.i(TAG,"Service is not bound yet!, binding Service...");
}
if (getActivity()!= null && Main.mServiceIsBound) {
Main.mediaPlayerService.startActionPlay(getActivity().getApplicationContext(), songList, position);
}
Main class
/**This establishes the connection to the MediaPlayerService. */
public static ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
MediaPlayerService.MusicBinder binder = (MediaPlayerService.MusicBinder)service;
mediaPlayerService = binder.getService();
mServiceIsBound = true;
Log.i("Main","MediaPlayerService Connected!");
}
#Override
public void onServiceDisconnected(ComponentName name) {
mServiceIsBound = false;
Log.i("Main","MediaPlayerService Disconnected!");
}
};
public static void bindMusicService(Context c){
/*mediaPlayerServiceIntent binds our connection to the MediaPlayerService. */
try{
mediaPlayerServiceIntent = new Intent(c, MediaPlayerService.class);
c.bindService(mediaPlayerServiceIntent, serviceConnection, Context.BIND_AUTO_CREATE);
}catch (Exception e){
Log.e("Main", "Service is not bound!");
}
Log.i("Main","Service is bound!");
}
That's because AsyncTasks doInBackGround run asynchronously, yet when you bind a component to a Service by using bindService that also runs asynchronously causing Main.bindMusicService to return immediately.
An AsyncTask is not a good candidate for this sort of work. You should be binding to a service in the main thread (UI thread) and listen for the onServiceConnected callback of the ServiceConnection object.
Related
Background
I am creating a service that syncs a local Realm database (stored on phone) with an online database. The database stores users and measurements.
I initialise the service by calling 'startService(new Intent(this, SyncService.class));' during the splash activity's onCreate() method, and specify in the manifest that the service should run for the length of the application.
The service has a broadcast receiver. When the receiver detects a network change from 'not connected' to 'connected', it fires a method called syncDatabases().
This method finds all measurements recorded locally after the timestamp of the last API callback, and sends them to the database. The API responds to a request by returning the object + a unique ID.
When a measurement is made whilst the device is offline, it is stored locally. When an internet connection is made, the syncDatabases() method should be called in order to update the online server with the local measurements.
My steps...
Steps when debugging the project:
With wifi I open the app and with an external device make a new measurement. This appears on both the app and in the database. I then turn wifi off and make another measurement - this appears on the device.
I attach the debugger.
I turn back on wifi and this triggers the services' receivers' onReceive() method. I step through this and it all works according to plan. I reach the syncDatabases() method, and from there I receive the callback from the API, and it then updates the Realm database with the new ID value.
The problem...
If I don't attach the debugger, nothing happens. The new measurements aren't pushed to the database, and none of my Log.e calls are printed.
Why is this happening? And is there an alternative solution / fix for this problem?
Code
Service class
public class SyncService extends Service {
private static final String TAG = "SYNCSERVICE";
private boolean mConnected = false;
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getExtras() != null) {
final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
final NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
if (netInfo != null) {
switch (netInfo.getState()) {
case CONNECTED:
if (!mConnected) {
Log.e("NETWORK", "Network " + netInfo.getTypeName() + " now connected");
syncDatabases();
mConnected = true;
}
break;
default:
mConnected = false;
break;
}
} else mConnected = false;
}
}
};
#Override
public void onCreate() {
super.onCreate();
initReceiver();
ConnectivityManager connectivityManager = (ConnectivityManager) getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager.getActiveNetworkInfo() != null) {
mConnected = true;
}
android.util.Log.e(TAG, "onCreate: SyncService created");
}
#Override
public void onDestroy() {
super.onDestroy();
unInitReceiver();
android.util.Log.e(TAG, "onDestroy: SyncService destroyed");
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
android.util.Log.e(TAG, "onBind: SyncService bound");
return null;
}
#Override
public boolean onUnbind(Intent intent) {
android.util.Log.e(TAG, "onUnbind: SyncService unbound");
return super.onUnbind(intent);
}
#Override
public void onRebind(Intent intent) {
super.onRebind(intent);
android.util.Log.e(TAG, "onRebind: SyncService rebound");
}
private void initReceiver() {
IntentFilter filters = new IntentFilter();
filters.addAction("android.net.wifi.WIFI_STATE_CHANGED");
filters.addAction("android.net.wifi.STATE_CHANGE");
registerReceiver(mReceiver, filters);
}
private void unInitReceiver() {
unregisterReceiver(mReceiver);
}
public void syncDatabases() {
RealmResults<UserDB> users = RealmDB.getInstance(getApplicationContext()).where(UserDB.class).findAll();
if (users.size() > 0) {
int userId = users.get(0).getmUserID();
Log.e("MESSAGE", PreferenceUtils.getInstance().getLastSyncDate());
Date lastSync = null;
SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.getDefault());
try {
lastSync = sdf.parse(PreferenceUtils.getInstance().getLastSyncDate());
}
catch (ParseException e) {
e.printStackTrace();
try {
lastSync = BaseFragment.FORMAT.parse(PreferenceUtils.getInstance().getLastSyncDate());
}
catch (ParseException e1) {
e1.printStackTrace();
}
}
if (lastSync != null) {
Date lastSyncOffset = new Date(lastSync.getTime() + 1000);
/** Get all local results which have been made after the last sync date
**/
RealmResults<MeasurementDB> newLocalMeasurements = RealmDB.getInstance(getApplicationContext())
.where(MeasurementDB.class).equalTo("user_ID", userId)
.greaterThan("dateCreated", lastSyncOffset)
.findAll();
/** For each measurement made after the last sync, add it to the server
**/
for (MeasurementDB measurement : newLocalMeasurements) {
TemperatureListener mListener = new TemperatureListener(measurement);
ApiRequest.getInstance(getApplicationContext()).registerNewMeasurement(measurement.getAverage(),
measurement.getDateCreated().toString(), mListener, mListener);
}
}
}
}
/**
* Temperature listener receives the local copy of the temperature item. onResponse can then
* directly mutate the object instead of searching local db
*/
private class TemperatureListener implements Response.Listener<Measurement>, Response.ErrorListener {
private MeasurementDB measurement;
public TemperatureListener(MeasurementDB measurement) {
this.measurement = measurement;
}
#Override
public void onErrorResponse(VolleyError error) {
Log.e("OnResponse", "Failure");
}
#Override
public void onResponse(Measurement response) {
Log.e("OnResponse", "Success");
/** Update our local measurement's ID value (supplied by server)
**/
RealmDB.getInstance(getApplicationContext()).beginTransaction();
measurement.setMeasurement_ID(response.getmMeasurementId());
RealmDB.getInstance(getApplicationContext()).commitTransaction();
/** Update the last sync date
**/
PreferenceUtils.getInstance().setLastSyncDate(response.getmDateCreated());
}
}
}
Initialisation of Service in splash activity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mTimedOut = true;
finishActivity();
}
}, DURATION);
/** Will sync application / cloud databases in background of app when network connected. **/
startService(new Intent(this, SyncService.class));
doApiWork();
}
Manifest entry
Stop with task to kill the service at the same time as the app.
Exported 'false' stops other apps from using the service.
<service
android:name=".network.SyncService"
android:stopWithTask="true"
android:enabled="true"
android:exported="false"/>
EDIT
I removed the service and left a receiver class, registered in the manifest, which triggers methods on another class when needed. However the receiver is only triggered in debug mode.
(NOTE that at the end of this Question I have an EDIT in which I have replaced one method with what the Answer said to do in order to fix the problem of onReceive never getting called and added onDestroy to fix a new problem that cropped up after fixing first problem.)
Here's how I attempted to capture the broadcast data, but onReceive never gets called since Log.w never displays anything:
public class MatchesActivity extends Activity implements DatabaseConnector.DatabaseProcessListener
{
public static String SOME_ACTION = "com.dslomer64.servyhelperton.SOME_ACTION";
public static String STRING_EXTRA_NAME = "match";
#Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
LocalBroadcastManager.getInstance(this).registerReceiver
(
new BroadcastReceiver()
{
#Override public void onReceive(Context context, Intent intent)
{
String s = txaMatches.getText().toString() + intent.getStringExtra(STRING_EXTRA_NAME) ;
txaMatches.setText(s);
Log.w("MatchesActivity","`````onReceive <" + s + ">");
}
}, new IntentFilter(SOME_ACTION)
);
...
DatabaseConnector dbc = new DatabaseConnector(getApplicationContext(), assets);
dbc.setDbProcesslistener(this); // set way to know matches has been defined
dbc.findDBMatches();
} // end onCreate
} // end MatchesActivity
Database connector:
public DatabaseConnector(Context _context, AssetManager _assets)
{
mContext = _context;
//This method, called in `MatchesActivity` on button press, does start the service:
public void findDBMatches()
{
Intent i= new Intent(mContext, QueryDB.class);
mContext.startService(i);
}
// Here's the service:
public static class QueryDB extends IntentService
{
public QueryDB() { super(QueryDB.class.getSimpleName()); }
public QueryDB(String name) { super(name); }
//Here's the procedure that does all the work (and it does execute):
#Override protected void onHandleIntent(Intent intent)
{ ...
publishProgress(dicWord); // a String
}
//This does execute but it doesn't send `progress` back to `MatchesActivity`,
//which initiated request for service (note: `publishProgress` is so named
//because `QueryDB` used to be an `AsyncTask` and I just didn't change the name):
protected void publishProgress(String progress)
{
Intent intent = new Intent(MatchesActivity.SOME_ACTION);
intent.putExtra(MatchesActivity.STRING_EXTRA_NAME, progress);
this.sendBroadcast(intent); // THIS LINE IS THE PROBLEM, FIXED BELOW
Log.w("DatabaseConnector", "`````publishProgress <" + progress + ">");
}
}
What connection(s) have I failed to make?
EDIT
This is the CORRECTED method found just above:
protected void publishProgress(String progress)
{
Intent intent = new Intent(MatchesActivity.SOME_ACTION);
intent.putExtra(MatchesActivity.STRING_EXTRA_NAME, progress);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
Here is onDestroy in MatchesActivity (which starts the service), necessary to call when service has finished its work:
#Override protected void onDestroy()
{
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
}
Note that onDestroy refers to a new MatchesIntent variable, defined as:
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver()
{
#Override public void onReceive(Context context, Intent intent)
{
String s = intent.getStringExtra(STRING_EXTRA_NAME) ;
txaMatches.append(s + "\n");
}
};
And onCreate in MatchesActivity got simpler because of defining mMessageReceiver:
#Override protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
LocalBroadcastManager.getInstance(this).registerReceiver
(
mMessageReceiver, new IntentFilter(SOME_ACTION)
);
}
What connection(s) have I failed to make?
In your first block of code, you are using LocalBroadcastManager. In your second block of code, you are not.
Replace:
this.sendBroadcast(intent);
with:
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
I am having a BroadcastReceiver in a Service.
I am registering receiver in service's oncreate() method like,
final MyReceiver myReceiver = new MyReceiver();
#Override
public void onCreate() {
IntentFilter filter = new IntentFilter(SOME_FILTER);
registerReceiver(myReceiver, filter);
}
Then I have created a method to unregister receiver and stop service like,
public void stopService(){
if(myReceiver != null){
try{
unregisterReceiver(myReceiver);
}catch(Exception e){
e.printStackTrace();
}
}
stopSelf();
}
But it does not stop the service. There is no exception, no error. Simply the service doesn't stop.
I have also tried it following way,
public void stopService(){
if(myReceiver != null){
try{
unregisterReceiver(myReceiver);
}catch(Exception e){
e.printStackTrace();
}
}
stopForeground(true);
stopSelf();
}
By doing this the Notification of Service being run in foreground hides but service's onDestroy() is not being called.
I also tried to put the code to unregister receiver in onDestroy() but after calling stopSelf() method onDestroy() is never called.
If I don't register the BroadcastReceiver in the service, everything works perfectly fine, service stops and onDestroy() gets called but when I register receiver then it doesn't stop.
Any ideas what's going on here?
Edit
This is how I am binding and starting the service
Intent serviceIntent = new Intent(MainActivity.this, MyService.class);
bindService(serviceIntent, mConnection, Context.BIND_AUTO_CREATE);
startService(serviceIntent);
And this is mConnection
MyService myService;
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service){
LocalBinder binder = (LocalBinder) service;
myService = binder.getService();
isBound = true;
}
#Override
public void onServiceDisconnected(ComponentName arg0) {
isBound = false;
}
};
Below is how I am unbinding the service and stopping it.
if(isBound){
unbindService(mConnection);
isBound = false;
}
myService.stopService();
myService = null;
While a Service is bound, it will never be destroyed.
http://developer.android.com/reference/android/app/Service.html
A service can be both started and have connections bound to it. In such a case, the system will keep the service running as long as either it is started or there are one or more connections to it with the Context.BIND_AUTO_CREATE flag. Once neither of these situations hold, the service's onDestroy() method is called and the service is effectively terminated. All cleanup (stopping threads, unregistering receivers) should be complete upon returning from onDestroy().
While it is perfectly OK to have a Service that is both, bound and started, it makes for iffy lifecycle management issues (as you've just found out). If possible, you should decide if you need loose or tight coupling of your Service and then use a started or bound Service respectively. This will make your life easier.
Just found a solution of the same by hit and trial method.
I shifted the code to unregister the receiver in onDestroy() method and all worked.
#Override
public void onDestroy() {
super.onDestroy();
if(myReceiver != null){
try{
unregisterReceiver(myReceiver);
}catch(Exception e){
e.printStackTrace();
}
}
}
I think problem was occurring because I was trying to unregister the receiver before stopping the service.
First of all, I'm quite new to the Android and JAVA world (coming from C/C++/Objective-C).
I'm trying to integrate the Android bump API (3.0, latest version) but I'm running into trouble.
I copied the exemple, it is working fine under Android 2.2, bump services are started correctly, but as for Android 3.0 and upper it does not works.
I've got an exception (the network on main thread one) when loading my activity, I know this exception and how to avoid it, but in this case, Bump state that they run their API in their own thread so I don't really know why I got it. They said that you don't need to run a thread or tasks.
Here is a sample of my Activity
public class BumpActivity extends Activity {
private IBumpAPI api;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.bump);
bindService(new Intent(IBumpAPI.class.getName()), connection,
Context.BIND_AUTO_CREATE);
IntentFilter filter = new IntentFilter();
filter.addAction(BumpAPIIntents.CHANNEL_CONFIRMED);
filter.addAction(BumpAPIIntents.DATA_RECEIVED);
filter.addAction(BumpAPIIntents.NOT_MATCHED);
filter.addAction(BumpAPIIntents.MATCHED);
filter.addAction(BumpAPIIntents.CONNECTED);
registerReceiver(receiver, filter);
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
private final ServiceConnection connection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder binder) {
Log.i("BumpTest", "onServiceConnected");
api = IBumpAPI.Stub.asInterface(binder);
try {
api.configure("API_KEY", "Bump User");
} catch (RemoteException e) {
Log.w("BumpTest", e);
}
Log.d("Bump Test", "Service connected");
}
#Override
public void onServiceDisconnected(ComponentName className) {
Log.d("Bump Test", "Service disconnected");
}
};
}
Sound like the problem occur during the connection service on the api.configure....
Should I run it in a separate thread or in it's own AsynchTask, but then how and why ?
I stuck on this problem for a day or so... and literarily 2 minutes after posting it here I resolved it...
I just put the api.configure on a separate thread (shorter than a AsynchTask).
private final ServiceConnection connection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder binder) {
Log.i("BumpTest", "onServiceConnected");
api = IBumpAPI.Stub.asInterface(binder);
new Thread() {
public void run() {
try {
api.configure("API_KEY",
"Bump User");
} catch (RemoteException e) {
Log.w("BumpTest", e);
}
}
}.start();
Log.d("Bump Test", "Service connected");
}
#Override
public void onServiceDisconnected(ComponentName className) {
Log.d("Bump Test", "Service disconnected");
}
};
Make request in background process.
The network on main thread one exception is happening in 2.2 and on 3.0 and above, the difference is that on 3.0 and above they force you to put everything that involves some heavy or slow ops in a different thread, as you said in an asyncTask.
You just have to create an inner asyncTask and on its onBackground method put your api.configure :)
class LoadBumpAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
try {
api.configure("9b17d663752843a1bfa4cc72d309339e", "Bump User");
} catch (RemoteException e) {
Log.w("BumpTest", e);
}
return null;
}
}
Just call new LoadBumpAsyncTask().execute() on your service connected and will work.
I'm having an Activity that is launching more instances of itself on user interaction. This activity binds to a service that maintains a bluetooth connection. Currently this is realized like this:
#Override
public void onResume()
{
super.onResume();
//bind the activity to the service when it is not bound yet.
if (!this.isBound)
{
//Application context, because the Connection shall be kept over configuration change, and the activity will be replaced then.
Intent serviceIntent = new Intent(this.getApplicationContext(), ConnectionService.class);
bindService(serviceIntent, this.connection, Context.BIND_AUTO_CREATE);
this.isBound = true;
}
else
{
this.connection = (ServiceConnection)getLastNonConfigurationInstance();
}
this.visible = true;
}
/**
* Saves the bound state.
*/
#Override
public void onSaveInstanceState(Bundle outState)
{
outState.putBoolean("isBound", this.isBound);
}
/**
* Loads the bound state.
*/
#Override
public void onRestoreInstanceState(Bundle inState)
{
this.isBound = inState.getBoolean("isBound");
}
/**
* Called by system, gives the current ServiceConnection to the system to be retrieved again after restart.
*/
#Override
public Object onRetainNonConfigurationInstance() {
return this.connection;
}
#Override
public void onPause()
{
//unbind the activity to the service when it is finishing.
super.onPause();
this.visible = false;
if (this.isFinishing() && this.isBound)
{
unbindService(this.connection);
this.isBound = false;
}
}
Now my problem is: When I change the orientation while the app is running, everything is okay unitl i press the back button. Then
07-05 12:07:03.039: E/ActivityThread(17850): Activity mm.android.prototype.uilayer.DatapointActivity has leaked ServiceConnection mm.android.prototype.uilayer.DatapointActivity$1#408bcbb8 that was originally bound here
07-05 12:07:03.039: E/ActivityThread(17850): android.app.ServiceConnectionLeaked: Activity mm.android.prototype.uilayer.DatapointActivity has leaked ServiceConnection mm.android.prototype.uilayer.DatapointActivity$1#408bcbb8 that was originally bound here
is written on the Log, and the app crashes in the onPause() Method at the unbind command. I've read the post of Dianne Hackborn in the google forum about the thing, that i can't rely on the ServiceConnection, but i didn't find any solution how to cope with it. Can you help me to get rid of this error and/or explain me why my app behaves like this?
I solved it by makeing the ServiceConnection static and reusing it in every instance of myActivity. Also i added a static counter that counts the instances, and unbinds if the last instance closes. This also solves the config change issue:
private boolean isBound = false;
private static int bindCount = 0;
// handles the service connection.
protected static ServiceConnection connection = new ServiceConnection(){
// ...
}
#Override
public void onResume()
{
super.onResume();
//bind the activity to the service when it is not bound yet.
if (!this.isBound)
{
if (bindCount == 0) {
//Application context, because the Connection shall be kept over configuration change, and the activity will be replaced then.
Intent serviceIntent = new Intent(this.getApplicationContext(), ConnectionService.class);
bindService(serviceIntent, DatapointActivity.connection, Context.BIND_AUTO_CREATE);
this.isBound = true;
}
bindCount++;
this.isBound = true;
}
this.visible = true;
}
/**
* Saves the bound state.
*/
#Override
public void onSaveInstanceState(Bundle outState)
{
outState.putBoolean("isBound", this.isBound);
}
/**
* Loads the bound state.
*/
#Override
public void onRestoreInstanceState(Bundle inState)
{
this.isBound = inState.getBoolean("isBound");
}
#Override
public void onPause()
{
//unbind the activity of the service when it is finishing.
super.onPause();
this.visible = false;
if (this.isFinishing())
{
if(bindCount <= 1 && this.isBound )
{
unbindService(DatapointActivity.connection);
}
bindCount--;
}
}