ToneGenerator slows down app very heavily

1.3k views Asked by At

I'm writing a timer app, with a service and beeping every 30 seconds (actually there's a drop down that changes that time).

However when I make the app beep the beep lasts very long and freezes the app, eventually (after about 5 seconds) it finishes and then the timer catches up. Why is this happening? How do I fix this? Here is my code:

MainActivity.java:

package com.example.servicetimer;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.media.ToneGenerator;
import android.net.Uri;
import android.os.Vibrator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private Button startButton;
    private Button pauseButton;
    private Button resetButton;

    private TextView timerValue;
    private TextView timerValueMils;

    private long miliTime;

    private int beepTime = 0;

    private boolean running = false;
    private boolean beep = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        miliTime = 0L;
        timerValue = (TextView) findViewById(R.id.timerValue);
        timerValueMils = (TextView) findViewById(R.id.timerValueMils);

        registerReceiver(uiUpdated, new IntentFilter("TIMER_UPDATED"));

        startButton = (Button) findViewById(R.id.startButton);

        startButton.setOnClickListener(new View.OnClickListener() {

            public void onClick(View view) {
                if (running){
                    return;
                }
                Intent i = new Intent(MainActivity.this,LocalService.class);
                i.putExtra("timer",miliTime);

                startService(i);
                running = true;
                resetButton.setVisibility(View.GONE);
            }
        });

        pauseButton = (Button) findViewById(R.id.pauseButton);

        pauseButton.setOnClickListener(new View.OnClickListener() {

            public void onClick(View view) {
                if(!running){
                    return;
                }
                running = false;
                stopService(new Intent(MainActivity.this, LocalService.class));
                resetButton.setVisibility(View.VISIBLE);
            }
        });

        resetButton = (Button) findViewById(R.id.resetButton);

        resetButton.setOnClickListener(new View.OnClickListener() {

            public void onClick(View view) {
                stopService(new Intent(MainActivity.this, LocalService.class));
                running = false;
                miliTime = 0L;
                ((TextView) findViewById(R.id.timerValue)).setText(R.string.timerVal);
                ((TextView) findViewById(R.id.timerValueMils)).setText(R.string.timerValMils);
                beep = false;
            }
        });

        Spinner dropdown = (Spinner)findViewById(R.id.spinner1);
        ArrayAdapter<CharSequence> adapter = ArrayAdapter
                .createFromResource(this, R.array.times,
                        android.R.layout.simple_spinner_item);
        dropdown.setAdapter(adapter);
        dropdown.setSelection(1);

        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);

        dropdown.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {

            @Override
            public void onItemSelected(AdapterView<?> parent, View view,
                                       int position, long id) {
                // On selecting a spinner item
                String label = parent.getItemAtPosition(position).toString();
                beepTime = Integer.parseInt(label);
            }

            public void onNothingSelected(AdapterView<?> parent) {
                beepTime = 30;
            }
        });

    }

    private BroadcastReceiver uiUpdated = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            //This is the part where I get the timer value from the service and I update it every second, because I send the data from the service every second. The coundtdownTimer is a MenuItem
            miliTime = intent.getExtras().getLong("timer");
            long secs = miliTime/1000;
            int mins = (int) (secs/60);
            secs = secs % 60;
            if (secs > 0)
                beep = true;
            if ((secs % beepTime == 0) && beep)
                beep();
            int millis = (int) (miliTime % 1000);

            timerValue.setText("" + mins + "  "
                    + String.format("%02d", secs));
            timerValueMils.setText(String.format("%02d", millis/10));


        }

        public void beep(){
            /*try {
                Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
                Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
                r.play();
            } catch (Exception e) {
                e.printStackTrace();
            }*/

            final ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
            tg.startTone(ToneGenerator.TONE_PROP_BEEP,100);
            tg.stopTone();
            tg.release();
            /*ToneGenerator toneG = new ToneGenerator(AudioManager.STREAM_ALARM, 100);
            toneG.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, 200);*/
            Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
            // Vibrate for 500 milliseconds
            v.vibrate(500);
        }
    };


}

LocalService.java:

package com.example.servicetimer;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
import android.widget.Toast;

import java.util.Timer;
import java.util.TimerTask;

public class LocalService extends Service
{
    private static Timer timer;
    private Context ctx;
    private static long miliTime = 0;

    public IBinder onBind(Intent arg0)
    {
        return null;
    }

    public void onCreate()
    {
        timer = new Timer();
        super.onCreate();
        ctx = this;
        miliTime = 0;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        miliTime = intent.getExtras().getLong("timer");
        timer = new Timer();
        timer.scheduleAtFixedRate(new mainTask(), 0, 10);
        return START_STICKY;
    }

    private class mainTask extends TimerTask
    {
        public void run()
        {
            miliTime += 10;
            Intent i = new Intent("TIMER_UPDATED");
            i.putExtra("timer",miliTime);

            sendBroadcast(i);
        }
    }

    public void onDestroy() {
        super.onDestroy();
        timer.cancel();
        miliTime = 0L;
    }
}

activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:background="@drawable/silver"
    android:layout_height="match_parent" >

    <TextView
        android:id="@+id/timerValue"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/pauseButton"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="37dp"
        android:textSize="40sp"
        android:textColor="#000000"
        android:text="@string/timerVal" />

    <TextView
        android:id="@+id/timerValueMils"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/timerValue"
        android:layout_toEndOf="@+id/timerValue"
        android:layout_above="@+id/pauseButton"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="45dp"
        android:layout_marginLeft="10dp"
        android:layout_marginStart="10dp"
        android:textSize="20sp"
        android:textColor="#000000"
        android:text="@string/timerValMils" />

    <Button
        android:id="@+id/startButton"
        android:layout_width="90dp"
        android:layout_height="45dp"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_centerVertical="true"
        android:layout_marginLeft="38dp"
        android:layout_marginStart="38dp"
        android:text="@string/startButtonLabel" />

    <Button
        android:id="@+id/pauseButton"
        android:layout_width="90dp"
        android:layout_height="45dp"
        android:layout_alignBaseline="@+id/startButton"
        android:layout_alignBottom="@+id/startButton"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_marginRight="38dp"
        android:layout_marginEnd="38dp"
        android:text="@string/pauseButtonLabel" />

    <RelativeLayout android:id="@+id/dropdown"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/pauseButton"
        android:layout_marginTop="37dp">

        <TextView
            android:id="@+id/secondsToBeep"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="37dp"
            android:layout_marginStart="37dp"
            android:layout_marginEnd="20dp"
            android:layout_marginRight="20dp"
            android:textSize="30sp"
            android:textColor="#000000"
            android:text="@string/beeps" />

        <Spinner
            android:id="@+id/spinner1"
            android:dropDownWidth="80dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:background="@android:drawable/btn_dropdown"
            android:layout_alignParentRight="true"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="40dp"
            android:layout_marginRight="40dp"
            android:layout_marginLeft="40dp"
            android:layout_marginStart="40dp"
            android:spinnerMode="dropdown"
            android:popupBackground="@drawable/silver"/>
    </RelativeLayout>

    <Button
        android:id="@+id/resetButton"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_below="@id/dropdown"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:layout_marginTop="50dp"
        android:text="@string/resetButtonLabel"
        android:visibility="gone"/>

</RelativeLayout>

I can add my AndroidManifest if necessary. On AndroidStudio on the debug it gives me the following information when it happens:

I/Choreographer: Skipped 35 frames!  The application may be doing too much work on its main thread.
I/Choreographer: Skipped 175 frames!  The application may be doing too much work on its main thread.
I/Choreographer: Skipped 44 frames!  The application may be doing too much work on its main thread.

Should I be doing the beep in the service or something?

I'll add that I'm positive that this is from the ToneGenerator, I've commented all the sound parts out and just left the Vibrator and when it runs there's no problem. But the ToneGenerator and the Ringtone both caused this problem

3

There are 3 answers

0
user2386276 On BEST ANSWER

I actually answered my own question, the issue (in this case) was that I was calling beep() way too often.

My code was:

if ((secs % beepTime == 0) && beep)
    beep();

but what I really wanted was to do the computation on the milliseconds. The way I have resulted in calling beep() 100's of times (code updates every 10 ms).

0
ישו אוהב אותך On

you can use Thread:

Thread thread = new Thread(new Runnable() {
    public void run() {
      // your beep method
    });

Because you use the same beep sound, you only need to use thread.start() to call the beep method:

// make a new thread for beep only once.
Thread beepThread = new Thread(new Runnable() {
    public void run() {
      // call beep() method here
      beep();
    });


// move the beep method from BroadcastReceiver
public void beep() {
  // your code implementation.
  ...
}


private BroadcastReceiver uiUpdated = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            //This is the part where I get the timer value from the service and I update it every second, because I send the data from the service every second. The coundtdownTimer is a MenuItem
            miliTime = intent.getExtras().getLong("timer");
            long secs = miliTime/1000;
            int mins = (int) (secs/60);
            secs = secs % 60;
            if (secs > 0)
                beep = true;
            if ((secs % beepTime == 0) && beep)

                // Call the thread here.
                beepThread.start();

            int millis = (int) (miliTime % 1000);

            timerValue.setText("" + mins + "  "
                    + String.format("%02d", secs));
            timerValueMils.setText(String.format("%02d", millis/10));


        }
    ...
}
0
Jon Goodwin On

Try this in your beep() method (run your code on a background Thread):

AsyncTask.execute(new Runnable() {
   @Override
   public void run() {
            final ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
            tg.startTone(ToneGenerator.TONE_PROP_BEEP,100);
            tg.stopTone();
            tg.release();
            /*ToneGenerator toneG = new ToneGenerator(AudioManager.STREAM_ALARM, 100);
            toneG.startTone(ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD, 200);*/
            Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
            // Vibrate for 500 milliseconds
            v.vibrate(500);
   }
});

AsyncTask enables proper and easy use of the UI thread.

Should I be doing the beep in the service or something?

As you have no UI code in beep(), it would be better to do it in LocalService.java as it is easier to manage.