I am developing an Android application and I need show remind dialog from time to time in according to variable from shared preferences. If user does not want to see reminder message, he can disable this option also in my PreferenceActivity. I am using AsyncTask class to show remind message to user from time to time . User can press options button at every moment to go to PreferenceActivity to enable/disable reminder option and set pause time value between reminds, and the go back to activity. So I am checking all values in onResume method. And if users does not want to see remind messages I need to finish current working AsyncTask, or if user changed value between reminds, I need to restart current AsyncTack with new pause value. But I see an unpredictable behavior of my AsyncTack: sometimes it stops, sometimes not and continue working and showing message)))), sometimes it works and sometimes - not))))). Here is a piece of code:
This is my AsyncTack class
private class ReadReminderTask extends AsyncTask<Long, Void, Void> {
private long mRemindTime;
private volatile boolean running = true;
public ReadReminderTask(){
}
#Override
public Void doInBackground(Long... params){
mRemindTime = params[0];
while (running){
try{
Thread.sleep(mRemindTime);
publishProgress();
}
catch (Exception e){
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(Void... progress) {
super.onProgressUpdate(progress);
// showReminder is a method where I show remind message
showReminder();
}
#Override
protected void onCancelled() {
running = false;
}
}
This is onResume method of my Activity where I need to show reminder message:
if(!settings.getBoolean("needToRemind", false)) {
mReadReminderTask.cancel(true);
} else if(settings.getBoolean("needToRemind", false)) {
mReadReminderTask = new ReadReminderTask();
mReadReminderTask.execute(settings.getLong("timeRemind", 1));
}
Can anyone help me with my problem?
That's really not such a great way to do that. The "unpredictable behavior" happens because you leave the task running even after your Activity is stopped. The call to cancel in your onResume is not only too late, it actually is not doing anything at all.
Have a look at Handler.postDelayed. You want something like this:
In your onCreate
hdlr = new Handler();
reminder = new Runnable() { public void run() { showReminder(); } }
EDITED TO ADD ESSENTIAL 2ND PARAM TO postDelayed
... in your onResume:
hdlr.postDelayed(reminder, mRemindTime);
... and, in your onPause:
hdlr.removeCallbacks(reminder);
Please note that AsyncTask.cancel() does not really cancel running task. Instead it only sets a flag to notify that cancel has been requested. It is up to AsyncTask implementation if and when to take this flag into account.
Your current code does not check for cancellation after Thread.sleep returns. Correct implementation would be something like:
public Void doInBackground(Long... params){
mRemindTime = params[0];
while (!isCancelled()){
try{
Thread.sleep(mRemindTime);
if(!isCancelled())
publishProgress();
}
catch (Exception e){
e.printStackTrace();
}
}
return null;
}
Also attribute 'running' and onCancelled() method in your asynctask are not needed. You can use method 'isCancelled()' instead.
It's generally a bad solution. AsyncTasks are not great in handling config changes.
Depending on how long the delays are, you can use AlarmManager.setInexactRepeating in order to show these dialogs. You can create new PendingIntent using PendingIntent.getService or PendingIntent.getBroadcast depending on how you'd like to implement showing dialog – (from Service you will be able to show only system-like dialogs).
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intentStart = new Intent();
intentStart.setAction(SHOW_DIALOG_ACTION);
PendingIntent pendingIntentStart = PendingIntent.getBroadcast(context, 0, intentStart, 0);
And register a BroadcastReceiver which will handle showing the dialog.
If the delays are not long, you can create a custom ThreadPoolExecutor which will do what you want.
I don't think AsyncTask should be used for that. Thread.sleep(mRemindTime) is also very ugly! If you need a timer use CountDownTimer.
Related
My app keeps telling me that I'm doing too much work on the main thread, and if I don't wait long enough when the app first loads before touching the UI, it crashes. Specifically, it seems like what I'm waiting for is the video to load into the videoView, because when I comment out the 'myVideoView.setVideoURI' line, it works totally fine.
I already have an asynctask set up for communicating with the server, so I thought maybe I could some how use an asynctask to set the video in the background and then hide the progress bar when it's done.
I tried putting my entire video loading code below inside the asynctask, but it kept saying 'this must be done on the UI thread'. Of course, I'm probably just misunderstanding the concept of multi threading, but if anyone could help me clarify how I may go about reducing the strain on my main thread, that would be so great. Thank you!
Here is my code sample. All of this is currently inside onCreate.
myVideoView = (VideoView) findViewById(R.id.videoView);
try {
myVideoView.setVideoURI(Uri.parse("android.resource://" + getPackageName() + "/" + R.raw.video));
} catch (Exception e) {
Log.e("Error", e.getMessage());
e.printStackTrace();
}
myVideoView.requestFocus();
myVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
public void onPrepared(MediaPlayer mediaPlayer) {
myVideoView.seekTo(0);
}
});
// EMPTY ASYNCTASK
public class LoadVideo extends AsyncTask<String, String, String> {
#Override
protected void onPreExecute() {
super.onPreExecute();
// DO SOMETHING BEFORE IT STARTS
}
protected String doInBackground(String... args) {
// DO HEAVY LIFTING
return null;
}
protected void onPostExecute(String file_url) {
// DO SOMETHING AFTER IT FINISHES
}
}
Yes, there is a solution- use rxJava multithreading. It allow to create request on one thread, perform it on another (computation, for example) and handle result on main thread. It is modern way to deal with multithreading. I use it a lot in my current project.
See comment https://stackoverflow.com/a/38002606/6175778
My MainActivity on my Android application checks if the user is logged in (this is stored in SharedPreferences) and if it's not takes the user to the LoginActivity. I am trying to test this using the following code
public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {
private static final int TIME_OUT = 5000; /* miliseconds */
private MainActivity mMainActivity;
private Instrumentation mInstrumentation;
private SharedPreferences mLoginPrefs;
public MainActivityTest() {
super(MainActivity.class);
}
protected void setUp() throws Exception {
super.setUp();
setActivityInitialTouchMode(false);
mMainActivity = getActivity();
mInstrumentation = getInstrumentation();
mLoginPrefs = mInstrumentation.getTargetContext().getSharedPreferences(LoginActivity.PREFS_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = mLoginPrefs.edit();
// User is not logged in, so it should be redirect to LoginActivity
editor.putBoolean("logged_in", false);
editor.commit();
}
//...
public void testC_OpenLoginActivityIfUserIsNotLoggedIn() {
ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
Activity nextActivity = mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);
assertNotNull(nextActivity);
nextActivity.finish();
SharedPreferences.Editor editor = mLoginPrefs.edit();
// Login the user so we can continue the tests
editor.putBoolean("logged_in", true);
editor.commit();
}
But this doesn't work, the LoginActivity opens but waitForMonitorWithTimeout never returns so I got stuck on LoginActivity (I need to get back to MainActivity to do the other tests).
A code similar to this SO Question works for Button clicks, but this Activity is not loaded by any click so I am thinking maybe there is no time to the monitor to work.
I just need a way to get the actual Activity so I can make an assert and make it finish to continue my tests.
Just one other thing: I would prefer a method without using Robotium if it's possible.
In order to solve your problem, first take a look at the two most important methods for your test:
Instrumentation#addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean)
Instrumentation.ActivityMonitor#waitForActivity()
According to Android API reference:
addMonitor
Add a new Instrumentation.ActivityMonitor that will be checked whenever an activity is started. The monitor is added after any existing ones; the monitor will be hit only if none of the existing monitors can themselves handle the Intent.
waitForActivity
Block until an Activity is created that matches this monitor, returning the resulting activity.
Now let's make it a bit more clear.
addMonitor should be called always before the expected activity being started, never too late.
waitForActivity should be called only after the expected activity being started, never too early, since it will block.
Back to your code:
You're calling both of them together, without any magic happening in between. So it's either too late for addMonitor, or too early for waitForActivity.
ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
Activity nextActivity = mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);
If it's too early for calling waitForActivity, it will block and fail until the timeout (because the expected activity is not hit yet), and you would never see the expected activity being started.
If it's too late for calling addMonitor, the monitoring starts after the expected activity is launched, and the expected activity is not launched again since then, so waitForActivity will block because of no hit of the monitor.
So the difference between the two cases is whether the expected activity is started or not. And for your case, I think it's too late for calling addMonitor.
The solution is very easy: just move addMonitor to a early enough position before your LoginActivity starts, maybe move it to the setUp method, like this:
mInstrumentation = getInstrumentation();
ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
BTW, for your case, with timeout or without timeout doesn't matter.
Don't forget to remove the monitor after it's not needed anymore e.g:
#Override
protected void tearDown() throws Exception {
mInstrumentation.removeMonitor(monitor);
super.tearDown();
}
public static Activity getCurrentActivity() {
final Activity[] currentActivity = {null};
getInstrumentation().runOnMainSync(new Runnable() {
public void run() {
Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance()
.getActivitiesInStage(RESUMED);
if (resumedActivities.iterator().hasNext()) {
currentActivity[0] = (Activity) resumedActivities.iterator().next();
}
}
});
return currentActivity[0];
}
You were on the right track. The method you need to call is:
monitor.waitForActivityWithTimeout(TIME_OUT);
instead of
mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);
I have following piece of code:
public class SomeActivity extends Activity {
Context context;
List<MenuItem> menuItems;
public void importList(View v) {
menuItems = new ArrayList<MenuItem>();
ProgressDialog dialog = ProgressDialog.show(this.context, "TITLE", "MSG");
MyAsyncTask task = new MyAsyncTask(context); // Context is here because I tried to create ProgressDialog inside pre/postExecute, but it doesn't work either
task.execute();
try {
// menuItems = task.get();
} catch(Exception e) {
// : (
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
this.context = this;
}
}
When I comment the line, where i get values from AsyncTask ("menuItems = task.get()") everythings work ok. But when I uncomment it, ProgressDialog appears AFTER the task is finished, and value returned. Why is that?
I think that it has sth to do with these contexts (that's why I included onCreate method) but I don't have any idea how to fix it. Obviously, I want ProgressDialog to display BEFORE task is finished, not after.
Not sure if relevant - MyAsyncTask is doing http request and some json parsing.
I think that it has sth to do with these contexts
Not at all. Also, when sending Context from an Activity you don't need to create a variable. Simply use this or ActivityName.this.
MyAsyncTask task = new MyAsyncTask(this);
But when I uncomment it, ProgressDialog appears AFTER the task is finished, and value returned. Why is that?
Calling get() blocks the UI, this is why you don't see the progress until it is done. You don't need to call this. The point of onProgressUpdate() is so the background thread (doInBackground()) can call it to update the UI since doInBackground() doesn't run on the UI.
Remove that line from your code and never look at it again. If there is a reason that you think you need it then please explain and we can help you find a better way.
Edit
See this SO answer on updating when the task is finished
I understand I may be doing this a bit backwards, but haven't found a suitable solution yet and this is the closest I have come. I am first presenting a Login view to the user and submitting the inputted credentials to a web service which validates credentials and returns a HTTP 200 if the logon was successful. Upon success, I am then making a HTTP JSON request to the same web service to download a list of JSON objects. These objects are then parsed, and placed into a SQLite DB. There are around 100 or more objects, so an async task is appealing so I don't lock up the UI. The point of this is to display a ProgressBar (indeterminate) to the user saying there is work being done in the background, then return them to a separate Intent once the DB is populated. Here is the code snippets:
Validating the login and calling the login action:
switch(statusCode) {
case 200:
PrepareLogin doLogin = new PrepareLogin();
doLogin.execute();
if (doLogin.getStatus().equals(AsyncTask.Status.FINISHED)) {
// Redirect to intent?
}
break;
case 401:
Toast.makeText(Login.this, "Invalid Username/Password", Toast.LENGTH_LONG).show();
}
Async task with helper functions in the background:
private class PrepareLogin extends AsyncTask<Void,Void,Void> {
ProgressDialog dialog;
#Override
protected void onPreExecute() {
dialog = new ProgressDialog(Login.this);
dialog.setTitle(getString(R.string.get_login_dialog_title));
dialog.setMessage(getString(R.string.get_login_dialog_message));
dialog.setIndeterminate(true);
dialog.setCancelable(false);
dialog.show();
}
#Override
protected Void doInBackground(Void... params) {
Looper.myLooper().prepare();
Looper.myLooper().loop();
DBHelper dbHelp = new DBHelper(Login.this);
WSHelper wsHelp = new WSHelper();
long timeNow = System.currentTimeMillis();
dbHelp.resetDB(Login.this); // For dev environment only...remove in prod
dbHelp.create();
dbHelp.createUser(username, password, timeNow, "false");
dbHelp.close();
wsHelp.getEmployees(Login.this, username, password);
Looper.myLooper().quit();
return null;
}
protected void onPostExecute() {
Intent myIntent = new Intent(login.getContext(), Main.class);
startActivityForResult(myIntent, 0);
dialog.dismiss();
}
}
The only way I can get this to function without throwing the "Handler must prepare a looper" error, is with the Looper.myLooper.prepare() function.
I can see in the logs that the connection is made and DB populated, but the dialog continues to spin and it never reaches the onPostExecute() function of the Async task. It hits the Looper.myLooper().quit(); line though.
Any suggesstions?
If you get "Handler must prepare a looper" error, most probably you are calling the AsyncTask in a wrong context. I don't remember I ever need to explicitly call Looper.prepare() function in any AsyncTask. Once you get rid of the looper, it would be fine, the AsyncTask is one of the easiest to use class in Android. You should post your code in the Login class (I suppose it is an Activity, right?).
Another problem I can find is:
case 200:
PrepareLogin doLogin = new PrepareLogin();
doLogin.execute();
if (doLogin.getStatus().equals(AsyncTask.Status.FINISHED)) {
// Redirect to intent?
}
break;
Your doLogin.execute(); should not be a blocking call, so the next if is guaranteed to be wrong, so the redirect would never fire.
It appears that my WSHelper class was attempting to making some sort of change to the UI from the background process. By removing the wsHelp initialization from the AsyncTask and making it a constant, I was able to proceed just fine. It may also be worth noting I changed the return value to void for the wsHelp.getEmployees() method.
I'm working with a fairly common situation right now - download some data over the web, then update a view to display it. Clearly, I want to do the web download in the background, and then update the view on the main UI thread. Now looking at my code, I'm a little worried about my Activity and its UI elements being killed off before I update them. Here's the essence of what I have in mind:
Thread update = new Thread() {
public void run() {
final Data newData = requestData();
if (newData != null) {
post(new Runnable() {
public void run() {
Toast.makeText(MyClass.this, "I'll do things here that depend on my context and views being valid", Toast.LENGTH_SHORT).show();
}
});
}
}
};
update.start();
It seems possible that while I'm downloading data, the activity may be destroyed. What happens then? Will my thread continue to execute? Will I end up trying to access dead objects?
Usually I do this by AsycTask, but the work seemed simple enough this time to just inline the threads-launching-threads stuff. Will I make things any better by using an AsyncTask instead?
If your Context is an Activity, you can check if it is finishing or has finished with the isFinishing() method:
if ( context instanceof Activity ) {
Activity activity = (Activity)context;
if ( activity.isFinishing() ) {
return;
}
}
Toast.makeText(context, "I'll do things here that depend on my context and views being valid", Toast.LENGTH_SHORT).show();
What you really want to use is an AsyncTaskLoader. These are my new favorite classes in the Android API. I use them all the time and they were made to solve problems just like this. You won't have to worry about when to stop your download or anything like that. All the threading logic is taken care of for you, including telling the thread to stop if the activity has been closed. Just say what it is you want to do in the loadInBackground() method. Note that if you are developing for an API lower than 3.0, you can still access all the loaders via the Android Support Package.
If you use anonymous classes, they will have an internal reference to the outer class, so it's not like it becomes inaccessible all of a sudden because other references have been cleared. AsyncTask actually doesn't change anything, it uses similar mechanics for notifying about results.
You can use loaders, they are designed to be in sync with the activity lifecycle. They are available only since Android 3.0, but you can use support package to work with them on any device with 1.6 or later.
There is even a simpler solution, you can just use a boolean field which indicates whether activity has gone away. You should set this field in onPause() (or whenever you think you won't need the notifications anymore) and check for it when you show toast. You won't even have to use synchronization, since this field is confined to the main thread, so it's absolutely safe. By the way, if you change this field somewhere else than in onDestroy(), don't forget to add a statement which resets your field back in the counterpart method.
public class MyActivity extends Activity {
private boolean activityDestroyed = false;
#Override
protected void onDestroy() {
activityDestroyed = true;
}
private void updateData() {
new Thread() {
#Override
public void run() {
final Data newData = requestData();
if (newData == null) return;
runOnUiThread(new Runnable() {
public void run() {
if (activityDestroyed) return;
Toast.makeText(MyActivity.this, "Blah",
Toast.LENGTH_SHORT).show();
}
});
}
}.start();
}
}
I usually use Weak Reference to avoid leaking context in views
Weak Reference for Context
private var mContext: WeakReference<Context?>? = null
Assign Context
mContext = WeakReference(appContext)
Get Context
mContext .get()
Validate Context
if (mContext?.get() is Activity &&
(mContext?.get() as Activity).isFinishing){
return
}
Kurtis is right. However, if you REALLY want to keep it simple, you can try this:
class MyActivity extends Activity {
static MyActivity context;
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
MyActivity.context = this;
}
#Override
public void onDestroy() {
super.onDestroy();
MyActivity.context = null;
}
}
And then you just use MyActivity.context in your class (and check for null there). If you want the toast to not even show up when your app is in the background, use onPause/onResume instead.
Again, this is the quick and lazy approach. AsyncTask or AsyncTaskLoader is how you should be doing things.