I didn't see example of using jobFinshed of JobService, seems like we have to track changes when some condition meet we have to call jobFinished() method, am I right?
The difficulty of calling jobFinished() from another class like an IntentService seems to be getting an instance of your class that extends JobService to use to call jobFinished(). You can get info on the scheduled job, but not on the JobService (at least, I can't find a way). I can think of 3 ways to call jobFinished().
If you don't care if your work is successful or not, or if your work takes very little time.
In one of my JobService classes, I'm doing periodic work. I'm not worried about handling failures. The task will execute again soon enough. If this is the case, you can do this.
public boolean onStartJob(JobParameters params) {
startService(new Intent(this, MyIntentServiceThatDoesTheWork.class));
// job not really finished here but we assume success & prevent backoff procedures, wakelocking, etc.
jobFinished(params, false);
return true;
}
This is also the way you want to do it if your work is short enough it's no problem to do it on the UI thread. In this case, do all your work in onStartJob() then return false.
Use a BroadcastReceiver to send a message from the IntentService to the JobService (a separate file for each class).
// common Strings
public static final String IS_SUCCESS = "isSuccess";
public static final String MY_BC_RCVR = "MyBroadcastRcvr";
Your JobService
public class MyJobService extends JobService {
JobParameters mParams;
public boolean onStartJob(JobParameters params) {
mParams = params;
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
new IntentFilter(MY_BC_RCVR));
startService(new Intent(this, MyIntentServiceThatDoesTheWork.class));
return true;
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
boolean isSuccess = false;
if(intent.hasExtra(IS_SUCCESS)) {
isSuccess = intent.getBooleanExtra(IS_SUCCESS, false);
}
LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
jobFinished(mParams, !isSuccess);
}
};
}
& your IntentService
public class MyIntentServiceThatDoesTheWork extends IntentService {
#Override
protected void onHandleIntent(Intent intent) {
boolean isSuccess = methodToDoAllMyWork();
Intent bcIntent = new Intent(MY_BC_RCVR);
bcIntent.putExtra(IS_SUCCESS, isSuccess);
LocalBroadcastManager.getInstance(this).sendBroadcast(bcIntent);
}
}
Nest your worker thread class in the JobService class.
I've given an example of an AsyncTask based on this Medium post (also referenced by Arseny Levin) from a Google Developer Advocate but it should also be possible to use an IntentService (see this SO post for nesting IntentService).
public class MyJobService extends JobService {
JobParameters mParams;
public boolean onStartJob(JobParameters params) {
mParams = params;
new MyAsyncTaskThatDoesTheWork().execute();
return true;
}
private class MyAsyncTaskThatDoesTheWork extends AsyncTask<Void, Void, Boolean> {
#Override
protected Boolean doInBackground(Void... params) {
return methodToDoAllMyWork();
}
#Override
protected void onPostExecute(Boolean isSuccess) {
if(mParams != null) {
jobFinished(mParams, !isSuccess);
}
}
}
}
If your onStartJob() method returns true, that means that you are doing work in the background in support of this job. That background thread needs to call jobFinished() when that work has been completed or if the job needs to be rescheduled.
A simple approach is to start a new thread from onStartJob as shown below:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
#Override
public boolean onStartJob(final JobParameters params) {
new Thread(new Runnable() {
#Override
public void run() {
// do your job here
jobFinished(params, true);
}
}).start();
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
return true;
}
}
As #CommonsWare explained - inside onStartJob you'll decide if further work is required on a background thread. Only if that's the case, then you should call jobFinished from that background thread.
Just to answer your specific question - no condition tracking is required on your behalf. JobService will call onStopJob if the conditions you asked are no longer true.
Example of jobFinished can be found here:
https://medium.com/google-developers/scheduling-jobs-like-a-pro-with-jobscheduler-286ef8510129
Related
The situation is I call an activity from my RouteActivity by:
arrived.observe(this, new Observer<Boolean>() {
#Override
public void onChanged(Boolean aBoolean) {
if(aBoolean==true){
Intent intent = new Intent(MyApplication.getAppContext(), RouteCompleteActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.slide_up, R.anim.do_nothing);
finish();
}
}
});
That is fine then when I close the activity by calling:
Intent navIntent = new Intent(MyApplication.getAppContext(), NavigationStartActivity.class);
navIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(navIntent);
finish();
This takes me back to my main NavigationStartActivity, then when I again choose to go back to my RouteActivity the original, the RouteCompleteActivity is still over the top of it? so instead of RouteActivity I get RouteCompleteActivity then if I press back it goes to the RouteActivity?? as if it has remembered some backstack? can anyone explain this?
I assume that you're using a LiveData from a viewModel or repository which keeps its value. The case is like this: arrived has a true value and onChanged will be called. The next time RouteActivity observes arrived, onChanged will be called again because it already has a value and another startAcrivity will be called. A simple solution would be using SingleLiveEvent instead which was created by google long time ago link
public class SingleLiveEvent<T> extends MutableLiveData<T> {
private static final String TAG = "SingleLiveEvent";
private final AtomicBoolean mPending = new AtomicBoolean(false);
#MainThread
public void observe(LifecycleOwner owner, final Observer<T> observer) {
if (hasActiveObservers()) {
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
}
// Observe the internal MutableLiveData
super.observe(owner, new Observer<T>() {
#Override
public void onChanged(#Nullable T t) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
#MainThread
public void setValue(#Nullable T t) {
mPending.set(true);
super.setValue(t);
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#MainThread
public void call() {
setValue(null);
}
}
It simply calls onChange when a new value is set. I also recommend to take a look at this article which describes it deeper
I have AsyncTask class with methods like this(class: ApiConnector):
#Override
protected String doInBackground(Void... voids)
{
return getToken(); //<-- do many the most important things and return String
}
and
#Override
protected void onPostExecute(String result)
{
super.onPostExecute(result);
}
and then in my Activity:
new ApiConnector()
{
#Override
public void onPostExecute(String result)
{
Log.d("here: ", result);
}
}.execute();
It work's fine when I execute this one time, but i have to do this in endless loop to take always fresh token like fresh apples in my market. I tried something like that:
while (true)
{
new ApiConnector()
{
#Override
public void onPostExecute(String result)
{
Log.d("here!", result);
}
}.execute();
Thread.sleep(1000);
}
and many many stupid things, but i can't find working way. All thread bussiness is tricky for me. Give me some kick and I manage this for sure.
You don't want to do this. All AsyncTasks run on one thread. If you infinitely loop inside an AsyncTask, you'll starve all other tasks. If you have each task start a new task, then you'll still risk major starvation issues.
If you want to do this (and I'm not sure you really do, but lets ignore that), the correct way is to use a Thread. A Thread can just have a giant while(true) loop and a sleep statement at the end.
Like hrskrs commented I would prefer using a Handler to execute something repeatedly. The main advantage is that postDelayed makes the run() method execute in the main application thread - so you can access and change UI components.
Here's an example:
public class MyTest implements Runnable {
private final static int INTERVAL = 5000;
private Handler mHandler;
private MyTest() {
mHandler = new Handler();
}
public void start() {
run();
}
public void stop() {
mHandler.removeCallbacks(this);
}
#Override
public void run() {
// put here the logic that you want to be executed
mHandler.postDelayed(this, INTERVAL);
}
}
I have 2 Classes: a Main Class handling the UI and a second Class for retrieving Data from SQL Server by using PHP.
From the first class a mehtod in the second class is called with passing and retrieving variables.
Actually it is working fine without AsyncTask.
But since I want to use the code on devices running Android 3.0 and above, I need to change the method to be an AsyncTask. Else I get this error: "android.os.networkonmainthreadexception"
the actual working code Looks like this:
Main class:
...
String inputString="1";
String outputString;
outputString = Class2.SomeMethodInClass2(inputString);
....
Class2:
public class Class2 {
public static String SomeMethodInClass2(String input) {
String Output;
...
//Do some php-sql stuff based on "input"-variable
//and return the "output"-variable
...
return output;
}
}
This code works perfectly on Android 2.0 but I need to change it to AsyncTask, because Andoid 3.0 and above is giving me: "android.os.networkonmainthreadexception"
I have read a lot of threads about AsyncTask, but I can not get it to work with Input and Output in my code.
Eclipse allways tells me there is something wrong with my code.
How do I have to change my code, to be a working async Task? (please explain using my above code sample)
--edit: if there is an easier way to get rid of "android.os.networkonmainthreadexception" on 3.0 an above than AsyncTask, this would be fine too! --
Have a Callback in SecondClass
Extend your SecondClass with Asynctask
Implement preExecute,doinbackground, postExecute methods
Do your stuff in doinbackground
return result in doinbackground
In postExecute pass result to the Callback
Implement SecondClass.Callback in FirstClass
start SecondClass (execute) and pass a Callback reference from FirstClass
In Callback just handle your next operations with the result
EDIT :
public class SecondClass extends AsyncTask<String, Void, String> {
public interface Callback {
public void update(String result);
}
Callback mCallback;
public SecondClass(Callback callback) {
super();
mCallback = callback;
}
#Override
protected String doInBackground(String... params) {
String result = null;
//do your stuff and save result
return result;
}
#Override
protected void onPostExecute(String result) {
if(mCallback != null)
mCallback.update(result)
super.onPostExecute(e);
}
}
public class FirstClass implements SecondClass.Callback{
#Override
public void update(String result){
//do your stuff with result
}
return_type someMethod(){
SecondClass sc = new SecondClass(this) ;
sc.execute(someurl);
}
}
thanks for your Posts!
But i found an alternative way that seems much easier and better to me than doing async Task.
i added to my main class:
public class MainClass extends Activity implements OnTouchListener {
...
BackgroundOperations1 ourBackgroundOperations; //<===new
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
ourBackgroundOperations = new BackgroundOperations1(); //<===new
...
}
#Override
protected void onPause() {
....
ourBackgroundOperations.pause(); // <===new
}
#Override
protected void onResume() {
....
ourBackgroundOperations.resume(); // <===new
}
//whole new class inside of "MainClass"
//runs a different thread, i believe...?
public class BackgroundOperations1 implements Runnable {
Thread ourThread = null;
boolean isRunning = false;
public void pause() {
isRunning = false;
while (true) {
try {
ourThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
}
public void resume() {
isRunning = true;
ourThread = new Thread(this);
ourThread.start();
}
#Override
public void run() {
while (isRunning) {
if (dothis==true){
//here i call my mehtod from class2, whenever "dothis" is set to true (somewhere in my onTouch or where ever i want)
String inputString="1";
String outputString;
outputString = Class2.SomeMethodInClass2(inputString);
}
}
}
}
}
this works on Android 4.0 and i think it is the best way to do what i want. and to me it seems a lot clearer then AsyncTask, because i can call my methods an pass varaibles in a simple way.
or is there a reason to not do it like this?
I am trying to pass a Runnable to an AsyncTask and then set the results of doInBackgroud to it and run it.
I am trying the following code with no success.
// MyActivity.java
public void click(View v) {
if(v.getId() == R.id.button) {
new AsyncHTTP(myAsyncClassCallback()).execute();
}
}
public Runnable myAsyncClassCallback() {
return new StringRunnable() {
#Override
public void run() {
Log.v(DEBUG_TAG,result.toString());
}
};
}
// StringRunnable.java
public class StringRunnable implements Runnable {
volatile String result;
public void setResult(String res) {
this.result = res;
}
#Override
public void run() {
// do something with result
}
}
// MyAsyncClass.java
public class MyAsyncClass extends AsyncTask<String, Integer, String>{
private Runnable myCallback;
public MyAsyncClass(Runnable runnable){
this.myCallback = runnable;
}
#Override
protected String doInBackground(){
// works normally
return someString;
}
#Override
protected void onPostExecute(String result) {
myCallback.setResult(result); // <--- This is my question
myCallback.run();
super.onPostExecute(result);
}
}
So I get the message:
Cannot resolve method setResult
How can I fix this? Is this some access issue?
You declared myCallback a Runnable, but Runnable does not contain a definition for setResult(String result). Your extended class, however, does. Which means you should cast the Runnable to a StringRunnable, which it actually is
((StringRunnable)myCallback).setResult(result);
Or you should declare your myCallback as a StringRunnable
private StringRunnable myCallback;
EDIT: as Unihedron pointed out, the last options means you have to change your Constructor as well
private StringRunnable myCallback;
public MyAsyncClass(StringRunnable runnable){
this.myCallback = runnable;
}
I should note, however, that it is, in your case, unnecessary to use two non-UI-threads. AsyncTask is a Thread as well
instead of
private Runnable myCallback;
use
private StringRunnable myCallback;
You'll have to cast Runnable to StringRunnable like this:
((StringRunnable)myCallback).setResult(result);
I cant figure out why the variable "vysledek" stays unchanged after calling the void "Send" from activity. I probably doesnt fully understand the way AsyncTask works. Thanks for help.
public class Tools{
public String vysledek;
public void Send() {
Poslat Poslat = new Poslat();
Poslat.execute();
}
private class Poslat extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... urls) {
vysledek = "something I want it to be";
}
#Override
protected void onPostExecute(String result){
vysledek = "something I want it to be 2";
}
}
I want that the Activity that called "Send" have the variable already. So i guess it has to wait for it to finish. I tried to do the waiting like this:
while (Tools.vysledek.equals(""))
{ }
But that causes crash.
Timing. The asnc task happens on another thread. But when the OS switches to that thread is up to the OS. It should happen quickly (next few hundred ms), but it won't necessarily be immediate, so if you immediately check for it you may or may not see it changed. The correct place to put code that requires the async task to have run is in onPostExecute.
You should assign it after the asynctask finishes
public class Tools{
public String vysledek;
public void Send() {
Poslat Poslat = new Poslat();
Poslat.execute(vysledek);
}
private class Poslat extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... urls) {
}
#Override
protected void onPostExecute(String result){
vysledek = "I should be here";
}
}