简体   繁体   中英

ToneGenerator slows down app very heavily

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

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).

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.

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));


        }
    ...
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM