Xamarin.Forms楽しいです。ここ最近毎日触ってます。
先日からタイマーのアプリを作っていまして、そこでとある現象にぶち当たりました。
- タイマー起動中に画面がスリープモードに移行する
スリープ状態になると当然タイマーは止まります。
そんなアプリはタイマーアプリではありません。困りました。
タイマー起動中に放置しててもスリープ状態にさせないために設定をしていきます。
PCL
今回はスリープしないモードとスリープするモードをメソッド呼び出して切り替えられるように作りたいと思います。
スリープ云々の設定はiOSやAndroid側に書くことになるので、それらを呼び出すinterfaceを作ってやります。
public interface ISleepScreen
{
void SleepDisabled();
void SleepEnabled();
}
iOS
iOSの場合、UIApplication.SharedApplication.IdleTimerDisabled
を変更することで実現可能だそうです。
true
でスリープしない状態、false
でスリープする状態となります。
using UIKit;
[assembly: Dependency(typeof(SleepScreen))]
namespace Timer.iOS
{
public class SleepScreen : ISleepScreen
{
public void SleepDisabled()
{
UIApplication.SharedApplication.IdleTimerDisabled = true;
}
public void SleepEnabled()
{
UIApplication.SharedApplication.IdleTimerDisabled = false;
}
}
}
Android
OnCreate
でWindow.AddFlags(WindowManagerFlags.KeepScreenOn);
するとスリープを抑制できるようです。
namespace Timer.Droid
{
[Activity(Label = "Timer", Icon = "@drawable/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
Window.AddFlags(WindowManagerFlags.KeepScreenOn);
}
}
}
スリープ抑制をメソッド呼び出しで管理できてなくない?
できてない。
だけれども調べたところどうやら非推奨らしい。
http://monobook.org/wiki/Xamarin.Android/%E3%83%87%E3%82%A3%E3%82%B9%E3%83%97%E3%83%AC%E3%82%A4%E3%82%92%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%97%E3%81%95%E3%81%9B%E3%81%AA%E3%81%84
解決策:wakelock
この方法は権限が必要であり、ひとたびディスプレイのスリープを抑制するとアクティビティが遷移しようが何をしようが明示的に解除しない限り延々と抑制される。あまりオススメできない。 よって省略する。
でも制御したいよね
wakelock
をちょっと調べてみた。
https://developer.xamarin.com/api/type/Android.OS.PowerManager/
https://developer.xamarin.com/api/type/Android.OS.PowerManager+WakeLock/
https://developer.xamarin.com/api/member/Android.OS.PowerManager.NewWakeLock/p/Android.OS.WakeLockFlags/System.String/
wekelock
をnewWakeLock
で作成したら、それに対してacqure()
とrelease()
を行う必要があるみたい。
GetSystemService
がどこにあるのか最初わからず苦労しました。
using Android.OS;
using Android.Content;
using Xamarin.Forms;
[assembly: Dependency(typeof(SleepScreen))]
namespace Timer.Droid
{
public class SleepScreen : ISleepScreen
{
PowerManager.WakeLock wl;
public void SleepDisabled()
{
if (wl != null) return;
PowerManager pm = (PowerManager)(Forms.Context.GetSystemService(Context.PowerService));
wl = pm.NewWakeLock(WakeLockFlags.ScreenBright | WakeLockFlags.OnAfterRelease, "My Tag");
wl.Acquire();
}
public void SleepEnabled()
{
if (wl != null)
{
wl.Release();
wl = null;
}
}
}
}
Acqure()
とRelease()
を別メソッドで呼び出す以上、同一インスタンスを使い続ける必要があります。
DependencyService.Get<T>()
で呼び出すときは以下のようにオプションをつけましょう。
DependencyService.Get<ISleepScreen>(DependencyFetchTarget.GlobalInstance).SleepDisabled();
DependencyService.Get<ISleepScreen>(DependencyFetchTarget.GlobalInstance).SleepEnabled();
DependencyFetchTarget.GlobalInstance
を指定することで同じインスタンスで取得できます。
https://developer.xamarin.com/api/type/Xamarin.Forms.DependencyFetchTarget/
また、wakelockを使用するには権限が必要です。
Timer.Android/Properties/AndroidManifest.xml
で権限を与えることができます。
必要なアクセス許可
でWakeLock
を指定しましょう。
これでSleepDisabled()
とSleepEnabled()
でスリープ状態の制御ができるようになりました。
おわりに
iOSはあれでいいとして、Androidがなんだかややこしいですね。
Acquire()
を呼び出したらRelease()
も呼び出す必要があるので、もろもろ考えると素直にwakelock
ではなくKeepScreenOn
を使うのがよさそうな感じもします。どうするのが正解かはよくわからないです。
今回のような形で対応できることがわかったので、必要な際はこのように実装することにします。
明日は kmy72 さんです。よろしくお願いします。