WorkManager onStopped() method called twice - android

I have implemented WorkManager in my project. After finishing my task in doWork(), I've called stop(true); And I've sending a Broadcast to another activity in onStopped(). Everything is working fine but the problem is onStopped() is being called twice (I've tested by applying breakpoints). As a result the BroadcastReceiver is also called twice.
I know that the WorkManager is in alpha stage. But I'm not actually sure if this is a bug or I am doing something wrong. Please help. Codes are as follows:
public Result doWork() {
startGettingData();
stop(true);
return Result.SUCCESS;
}
#Override
public void onStopped(boolean cancelled) {
super.onStopped(cancelled);
Intent intent=new Intent(SERVER_SYNC_BROADCAST);
intent.putExtra(SYNC_RESULT_MESSAGE,responseCodes);
LocalBroadcastManager.getInstance(MyApplication.getContext()
.getApplicationContext())
.sendBroadcast(intent);
}
Calling from activity's onCreate() just once:
WorkUtil.startSyncing(SyncWorker.class);
WorkUtil.java
public class WorkUtil {
private static WorkManager mWorkManager;
public static WorkUtil workUtil;
private WorkUtil() {
mWorkManager = WorkManager.getInstance();
}
public static WorkUtil getInstance() {
if(workUtil == null) {
workUtil = new WorkUtil();
}
return workUtil;
}
public static void startSyncing(Class workerClass) {
Constraints constraints = new Constraints.Builder().build();
OneTimeWorkRequest someWork = new OneTimeWorkRequest.Builder(workerClass)
.setConstraints(constraints)
.build();
OneTimeWorkRequest oneTimeWorkRequest = someWork;
mWorkManager.enqueue(oneTimeWorkRequest);
}
public static void cancelAllWork() {
mWorkManager.cancelAllWork();
}
}

If I understand you correctly, you want to send a broadcast when your work is done.
You shouldn't do it inside onStopped, move it to the doWork like this:
public Result doWork() {
startGettingData(); // this method should be synchronous
Intent intent=new Intent(SERVER_SYNC_BROADCAST);
intent.putExtra(SYNC_RESULT_MESSAGE,responseCodes);
LocalBroadcastManager.getInstance(MyApplication.getContext()
.getApplicationContext())
.sendBroadcast(intent);
return Result.SUCCESS;
}
The onStopped method is not meant to be called when the work is done, but when it's stopped or canceled.
Source

Related

Android WorkManager not starting the worker

I want to download certain files from the server at the start of the app .So I tried using Work Manager which gets enqueued from my custom Application class.But the Worker class is not getting triggered and the state is only ENQUEUED and not going to RUNNING.Below is my code:
Application class:
#Override
public void onCreate() {
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
//.setRequiresStorageNotLow(true)
//.setRequiresBatteryNotLow(true)
.build();
OneTimeWorkRequest request = new OneTimeWorkRequest
.Builder(MyWorker.class)
//.setConstraints(constraints)
//.setInitialDelay(1,TimeUnit.SECONDS)
.addTag("download")
.build();
WorkManager.getInstance(getApplicationContext()).getWorkInfoByIdLiveData(request.getId()).observeForever(new Observer<WorkInfo>() {
#Override
public void onChanged(WorkInfo workInfo) {
if (workInfo == null) {
Log.d("download", "workInfo == null");
} else {
Log.d("download", "workInfo != null: " + workInfo.getState().toString());//This is giving ENQUEUED once..thats it
}
}
});
WorkManager.getInstance(getApplicationContext()).enqueue(request);
}
MyWorker class
public class MyWorker extends Worker {
public MyWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
//Some file to download
}
}
build.gradle:
implementation "androidx.work:work-runtime:2.3.2"
Things i have tried:
I tried adding an interval and also remove constraints, but then also, MyWorker is not triggered.
I have seen many SOF posts as given below but it didn't help me:
Worker manager: not start work in enqueue
Why workers in work manager still in ENQUEUED state?
Android WorkManager doesn't trigger one of the two scheduled workers

NetworkOnMainThreadException OR OnErrorNotImplementedException RxAndroid send/receive data to/from bound service

I am using the updated version of this answer to link (bind) an activity to a service.
MyService.java
public class MyService extends Service {
private LocalBinder binder = new LocalBinder();
private Observable<Integer> responseObservable;
private ObservableEmitter<Integer> responseObserver;
public static boolean isRunning = false;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return binder;
}
#Override
public int onStartCommand(#Nullable Intent intent, int flags, int startId) {
GsonConverterFactory factory = GsonConverterFactory.create(new GsonBuilder()
.setLenient()
.create());
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Client client = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build())
.addConverterFactory(factory)
.build()
.create(Client.class);
for (//some loop) {
Response<Result> response = client.search(//some params here)
.execute();
responseObserver.onNext(response.code());
}
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
isRunning = false;
}
public Observable<Message> observeResponse() {
if (responseObservable == null) {
responseObservable = Observable.create(em -> responseObserver = em);
responseObservable = responseObservable.share();
}
return responseObservable;
}
public class LocalBinder extends Binder {
public DService getService() {
return MyService.this;
}
}
}
MainActivity.java
public class MainActivityextends AppCompatActivity {
private MyService service;
private Disposable disposable;
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
service = ((MyService.LocalBinder) iBinder).getService();
disposable = service.observeResponse()
.observeOn(Schedulers.newThread())
.subscribe(responseCode -> updateUI()); //this must run on main thread
startService(new Intent(MainActivity.this, MyService.class));
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
#Override
protected void onDestroy() {
if (disposable != null)
disposable.dispose();
unbindService(serviceConnection);
super.onDestroy();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
//....
Button start = findViewById(R.id.start);
start.setOnClickListener(v -> {
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
});
//....
}
}
If I use observeOn(AndroidSchedulers.mainThread()), I get NetworkOnMainThreadException, and if I use observeOn(Schedulers.newThread()), I get OnErrorNotImplementedException: Only the original thread that created a view hierarchy can touch its views.
I know what both errors mean, in normal cases I can resolve them easily, but here nothing I normally do work.
I need the network requests in the service to be executed synchronously because it is in a loop, and I treat each request result in order, asynchronous calls are not an option for me.
I tried runOnUiThread(() -> updateUI()), but it produces the same error. I also tried to execute the service on a new thread, but still the same error too.
First of Service runs on Main Thread
A service runs in the main thread of its hosting process; the service
does not create its own thread and does not run in a separate process
unless you specify otherwise. If your service is going to perform any
CPU-intensive work or blocking operations, such as MP3 playback or
networking, you should create a new thread within the service to
complete that work. By using a separate thread, you can reduce the
risk of Application Not Responding (ANR) errors, and the application's
main thread can remain dedicated to user interaction with your
activities. REFERENCE
So, making api calls in Service directly will cause NetworkOnMainThreadException in all cases.
When you put observeOn(AndroidSchedulers.mainThread()), you are definitly bound to have NetworkOnMainThreadException; reason specified above
When you put observeOn(Schedulers.newThread()), the api call in service causes NetworkOnMainThreadException; but since you have used Rx it returns an error message to its subscriber; but in your case, you have not added error part.
You have used:
subscribe(responseCode -> updateUI());
to prevent app crash, you have to use
subscribe(responseCode -> updateUI(), error -> error.printStackTrace());
Now to fix the issue:
In service, make sure to call API on new Thread in Service;
OR
You can also try to make API call using reference to another class(like Presenter in MVP), where you make API calls and send response to UI directly using :
service.observeResponse()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(responseCode -> view.updateUI(), error -> view.displayError())

Worker manger doWork() method can't be executed, but onStartWork() is called multiple times

I am trying to schedule work when app start after 1 mint I need do some work it does not matter app still in the foreground or not but I need to do this take, but I don't know why my doWork method not run.but onStart mathod called multiple time
private void sacduleThePaddingNotifaction() {
OneTimeWorkRequest postNotationWithDelay =
new OneTimeWorkRequest.Builder(PostNotificationWork.class)
.setInitialDelay(1,TimeUnit.MINUTES)
.build();
WorkManager.getInstance().enqueue(postNotationWithDelay);
}
My worker class
public class PostNotificationWork extends Worker {
#Override
public void onStartWork(#NonNull WorkFinishedCallback callback) {
Log.d("PostNotificationWork: ","onStartWork");//this called many times, some time 4 time
}
#NonNull
#Override
public Result doWork() {
Log.d("PostNotificationWork: ","Posting notification");
return Result.SUCCESS;
}
}
i am using def work_version = "1.0.0-alpha07"
Try to update to this version
def work_version = "1.0.0-alpha06"
implementation "android.arch.work:work-runtime:$work_version"
Remove
#Override
public void onStartWork(#NonNull WorkFinishedCallback callback) {
Log.d("PostNotificationWork: ","onStartWork");//this called many times, some time 4 time
}
Finally Try again
Please do not override onStartWork(). Even if you do you need to call super.onStartWork(). We are still working on the ergonomics of the NonBlockingWorker API.

Android: get result from a service

I have android service which I need to return a result after it has been started.
So:
1) I start the service
2) The service does something
3) The service returns a result
I thought that maybe I can use a singleton class to take the result. I created a singleton class in order to make the service save the result in it and then take the result from the service by an activity. Here there's the code of the class:
public class Result {
//SIGLETON DECLARATION
private static Result mInstance = null;
public static Result getInstance() {
if (mInstance == null) {
mInstance = new Result();
}
return mInstance;
}
private Result() {
}
//CODE
Object result;
boolean resultIsSet = false;
public void deletePreviousResult() {
this.result = null;
resultIsSet = false;
}
public void setResult(Object result) {
Log.w("result", "setResult");
this.result = result;
resultIsSet = true;
}
public Object getResult() {
Log.w("result", "getResult");
while(resultIsSet == false) {}
return this.result;
}
Here's the code that I use to get the result:
public int getInfo() {
Result.getInstance().deletePreviousResult();
//start the service and ask to save the result in singleton class
Intent myIntent = new Intent(mInstanceContext, MediaPlayerService.class);
myIntent.setAction("getInfo");
mInstanceContext.startService(myIntent);
//take the result
return (Integer) Result.getInstance().getResult();
}
What is doing in onStartCommand is:
Result.getInstance().setResult(mMediaPlayer.getCurrentPosition());
where mMediaPlayer is a MediaPlayer object.
However the problem is that if setResult is also supposed to be called in onStartCommand, it is never called! Why? Is it my approach wrong?
This code you have in getInfo():
mInstanceContext.startService(myIntent);
//take the result
return (Integer) Result.getInstance().getResult();
assumes that when you call startService() the service is started and onStartCommand() is called synchronously so that in the next statement you can get the result using Result.getInstance().getResult().
Unfortunately, it doesn't work that way. Calling startService() isn't like calling a method on an object. What you do when you call startService() is that you tell Android that you want that service to be started at the next available moment. Since onStartCommand() in your service needs to be called on the main thread, usually this means that Android will start your service and call onStartCommand() at the next time when Android gets control of the main thread (ie: when all your activity methods have returned). In any case, you can't determine when the service will be started or when onStartCommand() will be called. It is an asynchronous call, so you need to rely on a callback mechanism. This means that you need to write some kind of callback method that the service can call when it has done the work and wants to return the result to you.
There are various ways to do this. You could have the service send an Intent to your activity. You could have the service broadcast an Intent with the results. You could bind to the service and register a callback listener (see developer documentation for bound services). There are probably other ways too.

Android how do I wait until a service is actually connected?

I have an Activity calling a Service defined in IDownloaderService.aidl:
public class Downloader extends Activity {
IDownloaderService downloader = null;
// ...
In Downloader.onCreate(Bundle) I tried to bindService
Intent serviceIntent = new Intent(this, DownloaderService.class);
if (bindService(serviceIntent, sc, BIND_AUTO_CREATE)) {
// ...
and within the ServiceConnection object sc I did this
public void onServiceConnected(ComponentName name, IBinder service) {
Log.w("XXX", "onServiceConnected");
downloader = IDownloaderService.Stub.asInterface(service);
// ...
By adding all kinds of Log.xx I found that the code after if(bindService(...)) actually goes BEFORE ServiceConnection.onServiceConnected is being called - that is, when downloader is still null - which gets me into trouble. All the samples in ApiDemos avoid this timing problem by only calling services when triggered by user actions. But what should I do to right use this service after bindService succeeds? How can I wait for ServiceConnection.onServiceConnected being called reliably?
Another question related. Are all the event handlers: Activity.onCreate, any View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. actually called in the same thread (mentioned in the doc as the "main thread")? Are there interleaves between them, or Android would schedule all events come into being handled one-by-one? Or, When exactly is ServiceConnection.onServiceConnected actually going to be called? Upon completion of Activity.onCreate or sometime when A.oC is still running?
How can I wait for
ServiceConnection.onServiceConnected
being called reliably?
You don't. You exit out of onCreate() (or wherever you are binding) and you put you "needs the connection established" code in onServiceConnected().
Are all the event handlers:
Activity.onCreate, any
View.onClickListener.onClick,
ServiceConnection.onServiceConnected,
etc. actually called in the same
thread
Yes.
When exactly is
ServiceConnection.onServiceConnected
actually going to be called? Upon
completion of Activity.onCreate or
sometime when A.oC is still running?
Your bind request probably is not even going to start until after you leave onCreate(). Hence, onServiceConnected() will called sometime after you leave onCreate().
I had the same problem. I didn't want to put my bound service dependent code in onServiceConnected, though, because I wanted to bind/unbind with onStart and onStop, but I didn't want the code to run again every time the activity came back to the front. I only wanted it to run when the activity was first created.
I finally got over my onStart() tunnel vision and used a Boolean to indicate whether this was the first onServiceConnected run or not. That way, I can unbindService in onStop and bindService again in onStart without running all the start up stuff each time.
I ended up with something like this:
1) to give the auxiliary stuff some scope, I created an internal class. At least, the ugly internals are separated from the rest of the code. I needed a remote service doing something, therefore the word Something in class name
private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
//...
}
2) there are two things necessary to invoke a remote service method: the IBinder and the code to execute. Since we don't know which one becomes known first, we store them:
private ISomethingService mISomethingService;
private Runnable mActionRunnable;
Each time we write to one of these fileds, we invoke _startActionIfPossible():
private void _startActionIfPossible() {
if (mActionRunnable != null && mISomethingService != null) {
mActionRunnable.run();
mActionRunnable = null;
}
}
private void performAction(Runnable r) {
mActionRunnable = r;
_startActionIfPossible();
}
This, of course, assumes that the Runnable has access to mISomethingService, but this is true for runnables created within the methods of the RemoteSomethingHelper class.
It is really good that the ServiceConnection callbacks are called on the UI thread: if we are going to invoke the service methods from the main thread, we do not need to care about synchronization.
ISomethingService is, of course, defined via AIDL.
3) Instead of just passing arguments to methods, we create a Runnable that will invoke the method with these arguments later, when invocation is possible:
private boolean mServiceBound;
void startSomething(final String arg1) {
// ... starting the service ...
final String arg2 = ...;
performAction(new Runnable() {
#Override
public void run() {
try {
// arg1 and arg2 must be final!
mISomethingService.startSomething(arg1, arg2);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
4) finally, we get:
private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
private ISomethingService mISomethingService;
private Runnable mActionRunnable;
private boolean mServiceBound;
private void _startActionIfPossible() {
if (mActionRunnable != null && mISomethingService != null) {
mActionRunnable.run();
mActionRunnable = null;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
// the methods on this class are called from the main thread of your process.
#Override
public void onServiceDisconnected(ComponentName name) {
mISomethingService = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
mISomethingService = ISomethingService.Stub.asInterface(service);
_startActionIfPossible();
}
}
private void performAction(Runnable r) {
mActionRunnable = r;
_startActionIfPossible();
}
public void startSomething(final String arg1) {
Intent intent = new Intent(context.getApplicationContext(),SomethingService.class);
if (!mServiceBound) {
mServiceBound = context.getApplicationContext().bindService(intent, mServiceConnection, 0);
}
ComponentName cn = context.getApplicationContext().startService(intent);
final String arg2 = ...;
performAction(new Runnable() {
#Override
public void run() {
try {
mISomethingService.startSomething(arg1, arg2);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
context is a field in my class; in an Activity, you can define it as Context context=this;
I did not need queuing actions; if you do, you can implement it.
You likely will need a result callback in startSomething(); I did, but this is not shown in this code.
I did something similar before, the only different is I was not binding to service, but just starting it.
I would broadcast an intent from the service to notify the caller/activity about it is started.
I wanted to add some things you should or should not do:
bind the service not on create but onResume and unbind it onPause. Your app can go into pause (background) at any time by user interaction or OS-Screens.
Use a distinct try/catch for each and every service unbinding, receiver unregistering etc in onPause so if one is not bound or registered the exception doesn't prevent the others from being destroyed too.
I usually capsule binding in a public MyServiceBinder getService() Method. I also always use a blocking boolean variable so I don't have to keep an eye on all those calls using the servie in the activity.
Example:
boolean isBindingOngoing = false;
MyService.Binder serviceHelp = null;
ServiceConnection myServiceCon = null;
public MyService.Binder getMyService()
{
if(serviceHelp==null)
{
//don't bind multiple times
//guard against getting null on fist getMyService calls!
if(isBindingOngoing)return null;
isBindingOngoing = true;
myServiceCon = new ServiceConnection(
public void onServiceConnected(ComponentName cName, IBinder binder) {
serviceHelp = (MyService.Binder) binder;
//or using aidl: serviceHelp = MyService.Stub.AsInterface(binder);
isServiceBindingOngoing = false;
continueAfterServiceConnect(); //I use a method like this to continue
}
public void onServiceDisconnected(ComponentName className) {
serviceHelp = null;
}
);
bindService(serviceStartIntent,myServiceCon);
}
return serviceHelp;
}
Android 10 has introduced a new bindService method signature when binding to a service to provide an Executor (which can be created from the Executors).
/**
* Same as {#link #bindService(Intent, ServiceConnection, int)} with executor to control
* ServiceConnection callbacks.
* #param executor Callbacks on ServiceConnection will be called on executor. Must use same
* instance for the same instance of ServiceConnection.
*/
public boolean bindService(#RequiresPermission #NonNull Intent service,
#BindServiceFlags int flags, #NonNull #CallbackExecutor Executor executor,
#NonNull ServiceConnection conn) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
This allows to bind to the service in a thread and wait until it is connected. E.g. stub:
private final AtomicBoolean connected = new AtomicBoolean()
private final Object lock = new Object();
...
private void myConnectMethod() {
// bind to service
ExecutorService executorService = Executors.newSingleThreadExecutor();
context.bindService(new Intent(context, MyServiceClass.class), Context.BIND_AUTO_CREATE, executorService, new
ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder binder) {
synchronized (lock) {
// TODO: store service instance for calls in case of AIDL or local services
connected.set(true);
lock.notify();
}
});
synchronized (lock) {
while (!connected.get()) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
}
}
It is also necessary to run the service in a separate process:
<service
android:name=".MyServiceClass"
android:process=":service"
android:enabled="true"
android:exported="true" />
I figured out that these workarounds are only worth the effort and the wait only if your bound services are running in a different process than your application's main process.
For accessing data and methods in the same process (or application), I ended up implementing singleton classes. If the classes need a context for some methods, I leak the application context to the singleton classes. There is, of course, a bad consequence of it as it breaks the "instant run". But that is an overall better compromise, I think.
*The basic idea is same with #18446744073709551615, but I will share my code as well.
As a answer of main question,
But what should I do to right use this service after bindService succeeds?
[Original expectation (but not work)]
wait until service connected like below
#Override
protected void onStart() {
bindService(service, mWebServiceConnection, BIND_AUTO_CREATE);
synchronized (mLock) { mLock.wait(40000); }
// rest of the code continues here, which uses service stub interface
// ...
}
It won't work because both bindService() in onCreate()/onStart() and onServiceConnected() is called at same main thread.
onServiceConnected() is never called before wait finishes.
[Alternative solution]
Instead of "wait", define own Runnable to be called after Service Connected and execute this runnable after service connected.
Implement custom class of ServiceConnection as follows.
public class MyServiceConnection implements ServiceConnection {
private static final String TAG = MyServiceConnection.class.getSimpleName();
private Context mContext = null;
private IMyService mMyService = null;
private ArrayList<Runnable> runnableArrayList;
private Boolean isConnected = false;
public MyServiceConnection(Context context) {
mContext = context;
runnableArrayList = new ArrayList<>();
}
public IMyService getInterface() {
return mMyService;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v(TAG, "Connected Service: " + name);
mMyService = MyService.Stub.asInterface(service);
isConnected = true;
/* Execute runnables after Service connected */
for (Runnable action : runnableArrayList) {
action.run();
}
runnableArrayList.clear();
}
#Override
public void onServiceDisconnected(ComponentName name) {
try {
mMyService = null;
mContext.unbindService(this);
isConnected = false;
Log.v(TAG, "Disconnected Service: " + name);
} catch(Exception e) {
Log.e(TAG, e.toString());
}
}
public void executeAfterServiceConnected(Runnable action) {
Log.v(TAG, "executeAfterServiceConnected");
if(isConnected) {
Log.v(TAG, "Service already connected, execute now");
action.run();
} else {
// this action will be executed at the end of onServiceConnected method
Log.v(TAG, "Service not connected yet, execute later");
runnableArrayList.add(action);
}
}
}
And then use it in the following way (in your Activity class or etc),
private MyServiceConnection myServiceConnection = null;
#Override
protected void onStart() {
Log.d(TAG, "onStart");
super.onStart();
Intent serviceIntent = new Intent(getApplicationContext(), MyService.class);
startService(serviceIntent);
myServiceConnection = new MyServiceConnection(getApplicationContext());
bindService(serviceIntent, myServiceConnection, BIND_AUTO_CREATE);
// Instead of "wait" here, create callback which will be called after service is connected
myServiceConnection.executeAfterServiceConnected(new Runnable() {
#Override
public void run() {
// Rest of the code comes here.
// This runnable will be executed after service connected, so we can use service stub interface
IMyService myService = myServiceConnection.getInterface();
// ...
}
});
}
It worked for me. But there may be more better way.

Categories

Resources