前台服务不发送通知 (API33)

huangapple go评论63阅读模式
英文:

Foreground service doesn't send notifications (API33)

问题

我正在尝试在Java上构建一个Android前台服务。大部分情况下,它工作得很好,但在测试期间,我的手机上没有显示通知(Redmi Note 10 Pro,Android 13)。

请突出显示哪里出错了:

public class MyScannerService extends Service {

    private final int MY_SERVICE_ID = 1;
    public static final String MY_CHANNEL_ID = "SERVICE_CHANNEL_ID";
    public static final String MY_CHANNEL_NAME = "Service Channel";
    public static final String ACTION_START_SERVICE = "START_MY_SERVICE";
    public static final String ACTION_STOP_SERVICE = "STOP_MY_SERVICE";

    private AtomicBoolean _isServiceStarted = new AtomicBoolean(false);

    private Waiter _waiter = null;

    public MyScannerService () {
    }

    @Override
    public IBinder onBind(Intent intent) {
        throw new UnsupportedOperationException("onBind() called. Binding not supported");
    }

    @Override
    public void onCreate() {

        super.onCreate();

        Log.d(TAG, "onCreate(): The service has been created");

        _waiter = new Waiter();

        Notification notification = createNotification();
        startForeground(MY_SERVICE_ID, notification);
    }

    @Override
    public void onDestroy() {

        Log.d(TAG, "onDestroy(): The service has been destroyed");

        _isServiceStarted.set(false);
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (intent != null) {
            String action = intent.getAction();

            Log.d(TAG, "onStartCommand(): Intent requested action: " + action);

            switch (action) {
                case ACTION_START_SERVICE:
                    startService();
                    break;
                case ACTION_STOP_SERVICE:
                    stopService();
                    break;
                default:
                    Log.d(TAG, "onStartCommand(): ERROR! Unsupported action requested by the intent!");
            }
        } else {
            Log.d(TAG, "onStartCommand(): Null intent received (probably system restart)");
        }

        return START_STICKY;
    }

    private Notification createNotification() {

        NotificationChannel channel = new NotificationChannel(MY_CHANNEL_ID, MY_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
        channel.setDescription("My test channel");
        channel.enableLights(true);
        channel.setSound(null, null);
        channel.setShowBadge(true);
        channel.setLightColor(Color.BLUE);
        //channel.enableVibration(true);
        //channel.setVibrationPattern(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 });
        channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);

        NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.createNotificationChannel(channel);

        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
        Notification notification = new NotificationCompat.Builder(this, MY_CHANNEL_ID)
                .setContentTitle("My Service scream")
                .setContentText("Some important text...")
                .setTicker("My ticker")
                .setSmallIcon(R.drawable.ic_clock)          // Just vector icon in res/drawable (ic_clock.xml)
                .setContentIntent(pendingIntent)
                //.setOngoing(true)
                //.setAutoCancel(false)
                .build();

        return notification;
    }

    private void startService() {

        if (_isServiceStarted.get())
            return;

        _isServiceStarted.set(true);

        _waiter.waiterThread.start();

        Log.d(TAG, "startService(): Service started");
    }

    private void stopService() {

        if (!_isServiceStarted.get())
            return;

        try {
            stopForeground(STOP_FOREGROUND_REMOVE);
            stopSelf();
        } catch (Exception e) {
            Log.d(TAG, "stopService(): Exception occurred during service stopping: " + e.getMessage());
        }

        _isServiceStarted.set(false);
        Log.d(TAG, "stopService(): Service stopped");
    }
}

Waiter类很简单,它只是将消息写入系统日志并尝试发送更多通知:

private class Waiter implements Runnable {

    Thread waiterThread;

    Waiter() {
        waiterThread = new Thread(this, "Just a test thread to monitor service work");
    }

    public void run() {

        while (_isServiceStarted.get())
        {
            Log.d(TAG, "Waiter still waits...");

            Notification notification = createNotification();
            NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

            notificationManager.notify(1, notification);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端应用程序以以下方式启动服务:

private void runService() {

    Log.d(TAG, "Starting service");

    Intent serviceIntent = new Intent(this, MyScannerService.class);
    serviceIntent.setAction(MyScannerService.ACTION_START_SERVICE);
    startForegroundService(serviceIntent);

    Log.d(TAG, "Started");
}

private void stopService() {

    Log.d(TAG, "Stopping service");

    Intent serviceIntent = new Intent(this, MyScannerService.class);
    serviceIntent.setAction(MyScannerService.ACTION_STOP_SERVICE);
    startForegroundService(serviceIntent);

    Log.d(TAG, "Stopped");
}

清单文件如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.PerfTest"
        tools:targetApi="33">
        <service
            android:name=".MyScannerService"
            android:enabled="true"
            android:exported="false"></service>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Gradle文件如下:

plugins {
    id 'com.android.application'
}

android {
    namespace 'com.ifa.perftest'
    compileSdk 33

    defaultConfig {
        applicationId "com.ifa.perftest"
        minSdk 33
        targetSdk 33
        versionCode 1


<details>
<summary>英文:</summary>

I&#39;m trying to build an Android foreground service on Java. Mostly, it works well, but no notifications appear on my phone during tests (Redmi Note 10 Pro, Android 13).

Please, highlight what is done wrong:

public class MyScannerService extends Service {

private final int MY_SERVICE_ID = 1;
public static final String MY_CHANNEL_ID = &quot;SERVICE_CHANNEL_ID&quot;;
public static final String MY_CHANNEL_NAME = &quot;Service Channel&quot;;
public static final String ACTION_START_SERVICE = &quot;START_MY_SERVICE&quot;;
public static final String ACTION_STOP_SERVICE = &quot;STOP_MY_SERVICE&quot;;
private AtomicBoolean _isServiceStarted = new AtomicBoolean(false);
private Waiter _waiter = null;
public MyScannerService () {
}
@Override
public IBinder onBind(Intent intent) {
throw new UnsupportedOperationException(&quot;onBind() called. Binding not supported&quot;);
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, &quot;onCreate(): The service has been created&quot;);
_waiter = new Waiter();
Notification notification = createNotification();
startForeground(MY_SERVICE_ID, notification);
}
@Override
public void onDestroy() {
Log.d(TAG, &quot;onDestroy(): The service has been destroyed&quot;);
_isServiceStarted.set(false);
super.onDestroy();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null) {
String action = intent.getAction();
Log.d(TAG, &quot;onStartCommand(): Intent requested action: &quot; + action);
switch (action) {
case ACTION_START_SERVICE:
startService();
break;
case ACTION_STOP_SERVICE:
stopService();
break;
default:
Log.d(TAG, &quot;onStartCommand(): ERROR! Unsupported action requested by the intent!&quot;);
}
} else {
Log.d(TAG, &quot;onStartCommand(): Null intent received (probably system restart)&quot;);
}
return START_STICKY;
}
private Notification createNotification() {
NotificationChannel channel = new NotificationChannel(MY_CHANNEL_ID, MY_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
channel.setDescription(&quot;My test channel&quot;);
channel.enableLights(true);
channel.setSound(null, null);
channel.setShowBadge(true);
channel.setLightColor(Color.BLUE);
//channel.enableVibration(true);
//channel.setVibrationPattern(new long[] { 100, 200, 300, 400, 500, 400, 300, 200, 400 });
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.createNotificationChannel(channel);
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
Notification notification = new NotificationCompat.Builder(this, MY_CHANNEL_ID)
.setContentTitle(&quot;My Service scream&quot;)
.setContentText(&quot;Some important text...&quot;)
.setTicker(&quot;My ticker&quot;)
.setSmallIcon(R.drawable.ic_clock)          // Just vector icon in res/drawable (ic_clock.xml)
.setContentIntent(pendingIntent)
//.setOngoing(true)
//.setAutoCancel(false)
.build();
return notification;
}
private void startService() {
if (_isServiceStarted.get())
return;
_isServiceStarted.set(true);
_waiter.waiterThread.start();
Log.d(TAG, &quot;startService(): Service started&quot;);
}
private void stopService() {
if (!_isServiceStarted.get())
return;
try {
stopForeground(STOP_FOREGROUND_REMOVE);
stopSelf();
} catch (Exception e) {
Log.d(TAG, &quot;stopService(): Exception occurred during service stopping: &quot; + e.getMessage());
}
_isServiceStarted.set(false);
Log.d(TAG, &quot;stopService(): Service stopped&quot;);
}

}


Waiter class is trivial, it just writes messages to the system log and tries to send more notifications:
private class Waiter implements Runnable {
Thread waiterThread;
Waiter() {
waiterThread = new Thread(this, &quot;Just a test thread to monitor service work&quot;);
}
public void run() {
while (_isServiceStarted.get())
{
Log.d(TAG, &quot;Waiter still waits...&quot;);
Notification notification = createNotification();
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

Client app starts the service this way:
private void runService() {
Log.d(TAG, &quot;Starting service&quot;);
Intent serviceIntent = new Intent(this, MyScannerService.class);
serviceIntent.setAction(MyScannerService.ACTION_START_SERVICE);
startForegroundService(serviceIntent);
Log.d(TAG, &quot;Started&quot;);
}
private void stopService() {
Log.d(TAG, &quot;Stopping service&quot;);
Intent serviceIntent = new Intent(this, MyScannerService.class);
serviceIntent.setAction(MyScannerService.ACTION_STOP_SERVICE);
startForegroundService(serviceIntent);
Log.d(TAG, &quot;Stopped&quot;);
}

Manifest looks like this:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

&lt;uses-permission android:name=&quot;android.permission.ACCESS_FINE_LOCATION&quot; /&gt;
&lt;uses-permission android:name=&quot;android.permission.ACCESS_COARSE_LOCATION&quot; /&gt;
&lt;uses-permission android:name=&quot;android.permission.ACCESS_BACKGROUND_LOCATION&quot; /&gt;
&lt;uses-permission android:name=&quot;android.permission.FOREGROUND_SERVICE&quot; /&gt;
&lt;uses-permission android:name=&quot;android.permission.POST_NOTIFICATIONS&quot; /&gt;
&lt;application
android:allowBackup=&quot;true&quot;
android:dataExtractionRules=&quot;@xml/data_extraction_rules&quot;
android:fullBackupContent=&quot;@xml/backup_rules&quot;
android:icon=&quot;@mipmap/ic_launcher&quot;
android:label=&quot;@string/app_name&quot;
android:supportsRtl=&quot;true&quot;
android:theme=&quot;@style/Theme.PerfTest&quot;
tools:targetApi=&quot;33&quot;&gt;
&lt;service
android:name=&quot;.MyScannerService&quot;
android:enabled=&quot;true&quot;
android:exported=&quot;false&quot;&gt;&lt;/service&gt;
&lt;activity
android:name=&quot;.MainActivity&quot;
android:exported=&quot;true&quot;&gt;
&lt;intent-filter&gt;
&lt;action android:name=&quot;android.intent.action.MAIN&quot; /&gt;
&lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; /&gt;
&lt;/intent-filter&gt;
&lt;/activity&gt;
&lt;/application&gt;

</manifest>


and gradle:

plugins {
id 'com.android.application'
}

android {
namespace 'com.ifa.perftest'
compileSdk 33

defaultConfig {
applicationId &quot;com.ifa.perftest&quot;
minSdk 33
targetSdk 33
versionCode 1
versionName &quot;1.0&quot;
testInstrumentationRunner &quot;androidx.test.runner.AndroidJUnitRunner&quot;
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile(&#39;proguard-android-optimize.txt&#39;), &#39;proguard-rules.pro&#39;
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_9
targetCompatibility JavaVersion.VERSION_1_9
}

}

dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.firebase:firebase-crashlytics-buildtools:2.8.1'
implementation 'androidx.compose.ui:ui-graphics:1.1.1'
implementation 'com.google.android.gms:play-services-location:19.0.1'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
implementation("com.google.guava:guava:31.1-android")
}


So, the service creates and starts, as well as Waiter thread does, I get the next output:

D/ClientApp: Starting service
D/ClientApp: Started
D/MyService: onCreate(): The service has been created
D/MyService: onStartCommand(): Intent requested action: START_MY_SERVICE
D/MyService: startService(): Service started
D/MyService: Waiter still waits...
D/MyService: Waiter still waits...
D/MyService: Waiter still waits...
D/ClientApp: Stopping service
D/ClientApp: Stopped
D/MyService: onStartCommand(): Intent requested action: STOP_MY_SERVICE
D/MyService: stopService(): Service stopped
D/MyService: onDestroy(): The service has been destroyed

...but no any notifications got
</details>
# 答案1
**得分**: 2
只需手动处理权限请求。据我所知,这是 Android 13 的要求。
如果其他人需要,我使用了下面的代码:
```java
private ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
if (!isGranted)
logMessageEverywhere("警告:推送通知已禁用!", true);
});
....
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED)
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
英文:

It turned out it just needed the manual permissions request handling.
AFAIK it's Android 13 requirement.

If someone else needs, I used the next code:

private ActivityResultLauncher&lt;String&gt; requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -&gt; {
if (!isGranted)
logMessageEverywhere(&quot;WARNING: Push notifications disabled!&quot;, true);
});
....
....
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED)            
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);

huangapple
  • 本文由 发表于 2023年5月24日 23:07:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/76324982.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定