iOS 8 Bug - 当设备从睡眠状态返回时,OnUpdateReady永远不会再次调用

时间:2014-09-24 16:44:39

标签: javascript ios web-applications sleep

当运行Web应用程序的iOS 8设备(即从主屏幕上的快捷方式启动)从其睡眠状态返回时,所有异步Web请求都无法触发OnUpdateReady回调。

问题很容易重现 - 只需将下面的两个代码文件放在任何Web服务器上并尝试一下。

还有其他人遇到过这个问题吗?如果是,有任何解决方法吗?

我发布此内容是为了吸引人们对iOS 8中的这个错误的关注,这个错误本质上毁了我的所有网络应用程序 - 我们不得不建议不要升级到iOS 7之外。是的,我发布了关于Apple Bug Reporter的问题,但我认为没有人看这些问题,因为它已经很长时间了。

app.manifest

CACHE MANIFEST
# 2014-09-24 - Test

CACHE:
default.html

default.html中

<!DOCTYPE html>
<html manifest="app.manifest">
<head>
  <title>Test Harness</title>
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
  <meta name="HandheldFriendly" content="true" />
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-status-bar-style" content="black" />
  <script language="javascript" type="text/javascript">
    var Test = new function () {
      var _GetEnd = function (oResult) {
        var sResult = ': ' +
          ((oResult.Value === true)
            ? 'Success'
            : 'Failure<br>' + oResult.Reason) +
          '<br>';

        var oLog = document.getElementById('idLog');
        oLog.innerHTML = (new Date()) + sResult + oLog.innerHTML

        setTimeout(_GetBegin, 1000);
      };

      var _GetBegin = function () {
        var sURL = 'app.manifest';
        var hAsyncCallback = _GetEnd;

        try {
          var oRequest = new XMLHttpRequest();
          oRequest.onreadystatechange =
            function () {
              if (oRequest.readyState != 4) return;
              if (oRequest.status != 200) {
                hAsyncCallback({ Value: false, Reason: oRequest.responseText });
              } else {
                hAsyncCallback({ Value: true, Reason: null });
              }
            };
          oRequest.open('GET', sURL, true);
          oRequest.send(null);
        } catch (e) {
          alert('Critical Error: ' + e.message );
        }
      };

      this.Start = function () { _GetBegin(); }
    };
  </script>
</head>
<body onload="Test.Start();">
  <ol>
    <li>Put both app.manifest and default.html on a web server.</li>
    <li>Make sure this page is being launched from the Home screen as a web application.</li>
    <li>Press the sleep button while it is running.</li>
    <li>Press the wake button and unlock the phone to get back to this screen.</li>
    <li>Under iOS7x the page continues, under iOS8 the onreadystatechange never gets called again.</li>
  </ol>
  <div id="idLog"></div>
</body>
</html>

6 个答案:

答案 0 :(得分:1)

安装iOS 8.1.1修复此问题。

答案 1 :(得分:0)

我也看到同样的问题,尽管我的例子更简单。只需使用

的webclip应用程序

<script> window.setInterval(function(){ console.log("Johnny Five Alive! : " + new Date()); },1000); </script>

在页面上

。在睡眠唤醒后检查控制台,不再有控制台输出。这在iOS7上运行正常(我的实际应用程序是一个复杂的angularJS,我刚刚将问题归结为此)。您对错误报告有任何回应吗?

答案 2 :(得分:0)

我们的解决方法(对于AJAX)是:

...

this.onProgress = function(e)
{
    var position = e.position || e.loaded;
    var total = e.totalSize || e.total;
    var percentage = 0.0;
    if(total != 0)
    {
        percentage = position / total;
    }
    if(percentage == 1) {
        if( this.isIOS8() ) {
            recovery_uuid.get(uuid, _.bind(this.ios8ScriptReturn, this));
        }
    }
}

    ...

   //this gets called when the script with this UUID is injected
   this.ios8ScriptReturn = function(uuid, value) {
//then we create a simpler non real one 
        this.xhr = {};
        this.xhr.readyState = 4;
        this.xhr.status = 200;
        this.xhr.responseText = value;
        this.xhr.onreadystatechange = null;
        this.xhr.isFake = true; 

        //fake stateChnage
        this.onReadyStateChange();

    }
  • 为每个请求添加UUID
if( this.isIOS8() ) {
    ajaxInfo.url += '&recoveryUUID='+ajaxInfo.uuid;
}
  • 然后仍然执行XHR发送(实际上工作正常,服务器获取并发送回来)。
  • 服务器端保存&#39;结果&#39;在数据库/文件中,UUID作为索引/文件名的一部分
//detect the need for saving the result, and save it till requested
if(isset($_GET['recoveryUUID'])) {
    $uuid = $_GET['recoveryUUID'];
    RecoveryUUID::post($uuid, $result_json_string);
}
  • 在客户端上创建一个小助手全局对象,侦听代码注入并将它们重定向到onProgress处理程序。
   var RecoveryUUID = (function (_root) {
    function RecoveryUUID() {
        this.callbacks = {};
    }
    var proto = RecoveryUUID.prototype;
    proto.onLoaded = null;
    proto.set = function(uuid, value) {
        console.log('RECOVERY UUID: Received DATA: '+uuid+' value: '+value);
        if(typeof this.callbacks[uuid] != 'undefined') {
            this.callbacks[uuid](uuid, value);
            delete this.callbacks[uuid]; //auto remove
        } 
        if(this.onLoaded != null) {
            this.onLoaded(uuid, value);
        }

        var script = document.getElementById("recoveryScript_"+uuid);
        script.parentElement.removeChild(script);

    }
    proto.getURL = function(uuid) {
        return "http://"+window.location.hostname+":8888/recoveryuuid/index.php?uuid="+uuid;
    }
    proto.get = function(uuid, callback) {
        var script = document.createElement("script");
        script.setAttribute("id", "recoveryScript_"+uuid);
        script.setAttribute("type", "text/javascript");
        script.setAttribute("src", this.getURL(uuid));
        if(typeof callback != 'undefined') {
            this.callbacks[uuid] = callback;
        }
        document.getElementsByTagName("head")[0].appendChild(script);
    }


    return RecoveryUUID;
})();

//global - just so the injected script knows what to call
recovery_uuid = new RecoveryUUID();
  • 立即加载的脚本执行(推送,因为setInterval也已死)。
 // this is: http://"+window.location.hostname+":8888/recoveryuuid/index.php?uuid=...."
<?php
header('Cache-Control: no-cache, no-store, must-revalidate, post-check=0, pre-check=0 '); // HTTP 1.1. //iOS force this file to keep fresh
header('Pragma: no-cache'); // HTTP 1.0.
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header('Content-type: application/javascript; charset=UTF-8');
if(isset($_GET['uuid'])) {
    $uuid = $_GET['uuid'];
    $out = 'recovery_uuid.set('.json_encode($uuid).','.json_encode(RecoveryUUID::get($uuid)).');';
    echo $out;
}
?>
  • 下面是一个简单的基于文件的结果实现。
<?php
class RecoveryUUID {
    static public function getFileName($uuid) {
        return SOMESTATIC LOCATION.$uuid.'.json';
    }
    static public function post($uuid, $stdClassOrString) {
        $data = '{ "data": '.json_encode($stdClassOrString).', "uuid": '.json_encode($uuid).' }';
        file_put_contents(self::getFileName($uuid), $data);
    }
    //might not work first time as the script tag might request this file before it was written.
    //so we wait just a bit.
    static public function getFull($uuid) {
        $tries = 10;
        $filename = self::getFileName($uuid);
        while ($tries > 0) {
            if(file_exists($filename)) {
                if (is_readable($filename)) {
                    $data = @file_get_contents($filename);
                    if($data !== FALSE) {
                        unlink($filename);
                        return $data;
                    }
                }
            }
            $tries = $tries -1;
            usleep(250000);//wait 0.25 secs ...
        }
        $data = new stdClass();
        $data->uuid = $uuid;
        $data->data = 'ERROR RECOVERYUUID: timeout on reading file';
        return $data;

    }
    static public function get($uuid) {
        $decoded = json_decode(self::getFull($uuid));
        if( $decoded->uuid == $uuid ) {
            return $decoded->data;
        }
        return null;
    }
}
?>

由于我们不使用JQuery所有我们需要做的是在我们的Ajax类中添加额外的逻辑,当然还有为所有请求保存到数据库..

缺点:

  • 讨厌
  • 将继续为每次通话添加内存占用(对于我们而言不是问题,因为在window.location.href次通话之间清除了内存(我们不使用SPA),因此最终会失败。
  • 额外的服务器端逻辑。

上升空间:

  • 工作直到内存耗尽(删除脚本标记,我们这样做不会删除相关的内存)

评论:

  • 你当然可以通过电话&#39;发送所有内容。但是我们想要对服务器端产生最小的影响(或者对我们来说工作量不大)+我们认为这将是固定的并且意味着我们的用户&#39;代码有0影响。

答案 3 :(得分:0)

很奇怪,Apple刚刚关闭了我的bug并提到了相同的bug号码。也是一个网络应用程序,但我发现css3过渡在屏幕锁定后停止工作,见下文:

Engineering已确定您的错误报告(18556061)与另一个问题(18042389)重复,并将关闭

我的报告:

如果您将HTML应用程序添加到主屏幕并打开它,则所有CSS3过渡都可正常工作。在没有关闭应用程序并按下屏幕锁定的情况下,转换似乎停止并且可能导致ui看起来冻结。例如,如果触发了绝对叠加(不透明度:0到不透明度:1),它将保持不可见状态,使应用看起来不起作用

答案 4 :(得分:0)

在iOS8上锁定屏幕后,Ajax请求,计时器功能和WebkitAnimation都被破坏了。

对于Ajax和Timer函数,我们在系统中使用此解决方案: How to resume JavaScript timer on iOS8 web app after screen unlock?(链接到评论中的gitHub)。

这不完全是问题的一部分,但我想与CSS3动画和事件分享我们的解决方法,因为它可能在将来帮助某人。

对于webkitAnimation,我们发现重新绘制带有动画的元素,或者更重要的是body将重新启动应用于它们的动画和事件(例如,webkitAnimationEnd,jquery-mobile大量使用)。

因此我们的代码提供了类似的内容:

    document.body.style.display='none';
    setTimeout( function() { document.body.style.display = 'block'; }, 1);

您可能需要或可能不需要第二个语句中的setTimeout函数。最有意思的是,一旦它被重新绘制,无论在那之后出现多少锁定屏幕,它都不会再次被冻结......

答案 5 :(得分:0)

在屏幕锁定后恢复时,webapp环境非常糟糕我不知道(a)Apple如何无限期地忽略这一点,以及(b)任何webapp如何能够自信地在残缺的环境中工作。

我的解决方案是使用setInterval(在恢复后停止工作)检测睡眠后的恢复,然后向用户发布警告(),说明必须从主屏幕重新启动应用程序,因为iOS无法恢复

在恢复之后,alert()也会被破坏 - 它会显示警报,然后当用户点击OK时,webapp会退出到主屏幕!因此,这会迫使用户重新启动。

用户重新启动时唯一的问题是处理apple-mobile-web-app-status-bar风格。我将此设置为黑色半透明,通常将状态栏内容设置为黑色(浅色背景)或白色(深色背景)。在恢复后的第一次重新启动时,状态栏内容始终为黑色。在随后的重新启动时(不会被睡眠/恢复中断),行为恢复正常。

真是一团糟。如果我对Apple负责,我会感到尴尬,而且我们在8.1这里仍然没有修复。