I have been trying to write a metronome in Android however I am finding really hard to sync the beats accurately using the Handler postdelayed method. I manage to achieve an accurate timing using a ScheduledThreadPoolExecutor, but the issue is that with the ScheduledThreadPoolExecutor I can't control the timing from within the run method and therefore I am forced to stop and start the scheduled job, which is not ideal. Is there a way to make the Handler postdelayed more accurate? or a way to reschedule the ScheduledThreadPoolExecutor without having to stop and start the thread?
My current code is as below:
public class Metronome extends Service implements Runnable
{
private Handler handler = new Handler();
private SoundPool soundPool;
private long interval;
private void initSoundPool()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
soundPool = new SoundPool.Builder()
.setMaxStreams(1)
.setAudioAttributes(new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.build();
} else
{
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
}
soundId = soundPool.load(context, R.raw.secondary_clave, 1);
}
#Override
public void run()
{
handler.postDelayed(this, interval);
soundPool.play(soundId, 1, 1, 0, 0, 1);
}
public void start()
{
handler.post(this);
}
#Override
public IBinder onBind(Intent intent)
{
return null;
}
}
With the ScheduledThreadPoolExecutor it's super accurate, however, I don't have control via the "interval" flag inside the run loop, so if I change the interval I have to terminate the executor and start a new one everytime I need to rechedule which is horrible.
public class Metronome extends Service implements Runnable
{
private SoundPool soundPool;
private long interval;
private ScheduledThreadPoolExecutor beatsPerBarExec;
private ScheduledFuture<?> futureThread;
private void initSoundPool()
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
{
soundPool = new SoundPool.Builder()
.setMaxStreams(1)
.setAudioAttributes(new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.build())
.build();
} else
{
soundPool = new SoundPool(1, AudioManager.STREAM_MUSIC, 0);
}
soundId = soundPool.load(context, R.raw.secondary_clave, 1);
}
#Override
public void run()
{
soundPool.play(soundId, 1, 1, 0, 0, 1);
}
public void start()
{
beatsPerBarExec = new ScheduledThreadPoolExecutor(1);
futureThread = beatsPerBarExec.scheduleAtFixedRate(this, 0, interval, TimeUnit.MILLISECONDS);
}
public void pause()
{
futureThread.cancel(false);
beatsPerBarExec.purge();
beatsPerBarExec = null;
}
#Override
public IBinder onBind(Intent intent)
{
return null;
}
}
You may be seeing the effects of drift.
Example: you want your Runnable to run every 200msec. You reschedule your Runnable in the run() method using postDelayed() and pass it 200msec as the delay. When the run() method is called the next time, it may not be exactly 200msec since the previous time. Perhaps it is 210msec. Now you reschedule your Runnable to run in another 200msec. This time the run() method may be called again after 210 msec, which means your sound plays 420msec since the first one, etc.
To eliminate drift, you need to determine the exact clock time you want the Runnable to run at, subtract the current time and use that in the call to postDelayed(). This will take into account any potential variance in the thread timing.
Be aware that when you call postDelayed() you are posting a Runnable to run on the main (UI) thread. This is the thread that handles all the UI updates and when your Runnable is ready to run it will just be queued to the main (UI) thread handler, and may not run immediately.
You can mitigate this problem by scheduling your Runnable to run on a background thread instead of the main (UI) thread. However this means your Runnable will be called on the background thread and I don't know if your other code (that plays the sound) needs to run on the main (UI) thread or not.
Related
I want to clear my shared preferance field phonenumber exactly at 12 am in broadcast receiver. How will I do that ?
Here is my code ...
#Override
public void onReceive(Context context, Intent intent) {
SharedPreferences prefs = context .getSharedPreferences("connect", Context.MODE_PRIVATE);
String username = prefs.getString("phonenumber", null ) ;
}
}```
Alarm Manager
The Alarm Manager holds a CPU wake lock as long as the alarm receiver's onReceive() method is executing.
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
synchronized public void run() {
preferences.edit().putString(key, "").apply()
}
}, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(1));
ScheduledThreadPoolExecutor.
You can use java.util.Timer or ScheduledThreadPoolExecutor (preferred) to schedule an action to occur at regular intervals on a background thread.
ScheduledExecutorService scheduler =
Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate
(new Runnable() {
public void run() {
// call the preferences clear logic
}
}, 0, 10, TimeUnit.MINUTES);
EDit:
You can actually save the install time and then do a calculation to see if a week has elapsed. If it has clear the shared preference
//First time
long installed = context
.getPackageManager()
.getPackageInfo(context.getPackageName(), 0)
.firstInstallTime;
Ref:More on periodic event handling
Get install time and clear shared preference
Hello There I am a newbie for Android Development, working to learn it to my own!
I just want to update my notification in a Java Thread in my application (I am just learning and curious about how can I do it).
I have an activity, a simple thread to increment an Integer value. Then, I just want to show it in my Notification whenever the Integer value increments!
My Code is as:
public class MainActivity extends Activity{
private final String LOG_KEY = this.getClass().getSimpleName();
private int c = 0;
private boolean flag = true;
private NotificationCompat.Builder builder;
private NotificationManager notificationManager;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
builder = new NotificationCompat.Builder(MainActivity.this)
.setSmallIcon(R.mipmap.ic_launcher)
.setAutoCancel(false);
builder.setOngoing(true);
notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
Thread t = new Thread(new MyThread());
t.start();
}//OnCreate ends
#Override
protected void onStop() {
super.onStop();
flag = false;
}//stop ends
#Override
protected void onDestroy() {
super.onDestroy();
flag = false;
}//destroy ends
private class MyThread implements Runnable {
#Override
public void run() {
while (flag) {
c+=1;
showNotification(Integer.toString(c) + " Counts");
}//while ends
}//run ends
private void showNotification(String msg) {
try {
//set the notification
builder.setContentText(msg);
notificationManager.notify(0, builder.build());
} catch (Exception exp) {
Log.e("xmn", exp.toString());
}//try catch ends
}//showNotification ends
}//private class ends
}//MainActivity class ends here
As from my code, the notification appears and updates the value! But the problem is that it freezes the device and application at a sudden!
I just want help for what I am doing wrong as I am a newbie and learning it to my own. Any help and idea will be highly appreciated!
Thanks
You shouldn't be creating a notification and then continuously updating it as fast as you can from a thread. It's really not designed for that.
The closest thing I can think of that would meet your use case is using a notification to display progress. See this link:
Displaying Progress in a Notification
You might want to put some kind a rate limiter in your thread, unless you want your count to reach very high numbers very quickly. Perhaps make the thread sleep for a second between updates.
The problem is that you produce too much notifications more then a device can consume.
For your goal (just learn) you can add a some pause between notifications like that:
private void showNotification(String msg) {
try {
//set the notification
Thread.sleep(1000); //set the pause
builder.setContentText(msg);
notificationManager.notify(0, builder.build());
} catch (Exception exp) {
Log.e("xmn", exp.toString());
}//try catch ends
}//showNotification ends
From the documentation of the cancel method:
(Loosely speaking, this method returns true if it prevents one or more scheduled executions from taking place.)
And when I execute this code:
private Timer timer = null;
private TimerTask runnable = new TimerTask() {
#Override
public void run() {
Log.e("DEBUG", "Scheduled task tick");
if(!looper){
Looper.prepare();
looper = true;
}
VolleyConnect vc = new VolleyConnect();
vc.connect(ErrorListActivity.this);
}
};
#Override
public void onWindowFocusChanged(boolean focus){
if(!focus){
kill();
}else{
start();
}
Log.e("DEBUG", "FOCUS " + focus);
}
public void start(){
if(timer == null){
timer = new Timer();
//180000 ms = 3 minutes
timer.scheduleAtFixedRate(runnable, 0L, 180000L);<--- This line is the one that triggers the error.
}
}
public void kill(){
if(timer != null) {
boolean rep = runnable.cancel();
Log.e("DEBUG", "REP = " + rep);
timer.cancel();
timer = null;
looper = false;
}
}
Unexpectedly, the debug statement prints:
E/DEBUG: REP = true
which shows that the cancel should be completed. However, when I regain window focus (and try to reschedule the timer), the app crashes with this error:
java.lang.IllegalStateException: Task already scheduled or cancelled
at java.util.Timer.sched(Timer.java:401)
at java.util.Timer.scheduleAtFixedRate(Timer.java:328)
at com.package.ErrorListActivity.start(ErrorListActivity.java:198)
at com.package.ErrorListActivity.onWindowFocusChanged(ErrorListActivity.java:189)
at android.support.v7.view.WindowCallbackWrapper.onWindowFocusChanged(WindowCallbackWrapper.java:128)
at android.support.v7.view.WindowCallbackWrapper.onWindowFocusChanged(WindowCallbackWrapper.java:128)
at com.android.internal.policy.DecorView.onWindowFocusChanged(DecorView.java:1414)
at android.view.View.dispatchWindowFocusChanged(View.java:10173)
at android.view.ViewGroup.dispatchWindowFocusChanged(ViewGroup.java:1192)
at android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:3757)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6120)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:865)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:755)
I have no clue how to fix this, and I have looked through the other similar questions but nothing has worked yet. Any ideas?
Incidentally, this is not a duplicate of this question. It is the same issue, but I call cancel and it returns true (meaning it should work, but it doesn't).
You can not reuse the timertask . Create new instance of TimerTask.
As Krish mentioned, you cannot reuse the TimerTask. If you want to, however, you can use Runnable instead of TimerTask and execute it with a ScheduledExecutorService. See this answer for an example.
I want to run some task (fetching data from database) in background after 5 minutes interval. What should I use?
Please mind that Google ask you to run long operations on Service. Please read the articles below, to detech what service do you need (service, interservice)!
Intent Service going to shut down itself after the job is done.
To fire a service in every 5mins to do the job , you can combine with a timer, as suggested above.
Mind before continue: Service belongs to the same thread, where you create it. So when you are about to developer your service please use a new Thread to start it. If you forget to do it, your service going to belong to the UI thread, mean you are in a trouble....
read first:
http://developer.android.com/guide/components/services.html
developer guide:
http://developer.android.com/reference/android/app/Service.html
You can use TimerTask inside a service
Timer timer = new Timer();
timer.schedule( new YourTask(), 50000 );
Try this.
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
//Do something
}
}, 0, 5000);
Use Async task:
pre Execute, do inBackground, Post Execute
With alarm Manager
Intent myIntent1 = new Intent(sign_in.this,MyNotificationService.class);
pendingintent2 = PendingIntent.getService(sign_in.this, 1,myIntent1, 1);
AlarmManager alarmManager1 = (AlarmManager) getSystemService(ALARM_SERVICE);
Calendar calendar1Notify = Calendar.getInstance();
calendar1Notify.setTimeInMillis(System.currentTimeMillis());
calendar.add(Calendar.SECOND, 20);
alarmManager1.set(AlarmManager.RTC_WAKEUP,calendar1Notify.getTimeInMillis(), pendingintent2);
long time = 300*1000;// 5 minutes repeat
alarmManager1.setInexactRepeating(AlarmManager.RTC_WAKEUP, calendar1Notify.getTimeInMillis(),time,pendingintent2);
Add Permission in manifest
<service android:name="com.example.MyNotificationService" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</service>
you can use a timer task:
TimerTask scanTask;
final Handler handler = new Handler();
Timer t = new Timer();
public void doTask(){
scanTask = new TimerTask() {
public void run() {
handler.post(new Runnable() {
public void run() {
//your task(fetch data)
}
});
}};
t.schedule(scanTask, 300000, 300000);
}
You can use timer, it is not a problem but method within android do have some advantages
private int mInterval = 5000; // 5 seconds by default, can be changed later
private Handler mHandler;
#Override
protected void onCreate(Bundle bundle) {
...
mHandler = new Handler();
}
Runnable mStatusChecker = new Runnable() {
#Override
public void run() {
updateStatus(); //this function can change value of mInterval.
mHandler.postDelayed(mStatusChecker, mInterval);
}
};
void startRepeatingTask() {
mStatusChecker.run();
}
void stopRepeatingTask() {
mHandler.removeCallbacks(mStatusChecker);
}
I have Service, that in onStart I'm calling to existing AsyncTask <> from new Thread.
public void onStart(Intent intent, int startId) {
Thread thread = new Thread() {
public void run() {
try {
//code that fetch data
}
myTask taskA = new myTask ();
taskA.execute();
}
};
thread.start();
}
After the work in taskA is finished, how he can inform Service so he can be stopped.
Firstly you should override both onStart and onStartCommand in a service to accommodate for all range of Android devices.
Regarding stopping a service from within you can you stopSelf() to do that.