startForeground 的错误通知:服务通知的通道无效

Posted

技术标签:

【中文标题】startForeground 的错误通知:服务通知的通道无效【英文标题】:Bad notification for startForeground: invalid channel for service notification 【发布时间】:2020-12-08 21:25:30 【问题描述】:

这个问题是针对 androidX 而不是 Android 8。我正在尝试使用 Google Transport Tracker Demo app。

由于项目的版本较低,我将其迁移到 AndroidX。我在已弃用的 Google Api 客户端上遇到问题。我已经尝试过其他 Stack Overflow 问题,但它们对我来说不太奏效。

当我在我的设备上安装应用程序时,它会因以下错误而崩溃:

startForeground 的错误通知:java.lang.RuntimeException:服务通知的通道无效

mGoogleApiClient = new GoogleApiClient.Builder 显示为已弃用

build.gradle

apply plugin: 'com.android.application'

android 
    compileSdkVersion 28
    buildToolsVersion "28"

    defaultConfig 
        applicationId "com.google.transporttracker"
        minSdkVersion 23
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        resValue "string", "build_transport_id", (project.findProperty("build_transport_id") ?: "")
        resValue "string", "build_email", (project.findProperty("build_email") ?: "")
        resValue "string", "build_password", (project.findProperty("build_password") ?: "")
    

    packagingOptions 
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE-FIREBASE.txt'
        exclude 'META-INF/NOTICE'
    


ext 
    support = '29'
    playServices = '10.2.4'


dependencies 
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.gms:play-services-maps:17.0.0'
    testImplementation 'junit:junit:4.12'
    implementation 'androidx.appcompat:appcompat:1.0.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'com.google.android.material:material:1.0.0'
    implementation "com.google.android.gms:play-services-gcm:17.0.0"
    implementation "com.google.android.gms:play-services-location:17.0.0"
    implementation "com.google.firebase:firebase-auth:19.3.2"
    implementation "com.google.firebase:firebase-config:19.2.0"
    implementation "com.google.firebase:firebase-database:19.3.1"
    implementation 'com.android.support:design:28.0.0-alpha3'
    implementation 'com.google.android.gms:play-services-location:17.0.0'
    implementation "com.google.android.gms:play-services-base:17.0.0"



apply plugin: 'com.google.gms.google-services'

Trackerservice.java

package com.google.transporttracker;

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.gcm.GcmNetworkManager;
import com.google.android.gms.gcm.OneoffTask;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.analytics.FirebaseAnalytics;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.location.Location;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.IBinder;
import android.os.PowerManager;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import android.util.Log;
import android.widget.Toast;

import java.io.File;
import java.io.FileWriter;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.LinkedList;

public class TrackerService extends Service implements LocationListener 

    private static final String TAG = TrackerService.class.getSimpleName();
    public static final String STATUS_INTENT = "status";

    private static final int NOTIFICATION_ID = 1;
    private static final int FOREGROUND_SERVICE_ID = 1;
    private static final int CONFIG_CACHE_EXPIRY = 600;  // 10 minutes.

    private GoogleApiClient mGoogleApiClient;
    private DatabaseReference mFirebaseTransportRef;
    private FirebaseRemoteConfig mFirebaseRemoteConfig;
    private LinkedList<Map<String, Object>> mTransportStatuses = new LinkedList<>();
    private NotificationManager mNotificationManager;
    private NotificationCompat.Builder mNotificationBuilder;
    private PowerManager.WakeLock mWakelock;

    private SharedPreferences mPrefs;

    public TrackerService() 
    

    @Override
    public IBinder onBind(Intent intent) 
        return null;
    

    @Override
    public void onCreate() 
        super.onCreate();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
            buildNotification();
        else
            startForeground(1, new Notification());

        setStatusMessage(R.string.connecting);

        mFirebaseRemoteConfig = FirebaseRemoteConfig.getInstance();
        FirebaseRemoteConfigSettings configSettings = new FirebaseRemoteConfigSettings.Builder()
                .setDeveloperModeEnabled(BuildConfig.DEBUG)
                .build();
                mFirebaseRemoteConfig.setConfigSettings(configSettings);
        mFirebaseRemoteConfig.setDefaults(R.xml.remote_config_defaults);

        mPrefs = getSharedPreferences(getString(R.string.prefs), MODE_PRIVATE);
        String email = mPrefs.getString(getString(R.string.email), "");
        String password = mPrefs.getString(getString(R.string.password), "");
        authenticate(email, password);

    

    @Override
    public void onDestroy() 
        // Set activity title to not tracking.
        setStatusMessage(R.string.not_tracking);
        // Stop the persistent notification.
        mNotificationManager.cancel(NOTIFICATION_ID);
        // Stop receiving location updates.
        if (mGoogleApiClient != null) 
            LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient,
                    TrackerService.this);
        
        // Release the wakelock
        if (mWakelock != null) 
            mWakelock.release();
        
        super.onDestroy();
    

    private void authenticate(String email, String password) 
        final FirebaseAuth mAuth = FirebaseAuth.getInstance();
        mAuth.signInWithEmailAndPassword(email, password)
                .addOnCompleteListener(new OnCompleteListener<AuthResult>()
                    @Override
                    public void onComplete(Task<AuthResult> task) 
                        Log.i(TAG, "authenticate: " + task.isSuccessful());
                        if (task.isSuccessful()) 
                            fetchRemoteConfig();
                            loadPreviousStatuses();
                         else 
                            Toast.makeText(TrackerService.this, R.string.auth_failed,
                                    Toast.LENGTH_SHORT).show();
                            stopSelf();
                        
                    
                );
    

    private void fetchRemoteConfig() 
        long cacheExpiration = CONFIG_CACHE_EXPIRY;
        if (mFirebaseRemoteConfig.getInfo().getConfigSettings().isDeveloperModeEnabled()) 
            cacheExpiration = 0;
        
        mFirebaseRemoteConfig.fetch(cacheExpiration)
                .addOnSuccessListener(new OnSuccessListener<Void>() 
                    @Override
                    public void onSuccess(Void aVoid) 
                        Log.i(TAG, "Remote config fetched");
                        mFirebaseRemoteConfig.activateFetched();
                    
                );
    

    /**
     * Loads previously stored statuses from Firebase, and once retrieved,
     * start location tracking.
     */
    private void loadPreviousStatuses() 
        String transportId = mPrefs.getString(getString(R.string.transport_id), "");
        FirebaseAnalytics.getInstance(this).setUserProperty("transportID", transportId);
        String path = getString(R.string.firebase_path) + transportId;
        mFirebaseTransportRef = FirebaseDatabase.getInstance().getReference(path);
        mFirebaseTransportRef.addListenerForSingleValueEvent(new ValueEventListener() 
            @Override
            public void onDataChange(DataSnapshot snapshot) 
                if (snapshot != null) 
                    for (DataSnapshot transportStatus : snapshot.getChildren()) 
                        mTransportStatuses.add(Integer.parseInt(transportStatus.getKey()),
                                (Map<String, Object>) transportStatus.getValue());
                    
                
                startLocationTracking();
            

            @Override
            public void onCancelled(DatabaseError error) 
                // TODO: Handle gracefully
            
        );
    

    private GoogleApiClient.ConnectionCallbacks mLocationRequestCallback = new GoogleApiClient
            .ConnectionCallbacks() 

        @Override
        public void onConnected(Bundle bundle) 
            LocationRequest request = new LocationRequest();
            request.setInterval(mFirebaseRemoteConfig.getLong("LOCATION_REQUEST_INTERVAL"));
            request.setFastestInterval(mFirebaseRemoteConfig.getLong
                    ("LOCATION_REQUEST_INTERVAL_FASTEST"));
            request.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
            LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,
                    request, TrackerService.this);
            setStatusMessage(R.string.tracking);

            // Hold a partial wake lock to keep CPU awake when the we're tracking location.
            PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
            mWakelock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakelockTag");
            mWakelock.acquire();
        

        @Override
        public void onConnectionSuspended(int reason) 
            // TODO: Handle gracefully
        
    ;

    /**
     * Starts location tracking by creating a Google API client, and
     * requesting location updates.
     */
    private void startLocationTracking() 
        mGoogleApiClient = new GoogleApiClient.Builder(this)
                .addConnectionCallbacks(mLocationRequestCallback)
                .addApi(LocationServices.API)
                .build();
        startForegroundService()
        mGoogleApiClient.connect();
    

    /**
     * Determines if the current location is approximately the same as the location
     * for a particular status. Used to check if we'll add a new status, or
     * update the most recent status of we're stationary.
     */
    private boolean locationIsAtStatus(Location location, int statusIndex) 
        if (mTransportStatuses.size() <= statusIndex) 
            return false;
        
        Map<String, Object> status = mTransportStatuses.get(statusIndex);
        Location locationForStatus = new Location("");
        locationForStatus.setLatitude((double) status.get("lat"));
        locationForStatus.setLongitude((double) status.get("lng"));
        float distance = location.distanceTo(locationForStatus);
        Log.d(TAG, String.format("Distance from status %s is %sm", statusIndex, distance));
        return distance < mFirebaseRemoteConfig.getLong("LOCATION_MIN_DISTANCE_CHANGED");
    

    private float getBatteryLevel() 
        Intent batteryStatus = registerReceiver(null,
                new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
        int batteryLevel = -1;
        int batteryScale = 1;
        if (batteryStatus != null) 
            batteryLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, batteryLevel);
            batteryScale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, batteryScale);
        
        return batteryLevel / (float) batteryScale * 100;
    

    private void logStatusToStorage(Map<String, Object> transportStatus) 
        try 
            File path = new File(Environment.getExternalStoragePublicDirectory(""),
                    "transport-tracker-log.txt");
            if (!path.exists()) 
                path.createNewFile();
            
            FileWriter logFile = new FileWriter(path.getAbsolutePath(), true);
            logFile.append(transportStatus.toString() + "\n");
            logFile.close();
         catch (Exception e) 
            Log.e(TAG, "Log file error", e);
        
    

    private void shutdownAndScheduleStartup(int when) 
        Log.i(TAG, "overnight shutdown, seconds to startup: " + when);
        com.google.android.gms.gcm.Task task = new OneoffTask.Builder()
                .setService(TrackerTaskService.class)
                .setExecutionWindow(when, when + 60)
                .setUpdateCurrent(true)
                .setTag(TrackerTaskService.TAG)
                .setRequiredNetwork(com.google.android.gms.gcm.Task.NETWORK_STATE_ANY)
                .setRequiresCharging(false)
                .build();
        GcmNetworkManager.getInstance(this).schedule(task);
        stopSelf();
    

    /**
     * Pushes a new status to Firebase when location changes.
     */
    @Override
    public void onLocationChanged(Location location) 

        fetchRemoteConfig();

        long hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        int startupSeconds = (int) (mFirebaseRemoteConfig.getDouble("SLEEP_HOURS_DURATION") * 3600);
        if (hour == mFirebaseRemoteConfig.getLong("SLEEP_HOUR_OF_DAY")) 
            shutdownAndScheduleStartup(startupSeconds);
            return;
        

        Map<String, Object> transportStatus = new HashMap<>();
        transportStatus.put("lat", location.getLatitude());
        transportStatus.put("lng", location.getLongitude());
        transportStatus.put("time", new Date().getTime());
        transportStatus.put("power", getBatteryLevel());

        if (locationIsAtStatus(location, 1) && locationIsAtStatus(location, 0)) 
            // If the most recent two statuses are approximately at the same
            // location as the new current location, rather than adding the new
            // location, we update the latest status with the current. Two statuses
            // are kept when the locations are the same, the earlier representing
            // the time the location was arrived at, and the latest representing the
            // current time.
            mTransportStatuses.set(0, transportStatus);
            // Only need to update 0th status, so we can save bandwidth.
            mFirebaseTransportRef.child("0").setValue(transportStatus);
         else 
            // Maintain a fixed number of previous statuses.
            while (mTransportStatuses.size() >= mFirebaseRemoteConfig.getLong("MAX_STATUSES")) 
                mTransportStatuses.removeLast();
            
            mTransportStatuses.addFirst(transportStatus);
            // We push the entire list at once since each key/index changes, to
            // minimize network requests.
            mFirebaseTransportRef.setValue(mTransportStatuses);
        

        if (BuildConfig.DEBUG) 
            logStatusToStorage(transportStatus);
        

        NetworkInfo info = ((ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE))
                .getActiveNetworkInfo();
        boolean connected = info != null && info.isConnectedOrConnecting();
        setStatusMessage(connected ? R.string.tracking : R.string.not_tracking);
    

    private void buildNotification() 
        String NOTIFICATION_CHANNEL_ID = "com.google.transporttracker";
        String channelName = "My Background Service";
        String channelId = "1";

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
        
            NotificationChannel channel = new NotificationChannel(channelId, "LOGIPACE", NotificationManager.IMPORTANCE_DEFAULT);
            mNotificationManager.createNotificationChannel(channel);
        

        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, TrackerActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
        mNotificationBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.bus_white)
                .setColor(getColor(R.color.colorPrimary))
                .setContentTitle(getString(R.string.app_name))
                .setOngoing(true)
                .setContentIntent(resultPendingIntent);
        startForeground(FOREGROUND_SERVICE_ID, mNotificationBuilder.build());
    

    /**
     * Sets the current status message (connecting/tracking/not tracking).
     */
    private void setStatusMessage(int stringId) 

        mNotificationBuilder.setContentText(getString(stringId));
        mNotificationManager.notify(NOTIFICATION_ID, mNotificationBuilder.build());

        // Also display the status message in the activity.
        Intent intent = new Intent(STATUS_INTENT);
        intent.putExtra(getString(R.string.status), stringId);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    


【问题讨论】:

这里已经有一个whole bunch of advice about this。你试过了吗? @RobertHarvey 已经试过了,这就是我发布问题的原因 好的。链接到其中的一些,并解释您尝试了什么以及为什么它对您不起作用。 “这个问题是针对 androidX 而不是针对 android 8”这不符合。在 Android 8.0+ 上使用 AndroidX 仍然有相同的要求。不过,我没有查看重复项,所以我没有立即发现问题。 【参考方案1】:

在您的 buildNotification() 函数中,您使用 mNotificationManager 创建频道,但经理的影响在下面 3 行。

因此,当您尝试创建频道时,manager 为 null,并且永远不会创建频道。

你应该在SDK版本测试之前加上mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);指令。

【讨论】:

以上是关于startForeground 的错误通知:服务通知的通道无效的主要内容,如果未能解决你的问题,请参考以下文章

如何在不显示通知的情况下 startForeground()?

startForeground() 不显示我的通知

如何重用使用 startForeground 创建的通知中的***活动?

想要隐藏通知但获取 Context.startForegroundService() 并没有调用 Service.startForeground()

绑定服务崩溃与“Context.startForegroundService() 没有然后调用 Service.startForeground()”错误

升级到 Android 8.1 后 startForeground 失败