屏幕关闭时,Android绑定服务被冻结

时间:2018-10-28 14:29:05

标签: c# android xamarin.android text-to-speech

我创建了一个非常基本的服务,用于使用TTS播放字典(请参见下面的完整代码),并且在我的所有3个android设备(android版本)上都遇到了相同的问题5、7和8)。

要点: 该应用会播放词汇表条目,定义和示例。在每个应用之间,应用都会暂停。

症状:

当我使用8秒钟的暂停时间并且该应用处于后台模式(屏幕关闭)时,问题大多发生了。播放只会冻结。

  • 有时播放会继续自己的,并在长时间暂停后关闭屏幕,有时长达20-30分钟甚至更长(,但随后播放下一个条目如果我们还没有激活屏幕,也可以在很长的暂停之后进行。可能还有其他过程会部分唤醒电话吗?

  • 此外,在按电源按钮并打开屏幕后,播放将继续进行。

调试信息:

我估计应用程序被冻结后会在Visual Studio中按下暂停键,以查看是哪段代码引起的-不幸的是,调试器似乎使设备保持清醒状态,这个问题很难揭示< / strong>。


为了防止我的应用程序被冻结,我在服务中获得了 Partial WakeLock (但这仍然无济于事,即使应用程序清单包含对WAKE_LOCK的许可) >

private void AcquireWakeLock(MainActivity activity)
{
    var mgr = (PowerManager)activity.ApplicationContext.GetSystemService(Context.PowerService);
    WakeLock = mgr.NewWakeLock(WakeLockFlags.Partial, "myWakeLock");
    WakeLock.Acquire();
}

我的应用程序还具有“播放/暂停”按钮,我使用TaskCompletionSource来使应用程序等待直到恢复播放

public async Task PlayPause(bool isChecked, MainActivity mainActivity)
{
    if (isChecked)
    {
        ReleaseWakeLock();
        AppSuspended = new TaskCompletionSource<bool>();
        Tts.Stop();
    }
    else
    {
        AcquireWakeLock(mainActivity);
        AppSuspended.TrySetResult(true);
    }
}

然后,在即将播放下一个单词/词组之前,我将以下代码用于我的应用,以等待恢复播放

await AppSuspended.Task;

完整代码

[Service(Name = "com.my_app.service.PlaybackService")]
public class PlaybackService : Service, TextToSpeech.IOnInitListener, TextToSpeech.IOnUtteranceCompletedListener
{
    public IBinder Binder { get; private set; }
    private Java.Util.Locale Lang;
    private bool Playing;
    private int EntryIndex;
    private int DefinitionIndex;
    private DictionaryDto Dictionary;
    private EntryDto CurrentEntry;
    private DefinitionDto CurrentDefinition;
    private TaskCompletionSource<bool> AppSuspended;
    protected TextToSpeech Tts;
    private TaskCompletionSource<bool> PlaybackFinished;
    private WakeLock WakeLock;

    public override void OnCreate()
    {
        base.OnCreate();
        Tts = new TextToSpeech(this, this);
        Lang = Tts.DefaultLanguage;
        AppSuspended = new TaskCompletionSource<bool>();
        AppSuspended.TrySetResult(true);
    }

    public override IBinder OnBind(Intent intent)
    {
        Binder = new PlaybackBinder(this);
        return Binder;
    }

    public override bool OnUnbind(Intent intent)
    {
        return base.OnUnbind(intent);
    }

    public override void OnDestroy()
    {
        Binder = null;
        base.OnDestroy();
    }

    void TextToSpeech.IOnUtteranceCompletedListener.OnUtteranceCompleted(string utteranceId)
    {
        if (utteranceId.Equals("PlaybackFinished")) { PlaybackFinished.TrySetResult(true); }
    }

    void TextToSpeech.IOnInitListener.OnInit(OperationResult status)
    {
        // if we get an error, default to the default language
        if (status == OperationResult.Error)
            Tts.SetLanguage(Java.Util.Locale.Default);
        // if the listener is ok, set the lang
        if (status == OperationResult.Success)
        {
            Tts.SetLanguage(Lang);
            Tts.SetOnUtteranceCompletedListener(this);
        }
    }

    public async Task Play(string text)
    {
        Dictionary<string, string> myHashRender = new Dictionary<string, string>();
        myHashRender.Add(TextToSpeech.Engine.KeyParamUtteranceId, "PlaybackFinished");
        PlaybackFinished = new TaskCompletionSource<bool>();
        Tts.Speak(text, QueueMode.Flush, myHashRender);
        await PlaybackFinished.Task;
    }

    public async Task PlaySilence(long ms)
    {
        Dictionary<string, string> myHashRender = new Dictionary<string, string>();
        myHashRender.Add(TextToSpeech.Engine.KeyParamUtteranceId, "PlaybackFinished");
        PlaybackFinished = new TaskCompletionSource<bool>();
        Tts.PlaySilence(ms, QueueMode.Flush, myHashRender);
        await PlaybackFinished.Task;
    }

    private async Task PlayDictionary(MainActivity activity)
    {
        EntryIndex = 0;

        for (; EntryIndex < Dictionary.Entries.Count;)
        {
            CurrentEntry = Dictionary.Entries.ElementAt(EntryIndex);

            await AppSuspended.Task;

            if (!Playing) { return; }

            if (!string.IsNullOrEmpty(CurrentEntry.Text))
            {
                await AppSuspended.Task;
                if (!Playing) { return; }
                await Play(CurrentEntry.Text);
            }

            DefinitionIndex = 0;

            for (; DefinitionIndex < CurrentEntry.Definitions.Count();)
            {
                CurrentDefinition = CurrentEntry.Definitions.ElementAt(DefinitionIndex);

                await PlayDefinition();
                await PlayExamples();

                DefinitionIndex++;
            }

            if (Playing)
            {
                DefinitionIndex++;
            }

            EntryIndex++;
        }
    }

    private async Task PlayExamples()
    {
        if (!Playing) { return; }

        foreach (var example in CurrentDefinition.Examples)
        {
            if (!string.IsNullOrEmpty(example))
            {
                await AppSuspended.Task;

                if (!Playing) { return; }

                await Play(example);

                if (Playing)
                {
                    await PlaySilence((long)TimeSpan.FromSeconds(8).TotalMilliseconds);
                }
            }
        }
    }

    private async Task PlayDefinition()
    {
        if (!Playing) { return; }

        if (!string.IsNullOrEmpty(CurrentEntry.Definitions.ElementAt(DefinitionIndex).Text))
        {
            await AppSuspended.Task;

            if (!Playing) { return; }

            await PlayDefinitionText();

            if (Playing)
            {
                await PlaySilence((long)TimeSpan.FromSeconds(7).TotalMilliseconds);
            }
        }
    }

    private async Task PlayDefinitionText()
    {
        await AppSuspended.Task;

        await Play($"{CurrentEntry.Definitions.ElementAt(DefinitionIndex).Text}");
    }

    private void ReleaseWakeLock()
    {
        if (WakeLock != null)
        {
            WakeLock.Release();
        }
    }

    private void AcquireWakeLock(MainActivity activity)
    {
        var mgr = (PowerManager)activity.ApplicationContext.GetSystemService(Context.PowerService);
        WakeLock = mgr.NewWakeLock(WakeLockFlags.Partial, "myWakeLock");
        WakeLock.Acquire();
    }

    public async Task PlayPause(bool isChecked, MainActivity mainActivity)
    {
        if (isChecked)
        {
            ReleaseWakeLock();
            AppSuspended = new TaskCompletionSource<bool>();
            Tts.Stop();
        }
        else
        {
            AcquireWakeLock(mainActivity);
            AppSuspended.TrySetResult(true);
        }
    }

}

其他信息:

问题在我所有的设备上发生

  • Galaxy C7(Oreo)
  • Galaxy Tab A3(牛轧糖)
  • Galaxy A3(棒棒糖)

1 个答案:

答案 0 :(得分:0)

我对问题进行了彻底调查,并按照建议改用前台服务,这完美地解决了我的问题。

使用棒棒糖牛轧糖奥利奥进行了测试。


前台服务方式

将以下方法放在您的MainActivity类中

public void StartForegroundServiceSafely(Intent intent)
{
    if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
    {
        StartForegroundService(intent);
    }
    else
    {
        StartService(intent);
    }
}

然后您通过Intent

启动服务
public void PlayFromFile(Android.Net.Uri uri)
{
    AcquireWakeLock();

    Intent startIntent = new Intent(this, typeof(PlaybackService));
    startIntent.SetAction(PlaybackConsts.Start);
    startIntent.PutExtra("uri", uri.ToString());

    StartForegroundServiceSafely(startIntent);
}

在您的服务中实施OnStartCommand方法

public class PlaybackService : Service, TextToSpeech.IOnInitListener, TextToSpeech.IOnUtteranceCompletedListener

    [return: GeneratedEnum]
    public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
    {
        if (intent.Action.Equals(PlaybackConsts.Start))
        {
            var notification =
                new Notification.Builder(this)
                .SetContentTitle(Resources.GetString(Resource.String.ApplicationName))
                .SetContentText("HELLO WORLD")
                .SetOngoing(true)
                .Build();

            StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
        }

        if (intent.Action.Equals(PlaybackConsts.Start))
        {
            var uri = Android.Net.Uri.Parse(intent.GetStringExtra("uri"));
            var content = MiscellaneousHelper.GetTextFromStream(ContentResolver.OpenInputStream(uri));
            Dictionary = DictionaryFactory.Get(content);
            Playing = true;

            Task.Factory.StartNew(async () =>
            {
                await PlayDictionary();
            });
        }
        if (intent.Action.Equals(PlaybackConsts.PlayPause))
        {
            bool isChecked = intent.GetBooleanExtra("isChecked", false);
            PlayPause(isChecked);
        }

        if (intent.Action.Equals(PlaybackConsts.NextEntry))
        {
            NextEntry();
        }

        if (intent.Action.Equals(PlaybackConsts.PrevEntry))
        {
            PrevEntry();
        }

        if (intent.Action.Equals(PlaybackConsts.Stop))
        {
            Task.Factory.StartNew(async () =>
            {
                await Stop();
            });

            StopForeground(true);
            StopSelf();
        }

        return StartCommandResult.Sticky;
    }

从上面的代码中,我们学习了如何通过OnStartCommand方法触发服务的功能。

如何从服务中广播事件

定义您的BroadcastReceiver

[BroadcastReceiver(Enabled = true, Exported = false)]
public class PlaybackBroadcastReceiver : BroadcastReceiver
{
    public override void OnReceive(Context context, Intent intent)
    {
        var activity = MainActivity.GetInstance(); // if you need your activity here, see further code below

        if (intent.Action == "renderEntry")
        {
            string entryHtml = intent.GetStringExtra("html");

            // omitting code to keep example concise
        }
    }
}

在您的MainActivity类中声明接收方字段。

还可以在BroadcastReceiver类中进行活动,可以声明GetInstance方法(单例方法)。

public class MainActivity : AppCompatActivity
{
    PlaybackBroadcastReceiver receiver;

    protected DrawerLayout drawerLayout;
    protected NavigationView navigationView;

    protected WakeLock WakeLock;

    private static MainActivity instance;

    public static MainActivity GetInstance()
    {
        return instance;
    }

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        receiver = new PlaybackBroadcastReceiver();

        instance = this;
    }

    protected override void OnStart()
    {
        base.OnStart();
        RegisterReceiver(receiver, new IntentFilter("renderEntry"));
    }

要取消注册接收者,请使用以下行:

UnregisterReceiver(receiver);

广播来自服务的事件

在服务中,您还必须使用意图

private void SendRenderEntryBroadcast(EntryDto entry)
{
    Intent intent = new Intent("renderEntry");
    intent.PutExtra("html", GetEntryHtml(entry));
    SendBroadcast(intent);
}