当运行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>
答案 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();
}
if( this.isIOS8() ) {
ajaxInfo.url += '&recoveryUUID='+ajaxInfo.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);
}
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();
// 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),因此最终会失败。上升空间:
评论:
答案 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这里仍然没有修复。