Right now I have a Timer starting an AsyncTask in a Fragments onCreate method (with a timer interval of 1 minute). So that is not a good idea I believe because if there is a configuration change the fragment will call the onCreate method again and I will have 2 running Timer AsyncTasks right?
So I need some way to put the AsyncTask where it's only started once during the whole lifecycle of the app.
no need of Asynctask for simple timer, try this -
class MyFragment extends Fragment{
private int currentTime;
private void startTimer(){
new Handler().
postDelayed(new Runnable(){
#Override
public void run()
{
onTick();
startTimer();
}
},1000*60);
}
private void onTick()
{
currentTime++;
}
#Override
public void onSaveInstanceState(Bundle outState)
{
outState.putInt("currentTime", currentTime);
super.onSaveInstanceState(outState);
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
currentTime=savedInstanceState.getInt("currentTime",0);
startTimer();
}
}
You could use a Countdown Timer and cancel it in onDestroy(). That would guarantee that you only have a single one running at any time.
As for starting it exactly only once you will have to persist that knowledge somewhere depending on your needs. Maybe storing a boolean in onSaveInstanceState and reading it in onCreate() would do the trick?
And, as #marcin_j has pointed out, AsyncTasks are executed in sequence (unless specifically started differently), so using one will block every other Async task in your app (like downloading stuff etc).
if there is a configuration change the activity gets destroyed and re-created so no you will not have 2 timers running
Related
My MainActivity has a Thread that generates RSA keys and returns the amount of time in milliseconds that it took to generate them.
While I run this Thread, the app goes to a second Activity.
I need the second Activity to receive that time in milliseconds.
As I understand, once you call startActivity(), the parent Activity goes to sleep. So how can I run both simultaneously?
Thanks!
You can use LocalBroadcastReceiver.
public class SecondActivity extends AppCompatActivity {
private BroadcastReceiver mRsaReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
ArrayList<String> rsaList = intent.getStringArrayListExtra("rsaList");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
LocalBroadcastManager.getInstance(this).registerReceiver(mRsaReceiver, new IntentFilter("RSA"));
}
#Override
protected void onDestroy() {
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mRsaReceiver);
}
}
From Your rsa thread
Intent rsaIntent = new Intent("RSA");
rsaIntent.putExtra("rsaList", rsaArrayList);
LocalBroadcastManager.getInstance(context).sendBroadcast(rsaIntent);
As I understand, once you startActivity(), the parent activity goes to sleep. So how can I run both simultaneously
You do not. And you do not need to. Since you generate your RSA keys on a separate thread that code should be fully independent from your activities (aside from being started in MainActivity). So all you need to know is when your background task finished - and for that you can use either in-app broadcasts or use Event Bus like GreenRobot's EventBus library or use RxJava.
I want to set dynamically auto scroll speed to WebView. In onCreate calling autoScroll(25) and remove it, nextly calling autoScroll(300) but when the apk is running the auto scroll speed is 25 so earlier called 'mHandler.postDelayed' do not removing. How to fix the problem?
Handler mHandler;
Runnable runnable;
WebView wv;
protected void onCreate(Bundle savedInstanceState) {
...
autoScroll(25);
mHandler.removeCallbacks(runnable);
autoScroll(300);
}
public void autoScroll(final int speed){
if(mHandler == null) {
mHandler = new Handler();
}
wv.post(runnable = new Runnable() {
#Override
public void run() {
wv.scrollBy(0, 1);
mHandler.postDelayed(this, speed);
}
});
}
mHandler.removeCallbacks(runnable);
will only remove any pending posts of Runnable r that are in the message queue. It will not stop an already running thread. You have to explicitly stop the thread. One way to stop that thread is to use a boolean variable as a flag and run your code inside runnable based on the value of that flag. You can take some hints from https://stackoverflow.com/a/5844433/1320616
Several times I've had problems writing code on onCreate(). Mostly because the UI has not been sized and laid out on the screen yet (even if I place my code at the end of the function). I've looked over the activity life-cycle to see if there's anything that runs after onCreate(). There is onStart(), but the problem is that onRestart() recalls onStart(), I don't want that. So is there a way to write code between onCreate() and onStart()? OR where should I write code that runs after the UI is placed and only runs once during its process?
Not sure what exactly you need but you can "cheat" and simply store whether you have run code or not:
private boolean mInit = false;
void onStart() {
if (!mInit) {
mInit = true;
// do one time init
}
// remaining regular onStart code
}
The other way of running code when UI is placed is to use the global layout listener:
public class FooActivity extends Activity implements ViewTreeObserver.OnGlobalLayoutListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_content);
View content = findViewById(android.R.id.content);
content.getViewTreeObserver().addOnGlobalLayoutListener(this);
}
#Override
public void onGlobalLayout() {
// unregister directly, just interested once.
View content = findViewById(android.R.id.content);
content.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// do things here.
}
}
I need execute a method when the fragment is visible (to the user).
Example:
I have 2 buttons (button 1 and button 2) ,
2 fragments(fragment 1 and fragment 2)
and the method loadImages() inside the class fragment 2.
when I press "button2" I want to replace fragment 1 by fragment 2
and then after the fragment 2 is visible (to the user) call loadImages().
I tried to use onResume() in the fragment class but it calls the method before the fragment is visible and it makes some delay to the transition.
I tried setUserVisibleHint() too and did not work.
A good example is the Instagram app. when you click on profile it loads the profile activity first and then import all the images.
I hope someone can help me. I will appreciate your help so much. Thank you.
Use the ViewTreeObserver callbacks:
#Override
public void onViewCreated(View v, Bundle savedInstanceState) {
super.onViewCreated(v, savedInstanceState);
final View view = v;
// Add a callback to be invoked when the view is drawn
view.getViewTreeObserver().addOnDrawListener(new ViewTreeObserver.OnDrawListener() {
#Override
public void onDraw() {
// Immediately detach the listener so it only is called once
view.getViewTreeObserver().removeOnDrawListener(this);
// You're visible! Do your stuff.
loadImages();
}
});
}
I'm a little confused by what you are trying to do. It sounds like the images are loading too fast for you... so does that mean that you have the images ready to display? And that is a bad thing?
My guess (and this is just a guess) is that Instagram does not have the profile pictures in memory, so they have to make an API call to retrieve them, which is why they show up on a delay. If the same is the case for you, consider starting an AsyncTask in the onResume method of the fragment. Do whatever loading you need to do for the images in the background, and then make the images appear in the onPostExecute callback on the main thread. Make sure you only start the task if the images are not already loaded.
However, if you already have the images loaded in memory, and you just want a delay before they appear to the user, then you can do a postDelayed method on Handler. Something like this:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
loadImages();
}
}, 1000);
Edit
As kcoppock points out, the handler code is pretty bad. I meant it to be a quick example, but it is so wrong I should not have included it in the first place. A more complete answer would be:
private Handler handler;
public void onResume(){
super.onResume();
if(handler == null){
handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
loadImages();
}
}, 1000);
}
}
public void onDestroyView(){
super.onDestroyView();
handler.removeCallbacksAndMessages(null);
handler = null;
}
Use the onActivityCreated() callBck
I have a CountdownTimer that counts down from 60 seconds. This CountdownTimer works by setting a textView to the remaining milliseconds, but whenever i rotate my device, the CountdownTimer gets reset.
I know this happens because the Activity gets restarted on rotation. So i tried saving the time remaining in a bundle and then restoring it, after the Activity was restarted.
long transferValue;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_playtimemode);
Log.d("Debug", "onCreate: " + transferValue);
long setTime = 60000;
long difference = setTime - transferValue;
new CountDownTimer(difference, 1000) {
public void onTick(long millisUntilFinished) {
millisUntilFinishedToSave = millisUntilFinished;
tvCountdown.setText("" + millisUntilFinished / 1000);
}
public void onFinish() {
tvCountdown.setText("done!");
}
}.start();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong("millisKey", millisUntilFinishedToSave);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
transferValue = savedInstanceState.getLong("millisKey");
Log.d("Debug", "onRestoreInstanceState(): " + transferValue);
}
This however doesn't work. I am intializing transferValue at the top of this code (hence it returning 0), but how can i else save the data from the savedInstanceState to the CountdownTimer?
07-06 20:21:30.038: D/Debug(28995): onCreate: 0
07-06 20:21:30.043: D/Debug(28995): onRestoreInstanceState(): 55994
I would give your timer it's own thread. Your timer is being stopped because it's on the UI thread (as you stated) and when the UI redraws the Activity is re-initialized. All long running processes should have their own thread. Rule of thumb: get out of the UI thread as soon as possible.
Here is an example of using a Service. This service will start when called and stay in memory regardless of screen orientation changes or even activity focus changes.
Here is the service:
public class Timer extends Service {
#Override
public IBinder onBind(Intent i) {
return null;
}
#Override
public int onStartCommand(Intent i, int flags, int startId) {
// Put your timer code here
}
}
You will need to add this line to your manifest (somewhere between the application open/close tags):
<service android:name=".Timer" />
Then you can start the service at any time by using this (it's important to note that you can start it over and over again, the recursive calls do not make a new service.):
startService(new Intent(this, Timer.class));
Use System.currentTimeMillis to get the current system time, then add 60000 milliseconds to the time and store that as an end time. Then any time you have to change anything, just compare System.currentTimeMillis to the EndTime.
Endtime = System.currentTimeMillis + 60000;
then on every instance
TimeRemaining = Endtime - System.currentTimeMillis
The accepted answer makes the thing very complex. You can do it in a simpler way. The problem in your code is that Activity gets created on rotation (see Activity Lifecycle), so does the CountdownTimer instance.
I could write the whole example code but there is a nice answer from #Drew here: Android Innerclass TextView reference when activity resumes