今回はXamarin.Androidで再起動後にアプリやサービスを実行する方法をご説明いたします。Xamarinの場合はAndroidManifest.xmlに記述するのではなく、クラスにAttribute(属性)を付けることで、ビルド時にAndroidManifest.xmlに追記してくれる機能があります。
前提条件
・Windows10
・Visual Studio 2015 Community Update3
・Xamarin 4.3.0.784 (NuGet Xamarin.Forms 2.3.4.224)
・macOS Sierra 10.12.4 / Xcode8.3.1 / Xamarin.iOS 10.4.0.123
1.バックグラウンドサービスを作成する
アプリとは別スレッドで動作するプログラムを作成します。ServiceまたはIntentServiceを継承します。ServiceAttributeのProcessに任意のプロセス名を指定することでアプリが実行されていなくてもサービス単体で起動する設定としています。尚、ServiceにIntentFilterを設定してはいけません。Android5 Lollipopで動作しなくなります。
BackgroundService.cs
[Service(Name = "com.CompanyName.AppName.BackgroundService", Process = ":TestProcess")]
public class BackgroundService : Service
{
public override IBinder OnBind(Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Android.Content.Intent intent, StartCommandFlags flags, int startId)
{
Task task = new Task(() =>
{
while (true)
{
System.Threading.Thread.Sleep(10000);
}
});
task.Start();
return StartCommandResult.Sticky;
}
public void StartBackgroundService()
{
//サービスを起動する
Intent serviceIntent = new Intent(this, typeof(BackgroundService));
serviceIntent.AddFlags(ActivityFlags.NewTask);
serviceIntent.SetPackage(this.PackageManager.GetPackageInfo(this.PackageName, 0).PackageName);
base.StartService(serviceIntent);
}
public override void OnDestroy()
{
base.OnDestroy();
//Killされてもサービスを再起動する。
this.StartBackgroundService();
}
}
XamarinではJavaと異なり、ServiceAttribute([Service]のこと)を記述することによりコンパイル時に「アプリ名.Android\obj\Debug\android\AndroidManifest.xml」へ以下のコードが挿入されます。
<service android:name="com.CompanyName.AppName.BackgroundService"/>
ただし、以下のように記述すると正しく動作しなくなりました。
[Service(Exported = true, IsolatedProcess = true)]
2.BroadcastReceiverで起動完了アナウンスを取得する
BroadcastReceiverを継承したクラスにてIntentFilterでIntent.ActionBootCompletedを設定するとスマホの再起動時に動作してくれます。
また、"android.intent.action.PACKAGE_ADDED"を追加するとアプリインストール時に実行してくれます。アプリの更新時には"MY_PACKGE_REPLACED"も必要です。"PACKAGE_REPLACED"だと他のアプリ更新時も反応してしまうのでしょう。
IntentFilterにはCategoryとかDataというプロパティもあるようですが、設定すると正しく受信できなくなります。
BootReceiver.cs
[BroadcastReceiver]
[IntentFilter(new[] { Intent.ActionBootCompleted,
"android.intent.action.QUICKBOOT_POWERON",
"com.htc.intent.action.QUICKBOOT_POWERON",
"android.intent.action.PACKAGE_INSTALL",
"android.intent.action.PACKAGE_ADDED"
Intent.ActionMyPackageReplaced //,
//Intent.ActionPackageReplaced
})]
public class BootReceiver : BroadcastReceiver
{
public BootReceiver() : base()
{
}
public override void OnReceive(Context context, Intent intent)
{
//アプリを起動する場合はこちら
//Intent activityIntent = new Intent(context, typeof(MainActivity));
//activityIntent.AddFlags(ActivityFlags.NewTask);
//context.StartActivity(activityIntent);
//サービスを起動する
Intent serviceIntent = new Intent(context, typeof(BackgroundService));
serviceIntent.AddFlags(ActivityFlags.NewTask);
serviceIntent.SetPackage(context.PackageManager.GetPackageInfo(context.PackageName, 0).PackageName);
context.StartService(serviceIntent);
}
}
XamarinではJavaと異なり、BroadcastReceiverAttribute([BroadcastReceiver]のこと)を記述することによりコンパイル時に「アプリ名.Android\obj\Debug\android\AndroidManifest.xml」へ以下のコードが挿入されます。(xmlファイルへの以下の記述は不要です。)
<receiver android:name="***********.BootReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.QUICKBOOT_POWERON"/>
<action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
<action android:name="android.intent.action.PACKAGE_INSTALL"/>
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.MY_PACKGE_REPLACED"/>
</intent-filter>
</receiver>
ただし、以下のように記述すると正しく動作しなくなりました。
[BroadcastReceiver(Enabled = true, Name = "com.CompanyName.AppName.BootReceiver", Exported = false, Permission = "android.permission.RECEIVE_BOOT_COMPLETED")]
また、BroadcastReceiver.OnReceiveはアプリをビルドして実行しただけでは動作しません。Adbコマンド等でOSを再起動した際にしか実行されませんので、お気を付けください。Adbコマンドについて詳しくは以下のURLよりご参考ください。
https://itblog.dynaspo.com/blog-entry-147.html
3.パーミッションを設定する
AndroidManifest.xml
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
尚、アプリを外部ストレージに保存されるとBroadcastReceiverが正しく受信できなくなります。以下のようにインストール場所を内部のみに設定しましょう。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.CompanyName.AppName" android:installLocation="internalOnly">
4.Android5 Lollipop 対応
Androidバージョン5 Lollipop では、Googleの仕様変更により、明示的インテントが求められます。インテントにパッケージ名称をセットしなければなりません。しかしながら、Android4ではセットするとサービスが起動しなくなりました。
よって、私の環境では以下のように処理分岐しています。
Intent serviceIntent = new Intent(context, typeof(BackgroundService));
if (Build.VERSION.SdkInt >= BuildVersionCodes.Lollipop &&
Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
{
//Android5 Lollipop対応
string packageName = context.PackageManager.GetPackageInfo(context.PackageName, 0).PackageName;
serviceIntent.SetPackage(packageName);
serviceIntent.SetClassName(context, packageName + ".BackgroundService");
}
else
{
serviceIntent.AddFlags(ActivityFlags.NewTask);
}
context.StartService(serviceIntent);
5.Android8 Oreo 対応
Android8 Oreo からバックグラウンドサービスが禁止となりました。よって、以下のようにソースを分岐する必要が出てきました。
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
context.StartForegroundService(serviceIntent);
}
else
{
context.StartService(serviceIntent);
}
また、StartForegroundServiceを実行後5秒以内にStartForegroundをコールすることが必須となりました。その為 OnStartCommand または OnCreate にて StartForeground を実装することが望ましいです。
BackgroundService.cs
using Android.Support.V4.App;
public override StartCommandResult OnStartCommand(Android.Content.Intent intent, StartCommandFlags flags, int startId)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
//Android8.0 Oreo 対応
this.RegisterForegroundService();
}
Task task = new Task(() =>
{
//必要なバックグラウンド処理
});
task.Start();
return StartCommandResult.Sticky;
}
void RegisterForegroundService()
{
NotificationManager manager = (NotificationManager)context.GetSystemService(Context.NotificationService);
//Andorid8.0 Oreo 以降の通知で必要なChannel
string channelId = "任意のID";
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
string channelNm = "任意の名称"; //Androidのアプリ設定画面に表示されます。
var importance = NotificationManager.ImportanceHigh;
NotificationChannel channel = new NotificationChannel(channelId, channelNm, importance);
channel.Description = "任意の説明";
manager.CreateNotificationChannel(channel);
}
var notification = new Notification.Builder(this)
.SetContentTitle(Resources.GetString(Resource.String.app_name))
.SetContentText("Started ForegroundService.")
.SetSmallIcon(Resource.Drawable.icon_alpha) //Android7.0対応
.SetColor(ActivityCompat.GetColor(Android.App.Application.Context, Resource.Color.notification_color)) //Android7.0対応
.SetOngoing(true)
.SetChannelId(channelId) //Android8.0対応
.Build();
// Enlist this instance of the service as a foreground service
this.StartForeground(99999, notification); //IDの値は任意
}
6.サンプルコード
ソースコードを以下のURLにアップしてありますので、ご参考ください。
https://file.blog.fc2.com/itblogdsi/download/samplesource/xamarin/BootReceiverTest.zip
参考URL
http://www.jsucupira.com/2015/06/09/android-start-service-on-bootup/
今回の方法ではサービス単体で起動しており、再起動するプログラムにしていますので、Killしてもサービスは動作し続けます。
また、一定間隔でバックグラウンドサービスを実行する方法については以下のURLにてご紹介しております。
https://itblog.dynaspo.com/blog-entry-234.html
最後までお読みいただきありがとうございます。
当ブログの内容をまとめた Xamarin逆引きメニュー は以下のURLからご覧になれます。
https://itblog.dynaspo.com/blog-entry-81.html
- 関連記事
-