※ visibilitychange
への言及を修正いたしました。
※ iOS 8.1以降の変更かもしれませんが、いつの間にか、Safariをバックグラウンドに移したり、端末がスリープしたりするくらいでは、WebSocketが勝手に切られたりはしなくなったようですね...。何のために苦労してこんな記事を書いてしまったのだろう...
はじめに
iOS向けにWebSocketを使うWebアプリを書いていたら、端末放置中にロック画面になったのでロック解除してSafariに戻ってみたら...問答無用でWebSocketが切断されましたとさ、という目に遭ってムッとしてしまったので、せっかくなので色々とまとめてみようかと思いまして。
Safariを使う場合とUIWebViewでの埋め込みWebアプリでの両方について書いてみます。(後者は条件によって色々変わるかもしれませんが...)
WebSocket
- Safariの場合: 先程述べた通り、Safariではブラウザがバックグラウンドに移ったり画面ロックされたりすると、ネットワークの状態にかかわらずWebSocketが切断されます。また、別のタブにフォーカスしてもWebSocketは切断されてしまいます。
- UIWebViewの場合: UIWebViewでも、画面ロック時にWebSocketが切断されてしまいます。なお、親アプリがバックグラウンドに移っても、直ちにWebSocketが切断されるわけではないようです。(親アプリ側の挙動に依存します。)
状態変更の検出
WebSocketの切断自体は、WebSocket
オブジェクトのclose
イベントで検出できます。となると、再接続が可能になるタイミングを検出する方法があれば、Webアプリ復帰時にWebSocketを再接続するなどの対処ができることになりますが...
なお、単にネットワーク接続自体の切り替わりについてはwindow
オブジェクトのonline
,offline
イベントで検出できますが、今回の場合はネットワーク接続がオンラインであっても、問答無用でWebSocketが切断されてしまう現象への対処ですので、Webアプリがフォアグラウンドに復帰した時に再接続する方法を考える必要がありそうです。
-
Safariの場合: タブの復帰、バックグラウンドからの復帰、画面ロック解除については、いずれも
window
のpageshow
イベントで検出できます。なお、pageshow
イベントはWebアプリのロード完了時にも発生しますので、その場合には余計な再接続等を行わないように注意が必要です。例えば、次のサンプルコードのような対処が考えられるのではないでしょうか。
var ws = new WebSocket();
ws.addEventListener('open', function() {
window.addEventListener('pageshow', function() {
// WebSocketが切断されていれば、再接続を試みる
if(ws.readyState != WebSocket.OPEN)
connect();
}, false);
}, false);
function connect() {
ws.open('ws://foo.bar/ws');
...
}
-
UIWebViewの場合: UIWebViewが曲者で、
pageshow
イベントもpagehide
イベントも発生しません。よって、UIWebViewで対処するには、WebSocketが切断されてしまった時にユーザの操作で再接続を開始できるように、UIを表示する等の工夫が必要なように思われます。(他に何かいい方法があれば是非お寄せ下さい。)
なお、UIWebViewの場合、親アプリの画面から見ると表向きには埋め込みWebアプリを閉じているように見えてもまだWebアプリ自体が動いている場合があったりするので、さらに厄介です。ただ、この場合はdocument
のvisibilitychange
イベントでアプリが隠れてしまったことを検知できる場合もあるようです。