安卓AlarmManager在手机休眠时无法工作

8

我有一个关于AlarmManager的问题。

简单来说,我计划使用AlarmManager:

Intent intent = new Intent(context, MyActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayInMs, pendingIntent);

指定时间时,活动 MyActivity 就会出现。当设备插入时,它也可以在我的口袋里或延迟几分钟时正常运作。 但是,当我在晚上设置闹钟管理器时,早上它就不会工作。然而,只要我拿起手机或解锁屏幕,它就会工作。

因此,我认为这是由于设备的休眠模式引起的,但如何解决呢?

1) 我在 MyActivity 的每个方法中添加了日志,并确信在我手动唤醒设备之前没有调用任何方法。 2) 我尝试使用 PowerManagement 的唤醒锁(在清单中具有 WAKE_LOCK 权限),但什么也没改变:

alarmManager.setExact(.........);
wakeLock = ((PowerManager)contexte.getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "MyActivity");
wakeLock.acquire();

请帮忙!我相信我已经非常接近了...
编辑于2016年12月04日: 感谢Nick Friskel和Vikram Rao,我修改了我的初始代码以调用broadcastReceiver并在onReceive中获取我的wakeLock。不幸的是,它似乎没有起作用。当手机插上电源或者闹钟计划在35分钟后时,它完美地工作,但是在整个晚上,onReceive甚至都没有被调用。 我尝试了那个晚上,在9:00 AM计划好的闹钟,但是onReceive只在9:46 AM执行,这意味着我解锁设备的时刻。 这是我的新代码:
Intent intent = new Intent("com.blah.something.ALARM_RECEIVED");
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_ONE_SHOT);
AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pendingIntent);
alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delayInMs, pendingIntent);

尽管如此,我的日志写在“onReceive”的开始并不是真正的开始,我找到了真正的开始位置,现在我会查看监听器是否被调用。

2016年12月05日更改日志写入方式:我把日志写在“onReceive”顶部,但是问题还是一样:只要我手动唤醒设备,“onReceive”的开始就会被调用。我可以实现wakefulBroadcastReceiver,但我担心它没有解决任何问题。如果我理解正确,wakefulBroadcastReceiver有助于防止设备在onReceive和活动或服务启动之间进入睡眠状态。但是,如果onReceive根本没有被调用呢?我有点绝望...也许我应该直接问Sony。此外,我的手机具有Stamina模式,但未激活。

2016年12月11日更改日志:通过更多的测试,我现在确定自己什么都不懂了....我设置了一个广播接收器,每5分钟激活一次(onReceive会在5分钟后重置alarmManager),我可以看到它完美地工作......有时候。它可以持续几个小时,休息两个小时,然后再工作30分钟,然后回到睡眠状态。(当我的手机处于开启、未充电和空闲状态时)我将删除除我们感兴趣的代码以外的所有代码。这样更容易理解,我将能够在此处写出所有激活代码。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.par.hasard.mysimpleapplication">
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <receiver android:name="com.par.hasard.mysimpleapplication.MySimpleReceiver">
            <intent-filter android:priority="1">
                <action android:name="com.par.hasard.mysimpleapplication.REGULAR_ALARM" />
            </intent-filter>
        </receiver>
    </application>
</manifest>

MainActivity.java

package com.par.hasard.mysimpleapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button)findViewById(R.id.myExportButton);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                MyLogManager.copyLogToClipboard(view.getContext());
                MyLogManager.emptyLogFile(view.getContext());
            }
        });
        try {
            MyLogManager.createLogFile(this);
            MyLogManager.write(this, "Application launched\n");
            MyAlarmPlanner.planAlarm(this);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

MySimpleReceiver.java

package com.par.hasard.mysimpleapplication;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class MySimpleReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        try {
            MyLogManager.write(context, "Beginning of onReceive\n");
            MyAlarmPlanner.planAlarm(context);
            MyLogManager.write(context, "End of onReceive\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

MyAlarmPlanner.java

package com.par.hasard.mysimpleapplication;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.SystemClock;
import java.io.IOException;

public class MyAlarmPlanner {
    public static void planAlarm(Context context) throws IOException {
        MyLogManager.write(context, "Beginning of alarm planning\n");
        AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
        Intent intent = new Intent("com.par.hasard.mysimpleapplication.REGULAR_ALARM");
        PendingIntent pendingIntent =  PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
        alarmManager.cancel(pendingIntent);
        alarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 300000, pendingIntent);
        MyLogManager.write(context, "End of alarm planning\n");
    }
}

我认为MyLogManager.java并不实用,仅仅是一个令人厌烦的文件管理方法。

长时间空闲后日志文件的内容:

12/12 15h33m23s380 => Beginning of onReceive
12/12 15h33m23s381 => Beginning of alarm planning
12/12 15h33m23s383 => End of alarm planning
12/12 15h33m23s384 => End of onReceive
12/12 15h38m24s337 => Beginning of onReceive
12/12 15h38m24s339 => Beginning of alarm planning
12/12 15h38m24s375 => End of alarm planning
12/12 15h38m24s376 => End of onReceive
12/12 15h43m24s375 => Beginning of onReceive
12/12 15h43m24s376 => Beginning of alarm planning
12/12 15h43m24s380 => End of alarm planning
12/12 15h43m24s381 => End of onReceive
12/12 15h48m25s301 => Beginning of onReceive
12/12 15h48m25s304 => Beginning of alarm planning
12/12 15h48m25s307 => End of alarm planning
12/12 15h48m25s308 => End of onReceive
12/12 15h53m25s316 => Beginning of onReceive
12/12 15h53m25s318 => Beginning of alarm planning
12/12 15h53m25s328 => End of alarm planning
12/12 15h53m25s329 => End of onReceive
12/12 15h58m25s328 => Beginning of onReceive
12/12 15h58m25s329 => Beginning of alarm planning
12/12 15h58m25s331 => End of alarm planning
12/12 15h58m25s333 => End of onReceive
12/12 16h3m26s336 => Beginning of onReceive
12/12 16h3m26s351 => Beginning of alarm planning
12/12 16h3m26s379 => End of alarm planning
12/12 16h3m26s380 => End of onReceive
12/12 16h8m26s397 => Beginning of onReceive
12/12 16h8m26s401 => Beginning of alarm planning
12/12 16h8m26s404 => End of alarm planning
12/12 16h8m26s405 => End of onReceive
12/12 16h13m26s406 => Beginning of onReceive
12/12 16h13m26s407 => Beginning of alarm planning
12/12 16h13m26s410 => End of alarm planning
12/12 16h13m26s411 => End of onReceive
12/12 16h18m27s328 => Beginning of onReceive
12/12 16h18m27s329 => Beginning of alarm planning
12/12 16h18m27s346 => End of alarm planning
12/12 16h18m27s348 => End of onReceive
12/12 16h23m28s298 => Beginning of onReceive
12/12 16h23m28s299 => Beginning of alarm planning
12/12 16h23m28s303 => End of alarm planning
12/12 16h23m28s304 => End of onReceive
12/12 16h28m29s308 => Beginning of onReceive
12/12 16h28m29s310 => Beginning of alarm planning
12/12 16h28m29s323 => End of alarm planning
12/12 16h28m29s324 => End of onReceive
12/12 16h33m29s339 => Beginning of onReceive
12/12 16h33m29s340 => Beginning of alarm planning
12/12 16h33m29s355 => End of alarm planning
12/12 16h33m29s361 => End of onReceive
12/12 16h38m29s356 => Beginning of onReceive
12/12 16h38m29s357 => Beginning of alarm planning
12/12 16h38m29s360 => End of alarm planning
12/12 16h38m29s361 => End of onReceive
12/12 16h43m29s364 => Beginning of onReceive
12/12 16h43m29s365 => Beginning of alarm planning
12/12 16h43m29s367 => End of alarm planning
12/12 16h43m29s369 => End of onReceive
12/12 16h48m29s376 => Beginning of onReceive
12/12 16h48m29s380 => Beginning of alarm planning
12/12 16h48m29s390 => End of alarm planning
12/12 16h48m29s394 => End of onReceive
12/12 16h53m29s392 => Beginning of onReceive
12/12 16h53m29s394 => Beginning of alarm planning
12/12 16h53m29s402 => End of alarm planning
12/12 16h53m29s403 => End of onReceive
12/12 17h43m33s986 => Beginning of onReceive      //problem, the 16'58 onReceive wasn't called
12/12 17h43m33s988 => Beginning of alarm planning
12/12 17h43m33s996 => End of alarm planning
12/12 17h43m34s4 => End of onReceive
12/12 17h48m34s535 => Beginning of onReceive
12/12 17h48m34s536 => Beginning of alarm planning
12/12 17h48m34s539 => End of alarm planning
12/12 17h48m34s540 => End of onReceive
12/12 18h29m49s635 => Beginning of onReceive     //the moment I turned on my device
12/12 18h29m49s648 => Beginning of alarm planning
12/12 18h29m49s667 => End of alarm planning
12/12 18h29m49s668 => End of onReceive

有人能告诉我我的错误在哪里吗?

安卓版本是哪个?手机是什么牌子?根据我的经验,不同版本的安卓在某些方面的行为略有不同。一些由制造商进行定制的手机,在睡眠/唤醒相关操作上表现也有所不同,主要为了节省电池电量而限制其他功能。 - Vikram Rao
这是一部索尼XPERIA E5手机。是的,我知道这一点,但我希望如果我能在我的设备上工作,那么在大多数其他设备上也可以。我希望... - mcourpot
3个回答

6

1
在安卓系统中,AlarmManager API有其限制。具体来说:

  1. 设备重新启动时会被清除(所有闹钟都会丢失)
  2. 在设备锁定/睡眠状态下,不同制造商和安卓系统版本之间的行为不一致

我解决这些问题的方法是 -

  1. 创建一个具有广播意图的闹钟,然后添加监听器以执行必要操作。

就像这样 -

Intent intent = new Intent("com.blah.something.ALARM_RECIEVED");
PendingIntent pendingIntent =  PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_ONE_SHOT);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Html5Activity.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC, triggerTimeInMillis, pendingIntent);

清单文件 -
<receiver android:name=".receiver.BackgroundScheduledAlarmReceiver">
     <intent-filter android:priority="1">
          <action android:name="com.blah.something.ALARM_RECIEVED" />
     </intent-filter>
</receiver>
  1. 将闹钟保存在 SQLite 或其他地方(此处未显示),并通过监听设备启动来重新创建它们,例如:

清单文件 -

<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
...
<receiver
    android:name=".receiver.RecreateAlarmsAtBootReceiver"
    android:enabled="true"
    android:exported="true"
    android:label="RecreateAlarmsAtBootReceiver">
    <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED" />
        <action android:name="android.intent.action.QUICKBOOT_POWERON" />
    </intent-filter>
</receiver>

RecreateAlarmsAtBootReceiver中,读取存储闹钟的sqlite并将它们再次添加到闹钟管理器中。

对于第二点,我已经添加了RECEIVE_BOOT_COMPLETED,它会使用保存的数据初始化闹铃。看起来好像可以工作,尽管我没有花太多时间进行测试。对于第一点,我会尝试并及时告知您结果。 - mcourpot
广播接收器目前没有解决问题,当设备长时间未插入并处于非活动状态时,似乎监听器没有被调用。正如我在编辑后的问题中所说,我会确保它可以正常工作。 - mcourpot

0

正确的做法是让AlarmManager触发一个BroadcastReceiver而不是直接启动Activity。然后,您将在广播接收器类中放置您的wakelock,然后从BroadcastReceiver或从由BroadcastReceiver启动的IntentService中运行您的Activity。

您的意图将更改为:

Intent intent = new Intent(context, MyActivityReceiver.class);

然后创建你的广播接收器:

package com.yourpackage (change this to your package)

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public class MyActivityReceiver extends BroadcastReceiver {
    public static final int REQUEST_CODE = 0; //only necessary if you have more receivers 

    // when the alarm is triggered
    @Override
    public void onReceive(Context context, Intent intent) {
    wakeLock = ((PowerManager)contexte.getSystemService(Context.POWER_SERVICE)).newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "MyActivity");
    wakeLock.acquire();
    Intent i = new Intent(context, MyActivity.class);
    context.startService(i);
    }
}

并将接收器添加到您的清单中:

<receiver
            android:name="service.TimeService"
            android:enabled="true"
            android:exported="false"
            >
</receiver>

如果这不起作用,那么您需要通过一个WakefulService来启动您的活动。

谢谢您的回答。我刚刚编辑了我的问题。所以我会确认我的onReceive没有被调用,如果确认了,我会尝试您的WakefulService想法。 - mcourpot
那么onReceive仅在手机插入时才不会被调用? - Nick Friskel
1
不,当手机插入或空闲几分钟时,onReceive会被完美调用。只有在长时间未插电并处于空闲状态时,才不会被调用。 - mcourpot

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接