4

最近,我收到了用户关于我的闹钟应用程序没有响铃的信息。最后,其中一位用户从构建日志中向我发送了信息,这真的很奇怪:

74. 4:25:0 - StartAlarm received
75. 5:22:15 - AlarmOnScreen create
76. 5:22:15 - Time: 04:25

问题是,要记录的信息保存如下:

//BroadcastReceiver
@Override
public void onReceive(Context context, Intent intent) {
    Logger.initialize(context);
    Logger.log("StartAlarm received");
    Intent i = new Intent(context, AlarmOnScreen.class);
    i.putExtras(intent.getExtras());
    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(i);
}

//AlarmOnScreen (activity)
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.alarm_on_screen);
    Logger.log("AlarmOnScreen create");
    //Time value of alarm is logged below
    (...)

如您所见,活动的开始被大大延迟了。这怎么可能?用户报告说警报被延迟,直到他开始“使用”手机 - 我想这意味着,直到锁屏被解锁或屏幕打开。我仍在等待更多信息的答案。在其他时间延迟只有 5 分钟 - 每次,直到用户开始“使用电话”

有任何想法吗?

编辑:让我补充一下,这是最近开始发生的事情,在申请几个月后。我仍在寻找我是否可能在清单和上次更新中更改了任何内容,但它是否可能仅在新的 Android 版本上发生?

4

2 回答 2

5

我认为您的问题是在没有正确使用 WakeLocks 的情况下使用 AlarmManager,当设备在屏幕关闭的情况下“休眠”时,您的接收器将无法正常工作。

我猜你的接收器从 AlarmManager 得到了 onReceive(),这很可能是用_WAKEUP这样的标志开始的:

mAlarmManager.set(AlarmManager.RTC_WAKEUP, .......);

这个 _WAKEUP 标志意味着设备将“开启”,即使它处于睡眠模式。但是,如此处的文档所述(http://developer.android.com/reference/android/app/AlarmManager.html):

只要警报接收器的 onReceive() 方法正在执行,警报管理器就会持有 CPU 唤醒锁。这保证了在您完成广播处理之前手机不会休眠。一旦 onReceive() 返回,警报管理器就会释放这个唤醒锁。这意味着在某些情况下,一旦您的 onReceive() 方法完成,手机就会进入睡眠状态。如果您的警报接收器调用了 Context.startService(),则手机可能会在请求的服务启动之前休眠。为防止这种情况,您的 BroadcastReceiver 和 Service 将需要实施单独的唤醒锁定策略,以确保手机继续运行直到服务可用。

在您的代码中,这意味着系统一旦onReceive()结束就会重新进入睡眠状态,并且startActivity(i)不会同步工作 - 这直接导致了上面提到的问题 - 它将被启动,但很久很久之后,就在用户转身的时候屏幕上。

为了解决它,我建议做这样的事情:

//BroadcastReceiver
@Override
public void onReceive(Context context, Intent intent) {
    Logger.initialize(context);
    Logger.log("StartAlarm received");
    Intent i = new Intent(context, AlarmOnScreen.class);
    i.putExtras(intent.getExtras());
    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(i);
    AlarmOnScreen.acquireLock(context);
    //Before, system could sleep right after this line(not exactly, however) and activity actually would be started much later
}

//AlarmOnScreen (activity)

private static WakeLock sWakeLock;
public static void acquireLock(Context context) {
    PowerManager pm = (PowerManager)  context.getSystemService(Context.POWER_SERVICE);
    sWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "providersLock");
    //Limit 10 sec, if something wrong will happen - we'll not drain the battery to much.
    sWakeLock.acquire(10000); 
    //As we are acquiring and releasing only once - we don't need a counter.
    sWakeLock.setReferenceCounted(false);
}

private static void releaseLock(Context context) {
    try {
        sWakeLock.release();
    } catch (Exception e) {
        //In case it's already auto-released
        e.printStackTrace();
    }
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.alarm_on_screen);
    Logger.log("AlarmOnScreen create");
    //Time value of alarm is logged below
    (...)

@Override
protected void onResume() {
    releaseLock(this);
}

该解决方案将首次起作用,让您更深入地了解问题。进行测试 - 只需在屏幕关闭时开始使用您的闹钟,并且可能会拔掉电缆,但我不确定最后一个是否真的需要让设备进入睡眠模式。

但是,我强烈建议实施更优雅的解决方案,适合您的项目,因为当前的静态参考设计很差,例如,它在赛车条件下无法完美运行。

希望它有所帮助,如果有任何问题,请告诉我。祝你好运。

UPD: 我想我还建议不仅使用 PARTIAL_WAKE_LOCK,而且还使用 FULL 一个。喜欢:

pm.newWakeLock(PowerManager.FULL_WAKE_LOCK 
    | PowerManager.ACQUIRE_CAUSES_WAKEUP, "providersLock");

无论如何,这将强制屏幕打开,而不取决于先前的状态和平台对新活动创建的反应。

于 2012-09-11T15:19:53.263 回答
0

您必须为活动窗口设置几个标志:

getWindow().addFlags(
    WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
    WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD |
    WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
    WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
);
于 2012-09-07T08:11:31.250 回答