簡體   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