【问题标题】:Intent Service not working in doze modeIntent Service 在打盹模式下不工作
【发布时间】:2016-09-19 11:05:42
【问题描述】:

我的一位同行开发人员编写了一个intent service,它会进行 API 调用,然后休眠 2 分钟。唤醒后再次发送。

下面是代码:

public class GpsTrackingService extends IntentService {

....

@Override
    protected void onHandleIntent(Intent intent) {
      do{
        try{
          //make API call here

           //then go to sleep for 2 mins
          TimeUnit.SECONDS.sleep(120);
       
        } catch(InterruptedException ex){
            ex.printStackTrace();
        }
      } while (preferences.shouldSendGps()); //till the user can send gps.

    }

....

}


Manifest

<service android:name=".commons.GpsTrackingService" />

当手机处于活动状态时,这可以正常工作。但是,只要手机进入打盹模式,它就无法唤醒。

使用带有WAKE permission 的警报管理器会解决这个问题吗?

我刚刚获得了代码库,需要在今天内解决这个问题。如果有人可以提供帮助,那就太好了。

【问题讨论】:

  • 是无限运行服务吗?在这种情况下,没有循环。是否有其他实体重复调用此服务?在这种情况下,也可以在此处发布该代码。
  • @n.arrow001 它无限期运行,我已经更新了 do while 循环。

标签: android android-6.0-marshmallow android-intentservice android-doze


【解决方案1】:

正如documentation 所说:

在打盹模式下,系统会尝试通过限制 应用程序对网络和 CPU 密集型服务的访问。 它还可以防止 应用程序访问网络并推迟其作业、同步和 标准警报。

系统会定期退出 Doze 短暂的时间以让应用 完成他们推迟的活动。在此维护窗口期间, 系统运行所有待处理的同步、作业和警报,并允许应用 访问网络。

简而言之,在打盹模式下,系统暂停网络访问,忽略唤醒锁,停止从传感器获取数据,将 AlarmManager 作业推迟到下一个打盹维护窗口(调用频率逐渐降低),还有 WiFi扫描、JobScheduler 作业和同步适配器不运行

对于每个应用,setAndAllowWhileIdle() 和 setExactAndAllowWhileIdle() 都不能每 9 (?) 分钟触发一次以上的警报。

而且前台服务似乎也参与了这场“打瞌睡剧”,至少在 MarshMellow (M) 中。

要在这种情况下生存下来,至少需要重新审核大量应用程序。你能想象一个简单的 mp3 播放器在设备进入打盹模式时停止播放音乐吗?

打盹模式自动启动,当设备从电源上拔下并留在桌子上大约 1 小时左右,甚至更早,当用户单击电源按钮关闭屏幕时,但我认为这可能也取决于设备制造商。

我尝试了很多对策,其中一些真的很搞笑。

在我的测试结束时,我找到了一个可能的解决方案:

即使主机设备处于打盹模式,让您的应用程序运行的一种可能(并且可能是唯一)方法基本上是拥有一个ForegroundService(即使是假的,根本不做任何工作)在another process 中运行并获得partial WakeLock

你需要做的基本上如下(你可以创建一个简单的项目来测试它):

  • 1 - 在您的新项目中,创建一个扩展 Application (myApp) 的新类,或使用 新项目的主要活动。
  • 2 - 在 myApp onCreate() 中启动一个服务 (myAntiDozeService)
  • 3 - 在 myAntiDozeService onStartCommand() 中,创建通知 需要将服务作为前台服务启动,启动 使用 startForeground(id, notification) 服务并获取 部分唤醒锁。

记住!这会起作用,但这只是一个起点,因为您必须小心这种方法会产生的“副作用”:

  • 1 - 电池消耗:CPU 将永远为您的应用程序工作,如果您 不要使用某些策略,让 WakeLock 始终处于活动状态。

  • 2 - 将始终显示一个通知,即使在锁屏中也是如此, 并且这个通知不能通过简单地刷出来来删除,它 将一直存在,直到您停止前台服务。

好的,我们开始吧。

myApp.java

    public class myApp extends Application {
    private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
    private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";

        @Override
        public void onCreate() {

            super.onCreate();            

            // start foreground service
            startForeService();
    }

    private void stopForeService() {
        Intent service = new Intent(this, myAntiDozeService.class);
        service.setAction(STOPFOREGROUND_ACTION);
        stopService(service);
    }

    private void startForeService(){
        Intent service = new Intent(this, myAntiDozeService.class);
        service.setAction(STARTFOREGROUND_ACTION);
        startService(service);
    }

    @Override
    public void onTerminate() {
        stopForeService();
        super.onTerminate();
    }
}

myAntiDozeService.java

public class myAntiDozeService extends Service {

    private static final String TAG = myAntiDozeService.class.getName();
    private static boolean is_service_running = false;
    private Context mContext;
    private PowerManager.WakeLock mWakeLock;
    private static final int NOTIFICATION_ID = 12345678;
    private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
    private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";

    @Override
    public void onCreate() {
        super.onCreate();
        mContext = getApplicationContext();

    }

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

        if (!is_service_running && STARTFOREGROUND_ACTION.equals(intent.getAction())) {
            Log.i(TAG, "Received Start Foreground Intent ");
            showNotification();
            is_service_running = true;
            acquireWakeLock();

        } else if (is_service_running && STOPFOREGROUND_ACTION.equals(intent.getAction())) {
            Log.i(TAG, "Received Stop Foreground Intent");
            is_service_running = false; 
            stopForeground(true);
            stopSelf();
        }

        return START_STICKY;
    }

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

    private void showNotification(){

        Intent notificationIntent = new Intent(mContext, ActivityMain.class);
        notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);

        Notification notification = new NotificationCompat.Builder(mContext)
                .setContentTitle("myApp")
                .setTicker("myApp")
                .setContentText("Application is running")
                .setSmallIcon(R.drawable.ic_launcher)
                .setContentIntent(pendingIntent)
                .build();

        // starts this service as foreground
        startForeground(NOTIFICATION_ID, notification);
    }

    public void acquireWakeLock() {
        final PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
        releaseWakeLock();
        //Acquire new wake lock
        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG+"PARTIAL_WAKE_LOCK");
        mWakeLock.acquire();
    }

    public void releaseWakeLock() {
        if (mWakeLock != null && mWakeLock.isHeld()) {
            mWakeLock.release();
            mWakeLock = null;
        }
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

AndroidManifest.xml 更改。


在AndroidManifest.xml中添加这个权限:

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

不要忘记在&lt;application&gt; 标签中添加您的应用名称:

<application
        ....
        android:name=".myApp"
        ....

最后将你的前台服务添加到另一个进程中:

   <service
        android:name=".myAntiDozeService"
        android:process=":MyAntiDozeProcessName">
    </service>

一些笔记。

  • 在前面的示例中,创建的通知在单击时, 打开测试项目的 ActivityMain 活动。

    Intent notificationIntent = new Intent(mContext, ActivityMain.class);

    但你也可以使用另一种意图。

  • 要对其进行测试,您必须将一些要执行的作业添加到您的 ActivityMain.java,例如一些repeating alarm(它是 通常在设备进入打盹模式时停止),或成熟 网络访问,或播放定时铃声,或....任何你想要的。
  • 请记住,主要活动执行的作业必须运行 永远因为要测试这个 AntiDoze 你需要等待至少 1 小时以确保设备进入打盹模式。
  • 要进入打盹模式,设备必须保持安静并拔下电源,所以 调试时无法对其进行测试。首先调试您的应用程序, 检查一切都在运行然后停止它,拔下电源,重新启动 再次应用,然后将设备单独放在桌面上并保持安静。

  • 文档向simulate Doze and StandBy modes 建议的adb 命令可以也不能给您正确的结果 (我想这取决于设备制造商,驱动程序,bla 布拉)。请以真实的行为进行测试。

在我的第一个测试中,我使用 AlarmManager 和音调发生器每 10 分钟播放一次音调,以了解我的应用程序仍然处于活动状态。 而且它仍然从大约 18 小时开始运行,每 10 分钟就用一次响亮的音调打破我的耳朵。 :-)

编码愉快!

【讨论】:

  • 如果用户从任务列表中滑动应用程序会发生什么情况?我认为服务消失了,重复警报也消失了。如果您有后台工作也将停止。
  • 从列表中滑动应用不会终止应用。您需要转到设置 -> 应用程序 -> 选择应用程序并使用终止按钮终止它。
  • 我在 Android 6 上试过这个,但是当我将前台服务运行到另一个进程时,我的服务不再运行了。有什么提示吗? android:process=":JobServiceProcess"
  • 谢谢!基本上保存了我的应用程序:)
【解决方案2】:

我的一位同行开发人员编写了一个意图服务,该服务进行 API 调用,然后休眠 2 分钟。唤醒后再次发送。

Only have a service running while it is actively delivering value to the user。坐两分钟,看着时钟滴答作响,并没有积极地为用户提供价值。

使用具有 WAKE 权限的警报管理器会解决这个问题吗?

这取决于您所说的“解决这个问题”是什么意思。您可以使用AlarmManager 请求每两分钟获得一次控制权,以便您可以工作。当设备处于打盹模式时,您实际上不会每两分钟获得一次控制权,而是每个维护时段一次。

【讨论】:

  • 有没有办法覆盖打瞌睡模式,无论手机处于空闲还是使用状态,应用都会每 2 分钟发送一次信息。
  • @hannanessay:用户可以通过设置应用将你的应用加入电池优化白名单。
猜你喜欢
  • 1970-01-01
  • 2019-04-25
  • 1970-01-01
  • 2018-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-20
  • 1970-01-01
相关资源
最近更新 更多