10

我正在编写一个计时器应用程序,该应用程序具有服务并每 30 秒发出一次哔声(实际上有一个下拉菜单会改变该时间)。

但是,当我让应用程序发出哔哔声时,哔哔声会持续很长时间并冻结应用程序,最终(大约 5 秒后)它完成,然后计时器赶上。为什么会这样?我该如何解决?这是我的代码:

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


}

本地服务.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;
    }
}

活动主.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>

如有必要,我可以添加我的 AndroidManifest。在调试的 AndroidStudio 上,当它发生时,它会为我提供以下信息:

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.

我应该在服务中发出哔哔声还是什么?

我要补充一点,我很肯定这是来自ToneGenerator,我已经将所有声音部分都注释掉了,只是离开了振动器,当它运行时没有问题。但是ToneGenerator两者Ringtone都导致了这个问题

4

3 回答 3

5

我实际上回答了我自己的问题,问题(在这种情况下)是我打电话太频繁了beep()

我的代码是:

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

但我真正想要的是在毫秒内进行计算。我导致调用beep()100 次的方式(代码每 10 毫秒更新一次)。

于 2017-02-03T01:14:28.737 回答
0

在你的方法中试试这个beep()(在后台线程上运行你的代码):

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允许正确和轻松地使用 UI 线程。

我应该在服务中发出哔哔声还是什么?

由于您在 中没有 UI 代码beep(),因此最好在其中进行操作,LocalService.java因为它更易于管理。

于 2017-02-02T01:04:16.533 回答
0

你可以使用线程:

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

因为你使用的是相同的哔声,所以只需要使用thread.start()来调用哔声方法:

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


        }
    ...
}
于 2017-02-02T03:11:13.693 回答