簡體   English   中英

我的 Android 11 應用程序在用戶將其置於后台時停止運行,除非手機已連接到電源

[英]My Android 11 app stops running when the user puts it into the background unless the phone is connected to power

我編寫了一個前台服務,以確保我的應用程序在進入后台時可以繼續運行。 該應用程序需要在后台運行,因為在其計時器結束后,它會發出提示音並振動以提醒用戶。 但是,當按下電源或主頁按鈕時,應用程序的計時器會在大約 15 分鍾后停止運行,除非手機已插入電源。 我測試時手機已充滿電。

順便說一句,在閱讀各種網站后,我還將應用程序設置為不針對電池壽命進行優化,以確保應用程序繼續運行。 從我閱讀的所有內容來看,我做的一切都是正確的,但我仍然無法讓它發揮作用。 我在 Pixel 2 上運行 Android 11。我知道 Google 限制了更高版本的 Android 的前台處理,但是將應用程序設置為不優化電池壽命應該可以解決這個問題,不是嗎? 為了安全起見,當應用程序啟動時,它會要求用戶批准后台操作:

     PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
     if (!pm.isIgnoringBatteryOptimizations(APPLICATION_ID)) {
         // Ask user to allow app to not optimize battery life. This will keep
         // the app running when the user puts it in the background by pressing
         // the Power or Home button.
         Intent intent = new Intent();
         intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
         intent.setData(Uri.parse("package:" + APPLICATION_ID));
         startActivity(intent);
     }

因此,當應用程序運行並針對電池進行優化時,用戶會看到以下內容:

在此處輸入圖像描述

我按如下方式啟動前台服務:

    private void startForegroundMonitoring() {
    broadcastIntent = new Intent(context, BroadcastService.class);
    broadcastIntent.putExtra(ALLOWEDTIME, allowed_time);
    broadcastIntent.putExtra(BEEP, beep.isChecked());
    broadcastIntent.putExtra(VIBRATE, vibrate.isChecked());
    broadcastIntent.putExtra(NOTIFY, notify_monitor.isChecked());
    broadcastIntent.putExtra(CURFEW, curfew_config.isChecked());
    broadcastIntent.putExtra(CURFEWSTARTTIME, curfew_start_time);
    broadcastIntent.putExtra(CURFEWENDTIME, curfew_end_time);
    startService(broadcastIntent);
}

更新:這是一些演示問題的代碼:

主要活動:

package com.testapp.showbug;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;

import static com.testapp.showbug.BuildConfig.APPLICATION_ID;

public class MainActivity extends AppCompatActivity {

    private Context context;

    private Intent broadcastIntent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        context = getApplicationContext();

        PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
        if (!pm.isIgnoringBatteryOptimizations(APPLICATION_ID)) {
            // Ask user to allow app to not optimize battery life. This will keep
            // the app running when the user puts it in the background by pressing
            // the Power or Home button.
            Intent intent = new Intent();
        
            intent.setAction(
            Settings.
                ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
            intent.setData(Uri.parse("package:" + 
                APPLICATION_ID));
            startActivity(intent);
        }

    broadcastIntent = new Intent(context, 
      BroadcastService.class);
    startService(broadcastIntent);
    }
    public void onDestroy() {
        super.onDestroy();

        stopService(broadcastIntent);
    }
}

廣播服務:

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.widget.Toast;

import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;

import static android.content.pm.ServiceInfo.
  FOREGROUND_SERVICE_TYPE_LOCATION;

public class BroadcastService extends Service {
    private static final int ONE_MINUTE = 60000;

    private int allowed_time = 30, tickCounter;

    private CountDownTimer countDown;

    private NotificationManagerCompat notificationManager;

    private NotificationCompat.Builder notification;

    @Override
    public void onCreate() {
        super.onCreate();

        // Clear all notifications sent earlier.
        notificationManager = 
          NotificationManagerCompat.from(this);
        notificationManager.cancelAll();

        createNotificationChannel();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, 
        int startId) {
        if (intent == null) return START_STICKY;

        Intent notificationIntent = new Intent(this, 
          BroadcastService.class);
        PendingIntent pendingIntent =
            PendingIntent.getActivity(this, 0, 
            notificationIntent, 0);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            notification = new 
              NotificationCompat.Builder(this, 
              getString(
                R.string.default_notification_channel_id))
              .setContentTitle( 
                getText(R.string.notification_title))
              .setContentText(
                  getText(R.string.notification_message))
              .setStyle(new NotificationCompat.BigTextStyle()                           
              .bigText(
                  getText(R.string.notification_message)))
              .setContentIntent(PendingIntent.getActivity(
                  this, 0, new Intent(), 0))
              .setSmallIcon(R.mipmap.ic_launcher_round)
              .setLocalOnly(true)
              .setContentIntent(pendingIntent);
        } else {
            notification = new 
                NotificationCompat.Builder(this, 
                  getString(
                    R.string.default_notification_channel_id))
                
            .setContentTitle(
                getText(R.string.notification_title))
            .setContentText(
                getText(R.string.notification_message))
            .setStyle(new NotificationCompat.BigTextStyle()                          
            .bigText(
                getText(R.string.notification_message)))
            .setContentIntent(PendingIntent.getActivity(
                this, 0, new Intent(), 0))
            .setSmallIcon(R.mipmap.ic_launcher_round)
            .setContentIntent(pendingIntent);
        }

    startForeground(FOREGROUND_SERVICE_TYPE_LOCATION, notification.build());

    tickCounter = -1;
    // Start countdown timer for allowed time.
    countDown = new CountDownTimer(allowed_time * ONE_MINUTE, ONE_MINUTE) {
        @Override
        public void onTick(long millisUntilFinished) {
            tickCounter++;
            Toast.makeText(getApplicationContext(), "tickCounter = " + tickCounter, Toast.LENGTH_LONG).show();
        }

        @Override
        public void onFinish() {
            Toast.makeText(getApplicationContext(), "tickCounter = " + allowed_time, Toast.LENGTH_LONG).show();
        }
    }.start();
    return START_STICKY;
}

private void createNotificationChannel() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        CharSequence name = getString(R.string.channel_name);
       String description = getString(R.string.channel_description);
       int importance = NotificationManager.IMPORTANCE_DEFAULT;
       NotificationChannel channel = new NotificationChannel(getString(R.string.default_notification_channel_id), name, importance);
       channel.setDescription(description);
        
       notificationManager.createNotificationChannel(channel);
       }
    }

    @Override
    public IBinder onBind(Intent arg0) {
        return null;
    }
}

上面的代碼啟動了一個前台服務,該服務又啟動了一個 CountDownTimer,它每分鍾將刻度數增加 1 並打印結果。 30 分鍾后,它應該顯示 30 個滴答聲。 相反,它會提前停止,通常在 15-16 個滴答聲之后。

以下是運行代碼的方法:

  1. 開始活動,斷開設備電源(這很重要,顯然需要真實設備),然后點擊設備上的電源按鈕。
  2. 等待 28 分鍾。
  3. 將應用程序放回前台並等待下一個滴答聲。
  4. 請注意,下一個刻度顯示的值小於 28。

感謝您對此的任何幫助。 在我看來,它就像 Android SDK 中的一個錯誤,看起來不太可能。 我沒有看到任何其他原因。 順便說一句,我在 Pixel 2 和三星 Tab A 上測試了這段代碼,它們都運行 Android 11(我擁有的唯一設備),所以我不知道這個錯誤是否發生在早期版本的 Android 或其他設備上。

我終於解決了這個問題,使用喚醒鎖。 喚醒鎖確保按下電源按鈕后 CPU 繼續運行。 我所要做的就是在 BroadcastService.java 中添加以下代碼:

在 onCreate() 中:

    PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
    wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "PeriSecure:MyWakeLock");

在 onStartCommand() 中:

    wakeLock.acquire(allowed_time * ONE_MINUTE);

在 onDestroy() 中:

wakeLock.release();

就這樣。 后台服務現在按原樣運行。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM