繁体   English   中英

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

[英]android Bound Service gets frozen when screen is off

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

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

症状:

大多数情况是在我使用8秒钟的暂停时间并且应用程序处于后台模式(屏幕关闭)时发生的。 播放只是冻结。

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

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

调试信息:

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


为了防止我的应用程序被冻结,我在服务中获取了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(奥利奥)
  • Galaxy Tab A3(牛轧糖)
  • Galaxy A3(棒棒糖)

我彻底调查了此问题,并按照建议改用了“ 前台服务” ,从而完美地解决了我的问题。

经过棒棒糖牛轧糖奥利奥的测试。


前台服务方式

将以下方法放在您的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);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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