iOS 8 Bug - OnUpdateReady never called again when device returns from sleep

此生再无相见时 提交于 2019-12-05 00:25:14

问题


When an iOS 8 device running a Web Application (i.e. launched from a shortcut on the Home Screen) returns from it's Sleep state all asynchronous web requests made fail to trigger the OnUpdateReady callback.

The problem is quite easy to reproduce - simply put the two code files below on any web server and give it a try.

Has anyone else run into this issue? If so is there any workarounds?

I'm posting this to try to attract attention to this bug in iOS 8 that has essentially ruined all of my web applications - we've had to recommend to NOT upgrade beyond iOS 7. And yes, I've posted the problem on Apple Bug Reporter but I think no one is looking at these since it has been a long time.

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>

回答1:


Installing iOS 8.1.1 fixes this.




回答2:


I am also seeing the same issue, though my example is much simpler. Simply have a webclip application with

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

on the page. Inspecting the console, after the sleep wakes up, no more console output. This works fine on iOS7 (my actual application is a complicated angularJS thing, I just boiled down the issue to this). Have you had any response on your bug report?




回答3:


Our workaround (for AJAX) is:

  • Detect iOS8 (indeed 8.0.2 still has this) (also see this for other workaround: How to resume JavaScript timer on iOS8 web app after screen unlock?)
  • Remove the normal eventListeners, but keep the onProgress one
...

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();

    }
  • add a UUID to each request
if( this.isIOS8() ) {
    ajaxInfo.url += '&recoveryUUID='+ajaxInfo.uuid;
}
  • Then still perform the XHR Send (that actually works fine, server gets and send back fine).
  • server Side save the 'result' in database/file with the UUID as index/part of filename
//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);
}
  • On the client create a little helper global object that listens to the code injects and redirects them to the onProgress handler.
   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();
  • The script that is loaded immediately executes (pushes, since setInterval is dead as well).
 // 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;
}
?>
  • Below is a simple filebased results implementation.
<?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;
    }
}
?>

As we do not use JQuery all we needed to do was add the extra logic in our Ajax class, and of course the Saving to Database for all requests..

Downsides:

  • Nasty
  • Will keep on adding memory footprint for each call (for us not an issue as the memory is cleared between window.location.href calls (we do not use SPA) so eventually will fall over.
  • Extra serverside logic.

Upsides:

  • Works until memory runs out (removing script tags, which we do does not remove the memory associated)

Comments:

  • You could of course just send everything with the 'call' but we wanted to have minimal server side impact (or not much work for us anyway) + we presume this will be fixed and means our 'user' code has 0 impact.



回答4:


Weird, Apple just closed my bug and referred to the same bug number. Also a web app but I found css3 transitions to stop working after screen lock see below:

Engineering has determined that your bug report (18556061) is a duplicate of another issue (18042389) and will be closed

My report:

If you add an HTML app to the home screen and open it, all CSS3 transitions work correctly. Without closing the app and pressing screen lock the transitions seem to stop and can cause the ui to appear to freeze. For example if an absolute overlay is triggered (opacity:0 to opacity:1) it remains invisible making the app appear not to work




回答5:


Ajax requests, Timer functions and WebkitAnimation are broken after a lock-screen on iOS8.

For the Ajax and Timer functions, we are using this solution in our system: How to resume JavaScript timer on iOS8 web app after screen unlock? (link to the gitHub in the comment).

It is not exactly part of the question but I would like to share our workaround with CSS3 animations and events, since it may help somebody in the future.

For the webkitAnimation, we found that redrawing the element with the animation on, or more drastically the body would restart animations and events applied to them (webkitAnimationEnd for instance, which is used heavily by jquery-mobile).

so our code gives something like:

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

You may or may not need the setTimeout function on the second statement. Most interesting thing is, once it has been redrawn, it will never go frozen again no matter how many lock screens come up after that...




回答6:


The webapp environment is so horribly broken when resuming after screen lock I don't see how (a) Apple could ignore this indefinitely and (b) how any webapp can confidently work around the crippled environment.

My solution is to detect resume after sleep using setInterval (which stops working after the resume) and then posting an alert() to the user stating that the app has to be re-launched from the home screen because iOS cannot resume it.

It just so happens that alert() is also broken after the resume--it displays the alert and then the webapp exits to the home screen when the user hits OK! So this forces the user to re-launch.

The only issue when the user re-launches is the handling of apple-mobile-web-app-status-bar-style. I have this set to black-translucent which normally sets the status bar content to black (on light backgrounds) or white (on dark backgrounds). On the first re-launch after resume, the status bar content is always black. On subsequent re-launches (not interrupted by sleep/resume) the behavior returns to normal.

What a mess. If I was responsible for this at Apple I'd be embarrassed, and here we are with 8.1 and it still hasn't been fixed.



来源:https://stackoverflow.com/questions/26022261/ios-8-bug-onupdateready-never-called-again-when-device-returns-from-sleep

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!