XamarinでAndroidの位置情報をバックグラウンドのServiceでとります
普通にLocationManager使っている。 世の中的にはFusedLocation使えってことなんだろうけどそうもいかない場合もきっとあるはず。
バックグラウンドで位置情報取り続けるには、 Activityクラスに加えてServiceクラス、BroadcastReceiverクラスが必要っぽい。
まずはActivityから。
public class MyActivity : Activity { private Intent ServiceIntent; protected override void OnCreate(Bundle savedInstanceState) { base.OnCreate(savedInstanceState); // ... ServiceIntent = new Intent(this, typeof(MyLocationService)); StartService(ServiceIntent); var receiver = new MyLocationReceiver(); RegisterReceiver(receiver, new IntentFilter()); var handler = new MyHandler(this); receiver.RegisterHandler(handler); } // ... public class MyHandler : Handler { private MyActivity Parent; public MyHandler(MyActivity parent) { Parent = parent; } public override void HandleMessage(Message msg) { var bundle = msg.Data; var latitude = bundle.GetDouble("latitude"); var longitude = bundle.GetDouble("longitude"); var message = string.Format("lat: {0}, lng: {1}, latitude, longitude"); System.Diagnostics.Debug.WriteLine(message); // 取得したlatitudeとlongitudeを使って好きな処理をする var loc = new MyLocation(latitude, longitude); // ... } } }
C#では何かを継承した匿名クラスを作れないみたいなので内部クラス作って対応してる。
MyLocationは簡単にこんな感じ。jsonにシリアライズしてサーバーとか他のActivityとかとやり取りしたいのでDataContract属性つけてる。
[DataCantract] public class MyLocation { [DataMember(Name = "latitude")] public double Latitude; [DataMember(Name = "longitude")] public double Longitude; public MyLocation(double latitude, double longitude) { Latitude = latitude; Longitude = longitude; } }
次はServiceクラス。
[Service] public class MyLocationService : Service { private static string TAG = "MyLocationService"; private LocationManager mLocationManager = null; private static int INTERVAL = 1000; private static float DISTANCE = 0f; public PendingIntent mPendingIntent; public override Android.OS.IBinder OnBind(Android.Content.Intent intent) { return null; } public override StartCommandResult OnStartCommand(Android.Content.Intent intent, StartCommandFlags flags, int startId) { mLocationManager = (LocationManager)getSystemService(Context.LocationService); var receiver = new Intent(this, typeof(MyLocationReceiver)); mPendingIntent = PendingIntent.GetBroadCast(this, 0, receiver, PendingintentFlags.UpdateCurrent); try { MLocationManager.RequestLocationUpdates(LocationManager.GpsProvider, INTERVAL, DISTANCE, mPendingIntent); } catch (Exception e) { Log.Debug(TAG, e.Message); } return StartCommandResult.Sticky; } public override void OnDestroy() { mLocationManager.RemoveUpdates(mPendingIntent); base.OnDestroy(); } }
Serviceに関してはほぼここ のソースを使わせて頂きました。
で、Receiver。
[BroadcastReceiver(Enabled = true, Exported = false)] public class MyLocationReceiver : BroadcastReceiver { public const string TAG = "MyLocationReceiver"; public static MyActivity.MyHandler Handler; public MyLocationReceiver() {} public override void OnReceive(Context context, Intent intent) { string locationKey = LocationManager.KeyLocationChanged; if (intent.HasExtra(locationKey)) { Location loc = (Location)intent.Extras.Get(locationKey); var location = new MyLocation(loc.Latitude, loc.Longitude); var task = Task.Run( async () => { // ハンドラーにメッセージ送ることでアクティビティ側で処理可能になる var msg = CreateLocationMessage(loc.Latitude, loc.Longitude); Handler.SendMessage(msg); // なんか非同期な処理をする。locationをjsonシリアライズしたりとかhttpで送ったりとか }); } } public void RegisterHandler(MyActivity.MyHandler handler) { Handler = handler; } private Message CreateLocationMessage(double latitude, double longitude) { var msg = new Message(); var data = new Bundle(); data.PutDouble("latitude", latitude); data.PutDouble("longitude", longitude); msg.Data = data; return msg; } }
ハンドラーに関してはここ を参考にさせていただきました。
正直何がどうなって動いているのかよくわからないが備忘のため書いておく。