ç«ã®äžã§ãParse.comã§ããã¯ãšã³ããéçºããéã«çŽé¢ããåé¡ãPubnubãšçµã¿åãããŠäœ¿çšââããå¿ èŠãããçç±ãAndroidçšã«éçºããéã«ãã¹ãŠãæ¥ç¶ããæ¹æ³ã«ã€ããŠèª¬æããŸãã
æåŸã«åºãŠãããã®ïŒ

å³å¯ã«èšãã°ãParse.comãšPubnubã¯ãã§ã«Habréã®çµã¿åããã®äž»é¡ãæ±ã£ãŠããŸãã ãã ãããã®èšäºãšã¯ç°ãªããããã§ã¯Parse.comã¯ã©ãŠãã³ãŒãã«ã€ããŠè©³ãã説æããŸããã¿ãŒã²ããã¢ããªã±ãŒã·ã§ã³ã¯IOSã§ã¯ãªãAndroidçšã«éçºããŠããŸãã
Parse.com
Parse.comã¯åºç¯ãªã¯ã©ãŠãæ©èœãæäŸããŸããããã§ã¯ãçŸããã°ã©ãã£ã«ã«ã©ãããŒããµãŒããŒã³ãŒããåæãããã«ã¯ããã·ã¥éç¥ã§ããŒã¿ããŒã¹ãèŠã€ããããšãã§ããŸãã ãããŠã1ç§ããã30ãªã¯ãšã¹ãã20 GBã®ã¹ãã¬ãŒãžäœ¿çšãªã©ã®ãããå€ãè¶ ãããŸã§ããã¹ãŠç¡æã§ãã ã ç§ã¯ãããã®èŠä»¶ã«å®å šã«æºè¶³ããŸããïŒç¡æã§ãïŒïŒã®ã§ããã®ç¹å®ã®ãµãŒãã¹ã«éžæè¢ããããŸããã
åé¡
ã¬ã€ããæ éã«äœæããçµæããªã³ã©ã€ã³ãŠãŒã¶ãŒã®ãªã¢ã«ã¿ã€ã ãªã¹ããšãã®å¯¿åœã«é¢é£ããããã€ãã®åé¡ãæµ®äžããŸãã ã
- ã«ã¹ã¿ã ãªããžã§ã¯ãçšã®ã¿ã€ããŒã¿ã€ãã®ãã£ãŒã«ãã¯ãããŸãã
- ãµãŒãã¹ã¯ã é·ãåŒãå¯ãã®å¯èœæ§ãæäŸããŸããïŒãŸãã¯ãç§ã¯ãããèŠã€ããŸããã§ããïŒ
- æšæºã®User / Sessionã¯ã©ã¹ã¯ãã®ã¿ã¹ã¯ã«ã¯é©ããŠããŸãã
解決ç
ããããåé¡ã§ããçç±ã説æãããŠãã ããã
ãŠãŒã¶ãŒããæ»äº¡ããããšãã«éç¥ãåä¿¡ããïŒçæ³çã«ã¯èªåçã«åé€ããïŒããã«ãExpireAtãã£ãŒã«ããšããŠTimerã¿ã€ãïŒãŸãã¯ãã®ãããªãã®ïŒã䜿çšããããšãèšç»ãããŠããŸããã
ãã®ã¿ã€ããååšããªãå Žåã¯ãéåžžã®æ¥ä»ã¿ã€ãã䜿çšãããŠãŒã¶ãŒãã殺ããå¿ èŠããããšãã«èªåã§ç£èŠããå¿ èŠããããŸãã
ãã³ã°ãã«ã䜿çšããŠçä¿¡/çºä¿¡ãŠãŒã¶ãŒã远跡ããããšãèšç»ãããŠããŸãããããã®ãµãŒãã¹ã¯ãã®ãããªæ©äŒãããã«æäŸããŸããã
ã€ã³ã¹ããŒã«ã䜿çšããããšã決å®ãããŸããã èŠããã«ããããã¯èª°ïŒãµãŒããŒ-ã¯ã©ã€ã¢ã³ããã¯ã©ã€ã¢ã³ã-ã¯ã©ã€ã¢ã³ããªã©ïŒã®éã§ããŒã¿ã転éããããã®ã°ããŒãã«ãã£ãã«ã§ãã ãããã£ãŠããã°ã€ã³/ raloginã®åŸããµãŒããŒã¯ããã®ãããªãŠãŒã¶ãŒãåºå ¥ããããšããã¡ãã»ãŒãžããã£ãã«ã«éä¿¡ããå¿ èŠããããŸãã ããã«ããããŠãŒã¶ãŒã®ãªã¢ã«ã¿ã€ã ãªã¹ããæäŸãããŸãã ïŒãã ããããã¯å®å šã«çå®ã§ã¯ãããŸããããããã«ã€ããŠã¯åŸã§è©³ãã説æããŸãïŒã
Parse.com SDKã«ã¯ãã°ã€ã³/ãã°ã€ã³ã¡ãœãããçµã¿èŸŒãŸããŠãããããéçºã§ãããã䜿çšãããšéåžžã«äŸ¿å©ã§ãã ããããããã¯äžå¯èœã§ããããšãå€æããŸããã
äžèšã®ããã«ããã°ã€ã³/ãã°ã¢ãŠããããšãããŠãŒã¶ãŒããçããŠãããïŒãŸãã¯ãã°ã¢ãŠãããŠããå Žåã¯ãæ»ãã§ãããïŒãã©ããã«é¢ããã¡ãã»ãŒãžããã£ãã«ã«éä¿¡ããå¿ èŠããããŸãã ãã®ãµãŒãã¹ã¯ãAfterAfterãbeforeDeleteãªã©ã®ããªã¬ãŒãäœæããæ©èœãæäŸããŸãã åé¡ã¯ãã»ãã·ã§ã³ã«ãã®ãããªã€ãã³ãããªãããšã§ãã ããã¯ããã°ãèšé²ãããã³ã«ãã»ãã·ã§ã³ã§ãŠãŒã¶ãŒãæåéãåé€ããå¿ èŠãããããšãæå³ããŸããããã«ãããSDKã«çµã¿èŸŒãŸããã¡ãœããã®å©ç¹ããã¹ãŠå€±ãããŸãã
ãããã£ãŠãã¢ã©ãŒããã°ããŒãã«ãã£ãã«ã«éä¿¡ãããafterDeleteããã³afterSaveããªã¬ãŒã§ãã³ã°ããããšã«ãããã«ã¹ã¿ã ã¯ã©ã¹IMH_Sessionã䜿çšããããšã決å®ãããŸããã
ãã¥ã¢ã³ã¹
ãããŠãAndroid Studioã§åå©ãç¥ã£ãŠåº§ãæéã«ãªããŸããã...ã€ã³ã¹ããŒã«ã¯ããã©ã«ãã®ããã·ã¥éç¥ã«åºã¥ããŠããŸãã ç§ã®ããã«æŠè»ã«ä¹ã£ãŠãã人ã®ããã«èª¬æããŸãã ããã·ã¥éç¥ã¯äœãä¿èšŒããŸããã 圌ãã¯FireïŒForgetã®ååã«åºã¥ããŠåäœããŸããã€ãŸããããã·ã¥éç¥ãéä¿¡ããããšã«ãããåä¿¡è ã«å°éãããšãã確å®æ§ã¯ãããŸããã ããã«ãããããã€èµ·ãã£ãããèšãããšããäžå¯èœã§ãïŒ
ãã®ããããªã¢ã«ã¿ã€ã ã§è©±ãå¿ èŠã¯ãããŸããã
ãŸãããã£ãã«ã®ãã©ããã£ã³ã°ã«ãåé¡ããããŸãã ã€ã³ã¹ããŒã«ã¯åãã§ãããããã©ã®ã¯ã©ã€ã¢ã³ããäœã§ãéä¿¡ã§ããæ®ãã¯ãã®ã¬ããŒãžãããµãŒããŒã¡ãã»ãŒãžã®ã¿ãéžæããå¿ èŠããããŸãã ãããŠãããã¯åœŒãããµãŒããŒããæ¥ããšããäºå®ã§ã¯ãããŸããã æ€èšŒã®åé¡ããããŸãã å°ãªããšãããµãŒããŒãžã®åã¡ãã»ãŒãžã«ã¯ããŠãŒã¶ãŒã«é¢ããæ å ±ã®ç¢ºèªèŠæ±ãéä¿¡ããå¿ èŠããããŸããããã«ãããæ··ä¹±ãçãã1ç§ããã30ã®éåžžã«ç¡æã®èŠæ±ãããã«çµäºããŸãã
ãããã
ãã¹ãŠã®åããããç¶æ³ã«ããããããã解決çãèŠã€ãããŸããã ããã¯Pubnubã§ãã ã äžè¬ã«ããã®ãµãŒãã¹ã¯ããã«äœ¿çšã§ãããªã³ã©ã€ã³ãã£ããçšã®SDKãæäŸããŸãããæ®å¿µãªããææã§ãããã¢ããªã³ãšåŒã°ããŸãã
ãµãŒãã¹èªäœã¯ããªã¢ã«ã¿ã€ã ã¢ããªã±ãŒã·ã§ã³ã«å¿ èŠãªãã¹ãŠãæäŸããŸãã ããããç§ãã¡ãé¢å¿ãæã£ãŠããã®ã¯ããªã¢ã«ã¿ã€ã ãã£ã³ãã«ãæŸéããããšã§ãã ãããã¯ç¡æã§äœ¿ããããããããŠããããæãéèŠãªããšã«ã¯ãã¢ã¯ã»ã¹å¶åŸ¡ããããŸãïŒ æ€èšŒã«ç ©ããããªãããã«å¿ èŠãªãã®ã
å·®å¥åã¯ãpublish_keyãšsubscribe_keyã®2ã€ã®å¥åã®ããŒã«ãããã®ã§ãã ãæ³åã®ãšãããæåã®ããŒã¯ãµãŒããŒã«éããã2çªç®ã®ããŒã¯ã¯ã©ã€ã¢ã³ãã¢ããªã±ãŒã·ã§ã³ã«éãããŸãã ããŒãæåã«ç§å¯ã«ããŠãããšã誰ããã£ãã«ãã¹ãã ããããã®äžã«ç€ºãããã¡ãã»ãŒãžãä¿¡é Œã§ããŸãã ããŒãã§ã¯ãïŒ
ããã¯ãšã³ã-Parse.com
ãµãŒããŒAPIãšãã®å®è£ ã®æŽçãéå§ããå¿ èŠããããŸããã ãã€ãã©ã€ã³ã®ã¢ã€ãã¢ã¯æ¬¡ã®ãšããã§ããããšãæãåºãããŠãã ããã
- ã«ã¹ã¿ã ïŒAPIçµç±ïŒãã°ã€ã³ïŒïŒ
- Parse.comã¯ã©ãŠãã³ãŒã
- ããŒã¿ããŒã¹ã§ã®ãŠãŒã¶ãŒäœæ
- afterSaveïŒïŒããªã¬ãŒã¯ãPubnubãã£ãã«ã«ãŠãŒã¶ãŒãã°ã€ã³ã«ã€ããŠéç¥ããŸã
- ãã°ã€ã³ïŒïŒã«å¿çããŠçŸåšã®ãŠãŒã¶ãŒãè¿ã
ãã®ããã«ã次ã®APIãäœæãããŸããã
- ãã°ã€ã³
- ãã°ã¢ãŠã
- GetOnlineUsers
- Getnow
åŸè ã«ã€ããŠèª¬æããŸãã ã¯ã©ã€ã¢ã³ãæéãšãµãŒããŒæéãåæããããã«äœ¿çšããäºå®ã§ããã APIèªäœãå®è£ ããŸããããã¯ã©ã€ã¢ã³ãã§ããã䜿çšããããšã¯ã§ããŸããã§ããã ãã ããAPIèªäœã¯ãã®ãŸãŸã«ããããšã«ããŸããã
ã³ãŒãã®åãããaã³ããŸãã 決ããŠjséçºè ã ãã®çµç¹ãžã®èŠæãæ€èšããŸãã
ãµãŒããŒã³ãŒã
/*global Parse:false, $:false, jQuery:false */ // Importas var _ = require('underscore'); // jshint ignore:line var moment = require('moment'); // jshint ignore:line // Constants var sessionObjName = "IMH_Session"; var sessionLifetimeSec = 13; var channelName = "events"; var publishKey = "pub-c-6271f363-519a-432d-9059-e65a7203ce0e", subscribeKey = "sub-c-a3d06db8-410b-11e5-8bf2-0619f8945a4f", httpRequestUrl = 'http://pubsub.pubnub.com/publish/' + publishKey + '/' + subscribeKey + '/0/' + channelName + '/0/'; // Utils function Log(obj, tag) { "use strict"; var loggingString = "Cloud_code: "; if (tag != null) { // jshint ignore:line loggingString += "[" + tag + "] "; } loggingString += JSON.stringify(obj) + "\n"; console.log(loggingString); // jshint ignore:line } function GetNow() { "use strict"; return moment.utc(); } // Supporting var baseSession = {udid: "", loginedAt: GetNow(), aliveTo: GetNow()}; var errorHandler = function(error) { "use strict"; Log(error.message, "error"); }; function DeleteSession(obj) { obj.set("loginedAt", obj.get("aliveTo")); SendEvent(obj); obj.destroy(); } function DeleteDeadSessions() { "use strict"; var query = new Parse.Query(sessionObjName); // jshint ignore:line var promise = query.lessThanOrEqualTo("aliveTo", GetNow().toDate()) .each(function(obj) { Log(obj, "Delete dead session"); DeleteSession(obj); } ); return promise; } function NewSession(udid) { "use strict"; var session = _.clone(baseSession); session.udid = udid; session.loginedAt = GetNow(); session.aliveTo = GetNow().add({seconds: sessionLifetimeSec}); return session; } function GetSessionQuery() { "use strict"; var objConstructor = Parse.Object.extend(sessionObjName); // jshint ignore:line var query = new Parse.Query(objConstructor); //query.select("udid", "loginedAt", "aliveTo"); //not work for some reason return query; } function IsUserOnline(udid, onUserOnlineHanlder, onUserOfflineHanlder, onError) { "use strict"; var userAlive = false; var query = GetSessionQuery(); query.equalTo("udid", udid).greaterThanOrEqualTo("aliveTo", GetNow().toDate()); query.find({ success: function(result) { if (result.length == 0) { onUserOfflineHanlder(); } else { onUserOnlineHanlder(result); } }, error: onError }); } function NewParseSession(session) { "use strict"; var objConstructor = Parse.Object.extend(sessionObjName); // jshint ignore:line var obj = new objConstructor(); obj.set({ udid: session.udid, loginedAt: session.loginedAt.toDate(), aliveTo: session.aliveTo.toDate() } ); return obj; } function SendEvent(session) { "use strict"; Parse.Cloud.httpRequest({ // jshint ignore:line url: httpRequestUrl + JSON.stringify(session), success: function(httpResponse) {}, error: function(httpResponse) { Log('Request failed with response code ' + httpResponse.status); } }); } // API functions var API_GetNow = function(request, response) { "use strict"; var onUserOnline = function(result) { response.success( GetNow().toDate() ); }; var onUserOffline = function(error) { response.error(error); }; var onError = function(error) { response.error(error); }; IsUserOnline(request.params.udid, onUserOnline, onUserOffline, onError); }; var API_GetOnlineUsers = function(request, response) { "use strict"; var onUserOnline = function(result) { var query = GetSessionQuery() .addDescending("aliveTo"); query.find({ success: function(result) { response.success( JSON.stringify(result) ); }, error: errorHandler }); }; var onUserOffline = function(error) { response.error(error); }; var onError = function(error) { response.error(error); }; DeleteDeadSessions().always( function() { IsUserOnline(request.params.udid, onUserOnline, onUserOffline, onError); }); }; var API_Login = function(request, response) { "use strict"; var userUdid = request.params.udid; var session = NewSession(userUdid); var parseObject = NewParseSession(session); Parse.Cloud.run("Logout", {udid: userUdid}).always( function() { parseObject.save(null, { success: function(obj) { Log(obj, "Login:save"); response.success( JSON.stringify(parseObject) ); }, error: function(error) { errorHandler(error); response.error(error); } }); }); }; var API_Logout = function(request, response) { "use strict"; var userUdid = request.params.udid; var query = GetSessionQuery() .equalTo("udid", userUdid); query.each( function(obj) { Log(obj, "Logout:destroy"); DeleteSession(obj); }).done( function() {response.success();} ); }; // Bindings Parse.Cloud.afterSave(sessionObjName, function(request) { // jshint ignore:line "use strict"; SendEvent(request.object); }); // API definitions Parse.Cloud.define("GetNow", API_GetNow); // jshint ignore:line Parse.Cloud.define("GetOnlineUsers", API_GetOnlineUsers); // jshint ignore:line Parse.Cloud.define("Login", API_Login); // jshint ignore:line Parse.Cloud.define("Logout", API_Logout); // jshint ignore:line
ã芧ã®ãšãããafterDeleteïŒïŒããªã¬ãŒãªãã§å®è¡ããŸããã ãã®çç±ã¯ãafterDeleteïŒïŒã§ã¬ãŒã¹ãããããã§ãã äžæ¹ãæ°ãããªãªãŒã¹ããããŠãŒã¶ãŒã¯åé€ãããããã«ãã£ãã«ã«ã¢ã©ãŒããéä¿¡ãããŸãã äžæ¹ã圌ã¯ããã«å床ãã°ã€ã³ããããšããŸãã
ãã®çµæããã£ãã«ã«ã¯ãXãã°ã€ã³æžã¿ãããXãã°ã€ã³æžã¿ãããXãã°ã¢ãŠãæžã¿ããªã©ã衚瀺ãããŸãã æåŸã®2ã€ã®ã¡ãã»ãŒãžã¯äžé©åã§ãã ãã®ããããŠãŒã¶ãŒããŸã ãçããŠãããããã§ãã°ã€ã³ããŠããããã«èŠãããããªã³ã©ã€ã³ãªã¹ãã«ã¯è¡šç€ºãããªããšããç¶æ³ãã¯ã©ã€ã¢ã³ãã«ãããŸããã
ããå€ãã®ãã¥ã¢ã³ã¹ïŒ
åè¿°ã®ããã«ãParse.comã§ã¯ãããã€ãã®Timerã§ã¯ãªãDateã䜿çšããŠãexpireAtïŒãã®å Žåã¯aliveToïŒãæŽçããå¿ èŠããããŸãã ããããããã«è³ªåããããŸã-ãããŠããã¹ãŠã®ãŠãŒã¶ãŒããçããŠãããããã§ã«ãæ»ãã§ããããã©ããããã€ãã§ãã¯ããã®ã§ããïŒ
1ã€ã®è§£æ±ºçã¯ã ãžã§ãã䜿çšããŠãéã¢ã¯ãã£ããªãŠãŒã¶ãŒã5ã10ç§ããšã«åé€ããããšã§ãã ããããå³å¯ã«èšãã°ãããã¯éåžžã«ãªã¢ã«ã¿ã€ã ã§ã¯ãããŸããã äœããã®çš®é¡ã®ããã¯ã°ã©ãŠã³ããžã§ãã«é¢ä¿ãªãããŠãŒã¶ãŒãå³åº§ã« "æ»ã«"ããããã£ãïŒãšããã§ãæ倧å®è¡æéã«å¶éããããŸã-15åããããã£ãŠãåžžã«åäœæããå¿ èŠããããŸãïŒã ãããã£ãŠãå¥ã®ã¢ãããŒããå®è£ ãããŸããã
éåžžã®ãŠãŒã¶ãŒã©ã€ãã¯æ¬¡ã®ããã«ãªããŸãã
ãã°ã€ã³-> GetOnlineUsers->ãã°ã¢ãŠã
ãŸãã¯
ãã°ã€ã³-> GetOnlineUsers->ã¢ããªã±ãŒã·ã§ã³ã®æå°åãã€ãŸãããã£ã³ãã«å ã®ã¡ãã»ãŒãžã®æ¬ èœ-> GetOnlineUsers->ãã°ã¢ãŠã
誰ããGetOnlineUsersã«å°ããç¬éã«ãæ»ãã ããŠãŒã¶ãŒãåé€ããããšã決å®ãããŸããã ããã¯ãå®éã«ã¯ãå°ãªããšã誰ãããã©ã€ãããŠãŒã¶ãŒã®ãªã¹ããèŠæ±ãããŸã§ãããããããŠãŒã¶ãŒãããŒã¿ããŒã¹ã«ä¿åã§ããããšãæå³ããŸãã ãã®æç¹ã§ããã¹ãŠã®ããããŠãŒã¶ãŒãåé€ãããŸãïŒã¬ã€ãžãŒã³ã³ãã¥ãŒãã£ã³ã°ã®æé«ã®äŒçµ±ïŒã
ãããã£ãŠããŠãŒã¶ãŒã®ãç掻ãã¯ã¯ã©ã€ã¢ã³ãäžã§ããŒã«ã«ã«ç£èŠããå¿ èŠããããŸãã ãŠãŒã¶ãŒã®æ»äº¡ã«é¢ãããã£ã³ãã«å ã®éç¥ã¯ããŠãŒã¶ãŒãèªåã§ãã°ã¢ãŠãããå Žåã«ã®ã¿éä¿¡ãããŸãã ãã以å€ã®å ŽåããŠãŒã¶ãŒã¯æ°žé ã«çããŠãããšèŠãªãããŸãã
Android
ãããã
Pubnub SDKããŸãã¯ãã®ç¡æã®éšåã¯éåžžã«äœ¿ããããã§ãã ãŸããPubnubã®ã©ãããŒãäœæããŠãããããã°ãä»ã®ãµãŒãã¹ã䜿çšã§ããããã«ããŸããã
Pubnub Wrap-ãã£ã³ãã«
public class PubnubChannel extends Channel { static private final String CHANNEL_NAME = "events"; static private final String SUBSCRIBE_KEY = "sub-c-a3d06db8-410b-11e5-8bf2-0619f8945a4f"; Pubnub pubnub = new Pubnub("", SUBSCRIBE_KEY); Callback pubnubCallback = new Callback() { @Override public void connectCallback(String channel, Object message) { if (listener != null) { listener.onConnect(channel, "Connected: " + message.toString()); } } @Override public void disconnectCallback(String channel, Object message) { if (listener != null) { listener.onDisconnect(channel, "Disconnected: " + message.toString()); } } @Override public void reconnectCallback(String channel, Object message) { if (listener != null) { listener.onReconnect(channel, "Reconnected: " + message.toString()); } } @Override public void successCallback(String channel, Object message, String timetoken) { if (listener != null) { listener.onMessageRecieve(channel, message.toString(), timetoken); } } @Override public void errorCallback(String channel, PubnubError error) { if (listener != null) { listener.onErrorOccur(channel, "Error occured: " + error.toString()); } } }; public PubnubChannel() { setName(CHANNEL_NAME); } @Override public void subscribe() throws ChannelException { try { pubnub.subscribe(CHANNEL_NAME, pubnubCallback); } catch (PubnubException e) { e.printStackTrace(); throw new ChannelException(ChannelException.CONNECT_ERROR, e); } } @Override public void unsubscribe() { pubnub.unsubscribeAll(); } }
次ã«ããã£ãã«å ã®äžéšã®ã¡ãã»ãŒãžã§ã¯ãªããç¹å®ã®ãŠãŒã¶ãŒã远跡ããããã«ããªãŒããŒã©ããïŒã¯ããã¯ãïŒã«ã©ãããŒãäœæãããŸããã
ãã£ã³ãã«ã©ãããŒ-ServerChannel
public class ServerChannel { Logger l = LoggerFactory.getLogger(ServerChannel.class); JsonParser jsonParser; Channel serverChannel; ServerChannel.EventListener listener; private final Channel.EventListener listenerAdapter = new Channel.EventListener() { @Override public void onConnect(String channel, String greeting) { } @Override public void onDisconnect(String channel, String reason) { if (listener != null) { listener.onDisconnect(reason); } } @Override public void onReconnect(String channel, String reason) { } @Override public void onMessageRecieve(String channel, String message, String timetoken) { if (listener != null) { ServerChannel.this.onMessageRecieve(message, timetoken); } } @Override public void onErrorOccur(String channel, String error) { l.warn(String.format("%s : [error] %s", channel, error)); if (listener != null) { ServerChannel.this.unsubscribe(); } } }; public ServerChannel(Channel serverChannel, JsonParser jsonParser) { this.serverChannel = serverChannel; this.jsonParser = jsonParser; } public final void setListener(@NonNull ServerChannel.EventListener listener) { this.listener = listener; } public final void clearListener() { listener = null; } public final void subscribe() throws ChannelException { try { serverChannel.setListener(listenerAdapter); serverChannel.subscribe(); } catch (ChannelException e) { e.printStackTrace(); serverChannel.clearListener(); throw e; } } public final void unsubscribe() { serverChannel.unsubscribe(); serverChannel.clearListener(); } public void onMessageRecieve(String userJson, String timetoken) { DyingUser dyingUser = jsonParser.fromJson(userJson, DyingUser.class); if (dyingUser != null) { if (dyingUser.isAlive()) { listener.onUserLogin(dyingUser); } else { listener.onUserLogout(dyingUser); } } } public interface EventListener { void onDisconnect(String reason); void onUserLogin(DyingUser dyingUser); void onUserLogout(DyingUser dyingUser); } }
Parse.com
ç¹°ãè¿ããŸãããäœãè€éã§ã¯ãããŸããã ãã¹ãŠã®ããžãã¯ã¯ãµãŒããŒã«ä¿åãããŸãã å¿ èŠãªã®ã¯ãAPIã䜿çšããŠjsonããªããžã§ã¯ãã«è§£æããããšã ãã§ãã
Authapi
public class AuthApi extends Api { static final String API_Login = "Login", API_Logout = "Logout"; @Inject public AuthApi(JsonParser parser) { super(parser); } public DyingUser login(@NonNull final String udid) throws ApiException { DyingUser dyingUser; try { String jsonObject = ParseCloud.callFunction(API_Login, constructRequestForUser(udid)); dyingUser = parser.fromJson(jsonObject, DyingUser.class); } catch (ParseException e) { e.printStackTrace(); throw new ApiException(ApiException.LOGIN_ERROR, e); } return dyingUser; } public void logout(@NonNull final DyingUser dyingUser) { try { ParseCloud.callFunction(API_Logout, constructRequestForUser(dyingUser.getUdid())); } catch (ParseException e) { e.printStackTrace(); } } }
UserApi
public class UserApi extends Api { static final String API_GetOnlineUsers = "GetOnlineUsers"; @Inject public UserApi(JsonParser parser) { super(parser); } public final ArrayList<DyingUser> getOnlineUsers(@NonNull final DyingUser dyingUser) throws ApiException { ArrayList<DyingUser> users; try { String jsonUsers = ParseCloud.callFunction(API_GetOnlineUsers, constructRequestForUser(dyingUser.getUdid())); users = parser.fromJson(jsonUsers, new TypeToken<List<DyingUser>>(){}.getType()); } catch (ParseException e) { e.printStackTrace(); throw new ApiException(ApiException.GET_USERS_ERROR, e); } return users; } }
ããŠãåºæ¬ã¯ã©ã¹ïŒ
API
abstract class Api { final JsonParser parser; Api(JsonParser parser) { this.parser = parser; } protected Map<String, ?> constructRequestForUser(@NonNull final String udid) { Map<String, String> result = new HashMap<>(); result.put("udid", udid); return result; } }
äžèšã®ã¯ã©ã¹ãšãã®ã¡ãœããã䜿çšããŠããã°ã€ã³ã«ã¢ã¯ã»ã¹ãããã°ã€ã³ããŠããŠãŒã¶ãŒã®ãªã³ã©ã€ã³ãªã¹ããååŸããŸãã
ãªã¢ã«ã¿ã€ã
UIã®æŽæ°
ãŠãŒã¶ãŒã¯ãæ»ã¬ããããéåžžã«è¿ éã§ãããããæ®ãã®ã©ã€ãã¿ã€ã ã衚瀺ããããšã決å®ãããŸããã ã©ã€ãã¿ã€ã ã¯ç§åäœã§æž¬å®ãããã¿ã¹ã¯ã®ç®æšã¯ãªã¢ã«ã¿ã€ã ã確ä¿ããããšãªã®ã§ãUIã¯å°ãªããšã1ç§ã«1åæŽæ°ããå¿ èŠããããŸãã ãããè¡ãããã«ãTimeTickerã¯ã©ã¹ãäœæããããã®ãªããžã§ã¯ãã¯Activityã«ä¿åãããŸãã onAttachïŒïŒäžã®ã¢ã¯ãã£ããã£ãã©ã°ã¡ã³ãã¯ãActivityïŒïŒããTimeTickerãªããžã§ã¯ããåãåãïŒããã«TimeTicker.Ownerã€ã³ã¿ãŒãã§ã€ã¹ã䜿çšãããŸãïŒããã®ã€ãã³ãããµãã¹ã¯ã©ã€ãããŸãã
ã¿ã€ã ãã£ãã«ãŒ
public class TimeTicker extends Listenable<TimeTicker.EventListener> { private static final long TICKING_PERIOD_MS_DEFAULT = 1000; private static final boolean DO_INSTANT_TICK_ON_START_DEFAULT = true; long tickingPeriodMs; boolean doInstantTickOnStart; final Handler uiHandler = new Handler(Looper.getMainLooper()); final Timer tickingTimer = new Timer(); TimerTask tickingTask; public TimeTicker() { this(DO_INSTANT_TICK_ON_START_DEFAULT); } public TimeTicker(boolean doInstantTickOnStart) { this.doInstantTickOnStart = doInstantTickOnStart; setTickingPeriodMs(TICKING_PERIOD_MS_DEFAULT); } public void setTickingPeriodMs(final long tickingPeriodMs) { this.tickingPeriodMs = tickingPeriodMs; } public synchronized void start() { if (tickingTask != null) { stop(); } tickingTask = new TimerTask() { @Override public void run() { uiHandler.post(new Runnable() { @Override public void run() { forEachListener(new ListenerExecutor<TimeTicker.EventListener>() { @Override public void run() { getListener().onSecondTick(); } }); } }); } }; long delay = (doInstantTickOnStart) ? 0 : tickingPeriodMs; tickingTimer.scheduleAtFixedRate(tickingTask, delay, tickingPeriodMs); } public synchronized void stop() { if (tickingTask != null) { tickingTask.cancel(); } tickingTask = null; tickingTimer.purge(); } public interface EventListener extends Listenable.EventListener { void onSecondTick(); } public interface Owner { TimeTicker getTimeTicker(); } }
ããã«ãããUIã1ç§éã«1åæŽæ°ãããããã«ãªããŸããã€ãŸãããã¹ãŠããŠãŒã¶ãŒã®æ»ã«ãããŠããããã«èŠããŸãã
ãæ»ã«ãããŠããããŠãŒã¶ãŒã®ãªã¹ã
ãã®åé¡ã¯ããã®ã¿ã¹ã¯ã«é¢é£ããæãèå³æ·±ããã®ã®ããã«æããŸããããæ»ã«ãããŠããããŠãŒã¶ãŒã®ãªã¹ãããããŸãã æéã¯ãŒãã«è¿ã¥ãããããçºçããå ŽåããŠãŒã¶ãŒããªã¹ãããåé€ããå¿ èŠããããŸãã
æãç°¡åãªå®è£ ã¯ãåãŠãŒã¶ãŒã«ã¿ã€ããŒãä»å ãããæ»ãã«éãããšãã«åé€ããããšã§ãã ãã ããããã¯ç¹ã«èå³æ·±ã解決çã§ã¯ãããŸããã åé¯ããŸãããïŒ ããã«ãåäžã®ã¿ã€ããŒãšäžæåæ¢/åéã®å¯èœæ§ãåãããã®ãããªå®è£ ããããŸãïŒããšãã°ãã¢ããªã±ãŒã·ã§ã³ãæå°åãããŠããå Žåã«äŸ¿å©ã§ãïŒã
TemporarySet
public class TemporarySet<TItem> extends Listenable<TemporarySet.EventListener> implements Resumable { protected final SortedSet<TemporaryElement<TItem>> sortedElementsSet = new TreeSet<>(); protected final List<TItem> list = new ArrayList<>(); protected final Timer timer = new Timer(); protected TimerTask timerTask = null; protected TemporaryElement<TItem> nextElementToDie = null; boolean isResumed = false; public TemporarySet() { notifier = new TemporarySet.EventListener() { @Override public void onCleared() { for (TemporarySet.EventListener listener : getListenersSet()) { listener.onCleared(); } } @Override public void onAdded(Object item) { for (TemporarySet.EventListener listener : getListenersSet()) { listener.onAdded(item); } } @Override public void onRemoved(Object item) { for (TemporarySet.EventListener listener : getListenersSet()) { listener.onRemoved(item); } } }; } public boolean add(TItem object, DateTime deathTime) { TemporaryElement<TItem> element = new TemporaryElement<>(object, deathTime); return _add(element); } public boolean remove(TItem object) { TemporaryElement<TItem> element = new TemporaryElement<>(object); return _remove(element); } public void clear() { _clear(); } public final List<TItem> asReadonlyList() { return Collections.unmodifiableList(list); } private synchronized void _clear() { cancelNextDeath(); list.clear(); sortedElementsSet.clear(); notifier.onCleared(); } private synchronized boolean _add(TemporaryElement<TItem> insertingElement) { boolean wasInserted = _insertElementUnique(insertingElement); if (wasInserted) { if (nextElementToDie != null && nextElementToDie.deathTime.isAfter(insertingElement.deathTime)) { cancelNextDeath(); } if (nextElementToDie == null) { openNextDeath(); } notifier.onAdded(insertingElement.object); } return wasInserted; } private synchronized boolean _remove(TemporaryElement<TItem> deletingElement) { boolean wasDeleted = _deleteElementByObject(deletingElement); if (wasDeleted) { if (nextElementToDie.equals(deletingElement)) { cancelNextDeath(); openNextDeath(); } notifier.onRemoved(deletingElement.object); } return wasDeleted; } private synchronized void openNextDeath() { cancelNextDeath(); if (sortedElementsSet.size() != 0) { nextElementToDie = sortedElementsSet.first(); timerTask = new TimerTask() { @Override public void run() { _remove(nextElementToDie); } }; DateTime now = new DateTime(); Duration duration = TimeUtils.GetNonNegativeDuration(now, nextElementToDie.deathTime); timer.schedule(timerTask, duration.getMillis()); } } private synchronized void cancelNextDeath() { if (timerTask != null) { timerTask.cancel(); } timer.purge(); nextElementToDie = null; timerTask = null; } private synchronized Iterator<TemporaryElement<TItem>> findElement(TemporaryElement<TItem> searchingElement) { Iterator<TemporaryElement<TItem>> resultIterator = null; for (Iterator<TemporaryElement<TItem>> iterator = sortedElementsSet.iterator(); iterator.hasNext() && resultIterator == null;) { if (iterator.next().equals(searchingElement)) { resultIterator = iterator; } } return resultIterator; } private synchronized boolean _insertElementUnique(TemporaryElement<TItem> element) { boolean wasInserted = false; Iterator<TemporaryElement<TItem>> iterator = findElement(element); if (iterator == null) { wasInserted = true; sortedElementsSet.add(element); list.add(element.object); } return wasInserted; } private synchronized boolean _deleteElementByObject(TemporaryElement<TItem> element) { boolean wasDeleted = false; Iterator<TemporaryElement<TItem>> iterator = findElement(element); if (iterator != null) { wasDeleted = true; iterator.remove(); list.remove(element.object); } return wasDeleted; } @Override public void resume() { isResumed = true; openNextDeath(); } @Override public void pause() { cancelNextDeath(); isResumed = false; } @Override public boolean isResumed() { return isResumed; } public interface EventListener extends Listenable.EventListener { void onCleared(); void onAdded(Object item); void onRemoved(Object item); } }
ããã§ã¯äœ¿çšããªãasReadonlyListã¡ãœãããããããšã«æ³šæããŠãã ããã 以åã¯ãListFragmentã®ã¢ããã¿ãŒåŒæ°ãšããŠäœ¿çšãããŠãããããEventListenerããŸã£ãã䜿çšã§ããŸããã§ããã ããããåŸã§ç§ã¯ãã®ãã³ãã£ãŒããé¢ããããšã«æ±ºããŸããããã³ãŒããæ®ãããšã«ããŸããïŒå°æ¥ã®ããã«ãã©ãããã°äŸ¡å€ããªãããèŠãããïŒã
ãã®ãªã¹ãå ã®æ倧ã®ããã«ãã¢ã¯ãã¡ãœããfindElementã_insertElementUniqueããã³_deleteElementByObjectã§äœæãããŸãã ãã®çç±ã¯ãSortedSetã¯æ¥ä»é ã«ãœãŒãããããªããžã§ã¯ããæ ŒçŽãããããæ€çŽ¢ãæ¥ä»ããšã«è¡ãããããã§ãã ãã ãããŠãŒã¶ãŒããæ»äº¡ããããšããµãŒããŒã¯loginedAt == deathAtãšããã¡ãã»ãŒãžãéä¿¡ããŸããããã«ãããSortedSetãšTemporarySetå šäœãçæ°ã«ãªããŸãã
Javaã«ã¯éåžžã®ãã¢<AãB> ïŒ updïŒ Bringoffãæ£ããææããããã«ã ãŸã ãããŸã ïŒããªããããã©ãããŒãå®è£ ãããŸããïŒ
TemporaryElement
class TemporaryElement<T> implements Comparable { protected final T object; protected final DateTime deathTime; public TemporaryElement(@NonNull T object, @NonNull DateTime deathTime) { this.deathTime = deathTime; this.object = object; } public TemporaryElement(@NonNull T object) { this(object, new DateTime(0)); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TemporaryElement<?> that = (TemporaryElement<?>) o; return object.equals(that.object); } @Override public int hashCode() { return object.hashCode(); } @Override public int compareTo(@NonNull Object another) { TemporaryElement a = this, b = (TemporaryElement) another; int datesComparisionResult = a.deathTime.compareTo(b.deathTime); int objectsComparisionResult = a.hashCode() - b.hashCode(); return (datesComparisionResult != 0) ? datesComparisionResult : objectsComparisionResult; } }
ãã®çµæãå®è£ ãããTemporarySetã§ã¯ãåç¶æéã®ãããŠãŒã¶ãŒãè¿œå ãŸãã¯åé€ã§ããŸãããã®åŸãTemporarySet.EventListenerã€ã³ã¿ãŒãã§ã€ã¹ãå®è£ ããŠåŸ æ©ããã ãã§ãã
ãããã«
ã¿ã¹ã¯ã¯åœåã®èšç»ãããå°é£ã§ããã Parse.comã¬ã€ãã®è§£æã«å€ãã®æéãè²»ãããŸããã ãã¥ã¢ã³ã¹ã®1ã€ã®äŸã次ã«ç€ºããŸãã
afterSave
Parse.Cloud.afterSave("Foo", function(request) {}); // custom Foo object Parse.Cloud.afterSave("User", function(request) {}); // custom(!) User object Parse.Cloud.afterSave(Parse.User, function(request) {}); // Parse.com User object Parse.Cloud.afterSave(Parse.Session, function(request) {}); // error! can't bind to Parse.Session
ã°ã©ããŒã·ã§ã³ã¢ãã¡ãŒã·ã§ã³ã«ã¯ããã«å€ãã®æéãè²»ããããŸããã ããæ£ç¢ºã«ã¯ãã¢ãã¡ãŒã·ã§ã³ã§ã¯ãªããã¿ãŒã³ããŒãœãªã¥ãŒã·ã§ã³ãèŠã€ããããšã§ã¯ãããŸããã æ®å¿µãªãããèªåã«é©ããæ¹æ³ãèŠã€ãããªãã£ãããã解決çãæžããŸããã ç§ã¯è±èªã§ã¹ã¿ãã¯ãªãŒããŒãããŒã詳现ã«æããŸãã ã
ãã¹ãŠã®ç§ã®ã³ãŒãã¯ããã§èŠãããšãã§ããŸã ã
å ¬å¹³ãæããŠãGetUsersChangesAfterDateïŒïŒã®ãããªãã®ãAPIã«è¿œå ãããšãæå®ããæ¥ä»ã®åŸã«ãŠãŒã¶ãŒãªã¹ãã®å€æŽãååŸã§ããããã«ãªããŸãïŒã€ãŸããã¢ããªã±ãŒã·ã§ã³ãæå°å->å±é-> GetUsersChangesAfterDateïŒã
æåŸã«ãèªè ã«ããã€ã質åãããããšæããŸãã
- ããã¯ç°¡åã§ãããç¡æã§ãã§ããŸããïŒ
- Nç§ããšã«UIãæŽæ°ããç°¡åãªæ¹æ³ã¯ãããŸããïŒ
- ãŠãŒã¶ãŒã®ç涯ã0ïŒ0ããã©ãããŸããïŒ ãŠãŒã¶ãŒãã0ïŒ1ãã®åŸã«ãæ»ã¬ãããã«ãã©ã€ãã¿ã€ã ã«äººçºçã«1ç§ãè¿œå ããå¿ èŠããããŸããïŒ ãããšãå¥ã®æ¹æ³ã§æ±ºå®ãããŸããïŒ ãŸãã¯ãã0ïŒ0ãã®ãŸãŸã«ããŸã-ããã¯æ£åžžã§ããïŒ