我创建了 2 个 Xamarin Android 应用程序。一个应用程序托管一个 Android 服务。另一个是客户端应用程序。假设仅调用 StartForegroundService 来启动服务。它不需要与服务通信或获取响应,因此不需要使用绑定的服务。
目前这只是 2 个测试应用程序。我在 Java 和 Android Studio 中创建了相同的应用程序。他们工作得很好。客户端在第二个应用程序中启动服务,没有问题。
Xamarin 口味不起作用。我 90% 确定问题出在 Xamarin Android 服务上,因为我的 Xamarin 客户端应用程序可以在 Java Android Studio 版本的服务中启动该服务。进一步测试还发现 Java android studio 客户端也无法启动 Xamarin android 服务。
因此,由于某种原因,当我在 Xamarin 中编写一个服务(尽可能相同)时,它无法由其他应用程序启动。但如果我用 Java 编写并使用 Android studio,它就可以正常工作。
这是我的服务应用程序的清单。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.androidservicet3" android:installLocation="auto">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="androidservicet3.AUTOSETTLE" />
<permission android:name="androidservicet3.AUTOSETTLE" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" android:name="android.app.Application" android:debuggable="true" android:extractNativeLibs="true">
<service android:exported="true" android:name="AndroidServiceT3.AutoSettleService" android:permission="androidservicet3.AUTOSETTLE">
<intent-filter>
<action android:name="androidservicet3.autosettle" />
</intent-filter>
</service>
<activity android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar" android:name="crc645835191c6c6c2b11.MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="crc64396a3fe5f8138e3f.KeepAliveService" />
<receiver android:enabled="true" android:exported="false" android:label="Essentials Battery Broadcast Receiver" android:name="crc64a0e0a82d0db9a07d.BatteryBroadcastReceiver" />
<receiver android:enabled="true" android:exported="false" android:label="Essentials Energy Saver Broadcast Receiver" android:name="crc64a0e0a82d0db9a07d.EnergySaverBroadcastReceiver" />
<receiver android:enabled="true" android:exported="false" android:label="Essentials Connectivity Broadcast Receiver" android:name="crc64a0e0a82d0db9a07d.ConnectivityBroadcastReceiver" />
<activity android:configChanges="orientation|screenSize" android:name="crc64a0e0a82d0db9a07d.IntermediateActivity" />
<provider android:authorities="com.companyname.androidservicet3.fileProvider" android:exported="false" android:grantUriPermissions="true" android:name="xamarin.essentials.fileProvider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/xamarin_essentials_fileprovider_file_paths" />
</provider>
<activity android:configChanges="orientation|screenSize" android:name="crc64a0e0a82d0db9a07d.WebAuthenticatorIntermediateActivity" />
<provider android:name="mono.MonoRuntimeProvider" android:exported="false" android:initOrder="1999999999" android:authorities="com.companyname.androidservicet3.mono.MonoRuntimeProvider.__mono_init__" />
</application>
</manifest>
这是我客户的清单:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.companyname.settleclient" android:installLocation="auto">
<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="33" />
<uses-permission android:name="androidservicet3.AUTOSETTLE" />
<uses-permission android:name="com.b2ps.servicetest2.autosettle" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme" android:name="android.app.Application" android:debuggable="true" android:extractNativeLibs="true">
<activity android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar" android:name="crc641e54c039a041334a.MainActivity" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="crc64396a3fe5f8138e3f.KeepAliveService" />
<receiver android:enabled="true" android:exported="false" android:label="Essentials Battery Broadcast Receiver" android:name="crc64a0e0a82d0db9a07d.BatteryBroadcastReceiver" />
<receiver android:enabled="true" android:exported="false" android:label="Essentials Energy Saver Broadcast Receiver" android:name="crc64a0e0a82d0db9a07d.EnergySaverBroadcastReceiver" />
<receiver android:enabled="true" android:exported="false" android:label="Essentials Connectivity Broadcast Receiver" android:name="crc64a0e0a82d0db9a07d.ConnectivityBroadcastReceiver" />
<activity android:configChanges="orientation|screenSize" android:name="crc64a0e0a82d0db9a07d.IntermediateActivity" />
<provider android:authorities="com.companyname.settleclient.fileProvider" android:exported="false" android:grantUriPermissions="true" android:name="xamarin.essentials.fileProvider">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/xamarin_essentials_fileprovider_file_paths" />
</provider>
<activity android:configChanges="orientation|screenSize" android:name="crc64a0e0a82d0db9a07d.WebAuthenticatorIntermediateActivity" />
<provider android:name="mono.MonoRuntimeProvider" android:exported="false" android:initOrder="1999999999" android:authorities="com.companyname.settleclient.mono.MonoRuntimeProvider.__mono_init__" />
</application>
</manifest>
服务是这样的:
[Service( Exported = true, Name = "AndroidServiceT3.AutoSettleService", Permission = "androidservicet3.AUTOSETTLE" )]
[IntentFilter( new String[] { "androidservicet3.autosettle" } )]
public class AutoSettleService : Android.App.Service
{
private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = Resource.String.autosettle_started;
private System.Threading.Timer serviceRun = null;
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private IBinder mBinder;
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder : Android.OS.Binder
{
private AutoSettleService mService;
public LocalBinder( AutoSettleService service ) { mService = service; }
AutoSettleService GetService() { return mService; }
}
public override void OnCreate()
{
mBinder = new LocalBinder( this );
mNM = ( NotificationManager )GetSystemService( Context.NotificationService );
// mLogger = Logger.getDefault();
// Display a notification about us starting. We put an icon in the status bar.
// ShowNotification();
}
public override void OnStart( Intent intent, int startId )
{
base.OnStart( intent, startId );
Android.Util.Log.Debug( "AUTOSETTLESERVICE", "AUTOSETTLESERVICE: OnStart" );
}
public override StartCommandResult OnStartCommand( Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId )
{
Android.Util.Log.Debug( "AUTOSETTLESERVICE", "Received start id " + startId + ": " + intent );
try
{
Intent notificationIntent = new Intent( this, typeof( MainActivity ) );
notificationIntent.SetAction( AutoSettleConst.AUTOSETTLESERVICE_ACTION );
PendingIntent pendingIntent = PendingIntent.GetActivity( this, 0, notificationIntent, PendingIntentFlags.Immutable );
Notification notification = new Notification.Builder( this, AutoSettleConst.APP_CHANNEL_TXN_ID )
.SetContentTitle( "Auto-Settle" )
.SetContentText( "Running Auto-Settle" )
.SetSmallIcon( Resource.Drawable.AppLogo)
.SetContentIntent( pendingIntent )
.SetTicker( "ticker text" )
.SetOngoing( true )
.Build();
// .SetForegroundServiceBehavior( ( int )NotificationForegroundService.Immediate )
// Notification ID cannot be 0.
StartForeground( AutoSettleConst.AUTOSETTLE_NOTIFICATION, notification );
serviceRun = new System.Threading.Timer( ServiceTimer, null, 0, 1000 );
// autoSettleTask = Task.Run( () => {
// Console.WriteLine( "Do auto settle" );
// AutoSettleTask();
// } );
}
catch( Exception e )
{
Android.Util.Log.Debug( "AUTOSETTLESERVICE", e.ToString() );
}
return StartCommandResult.Sticky;
}
public override void OnDestroy()
{
// Cancel the persistent notification.
mNM.Cancel( NOTIFICATION );
// Tell the user we stopped.
Android.Util.Log.Debug( "AUTOSETTLESERVICE", "Service destroyed" );
}
public override IBinder OnBind( Intent intent )
{
Android.Util.Log.Debug( "AUTOSETTLESERVICE", "AUTOSETTLESERVICE: binding" );
return mBinder;
}
public override bool OnUnbind( Intent intent )
{
Android.Util.Log.Debug( "AUTOSETTLESERVICE", "AUTOSETTLESERVICE: unbinding" );
return base.OnUnbind( intent );
}
private int _serviceCounter = 0;
private void ServiceTimer( object state )
{
Android.Util.Log.Debug( "AUTOSETTLESERVICE", $"AUTOSETTLESERVICE: ServiceTimer: {_serviceCounter++}" );
if( _serviceCounter >= 15 )
{
serviceRun.Dispose();
serviceRun = null;
this.StopSelf();
Android.Util.Log.Debug( "AUTOSETTLESERVICE", $"AUTOSETTLESERVICE: Stopping service" );
}
}
/**
* Show a notification while this service is running.
*/
private void ShowNotification()
{
// In this sample, we'll use the same text for the ticker and the expanded notification
string text = GetText( Resource.String.autosettle_started );
// The PendingIntent to launch our activity if the user selects this notification
// PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
// new Intent(this, LocalServiceActivities.Controller.class), 0);
Intent notificationIntent = new Intent( this, typeof( MainActivity ) );
PendingIntent contentIntent = PendingIntent.GetActivity( this, 0, notificationIntent, PendingIntentFlags.Immutable );
// Set the info for the views that show in the notification panel.
Notification notification = new Notification.Builder( this )
.SetSmallIcon( Resource.Drawable.AppLogo ) // the status icon
.SetTicker( text ) // the status text
// .SetWhen( System.currentTimeMillis() ) // the time stamp
.SetContentTitle( GetText( Resource.String.autosettle_label ) ) // the label of the entry
.SetContentText( text ) // the contents of the entry
.SetContentIntent( contentIntent ) // The intent to send when the entry is clicked
.Build();
// Send the notification.
mNM.Notify( NOTIFICATION, notification );
}
}
public class AutoSettleConst
{
public const string AUTOSETTLE_ACTION = "com.somecompany.terminalservice.autosettlets";
public const string AUTOSETTLENOTIFY_ACTION = "com.somecompany.terminalservice.autosettlenotify";
public const string AUTOSETTLESERVICE_ACTION = "com.somecompany.terminalservice.autosettleservice";
public const string AUTOSETTLE_TIMEOUT_ACTION = "com.somecompany.terminalservice.autosettletimeout";
// Android notification constants
public const string APP_CHANNEL_TXN = "Transaction ChannelXX";
public const string APP_CHANNEL_TXN_ID = "com.somecompany.terminalservice.txn";
public const string APP_CHANNEL_TXN_DESCRIPTION = "Transaction notifications";
public const int AUTOSETTLE_NOTIFICATION = 1001;
}
您需要在客户端应用程序的清单中添加类似的内容,因为它面向 API30+:
<queries>
<package android:name="com.companyname.androidservicet3" />
</queries>
然后您应该能够在其他应用程序中启动该服务:
var intent = new Intent();
intent.SetComponent(new ComponentName(
"com.companyname.androidservicet3",
"AndroidServiceT3.AutoSettleService"));
Application.Context.StartService(intent);