PUSHっぽい何かを実現する
あくまでもローカルな環境向け。基本的にはGCMをつかうべきです。 WebSocket覚えたし、使ってみたかってん。ぐらいのなにか。
Android側にPushを受けるサービスを実装する
アプリケーション内にServiceを実装し、ws://IPADDRESS:PORT/path/to/url に対しWebSocketを張る部分を作る。 WebSocketの実装には、 Java-WebSocket というライブラリを利用しました。
まず、build.gradleにライブラリをインポートする部分を書きます。
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'org.java-websocket:Java-WebSocket:1.3.0@jar' }
続いて、Activityの実装。 Activityには2個ボタンを用意して、ServiceのStop,Startの制御とServerへのメッセージを投げる部分をハンドリングしています。
@Override public void onClick(View view) { Intent intent = new Intent(); intent.setClass(getApplicationContext(), PushService.class); switch (view.getId()){ case R.id.button1: { if (!isServiceRunning("com.kobashin.sample.PushService")) { isRunning = true; PushService.startForegroundService(getApplicationContext(), SERVER_URI); } else { isRunning = false; PushService.stopForegroundService(getApplicationContext()); } break; } case R.id.button2: { PushService.sendMessageToServer(getApplicationContext(), "Hello Server"); } } } private boolean isServiceRunning(String className){ boolean ret = false; ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(Integer.MAX_VALUE); for(ActivityManager.RunningServiceInfo info : services){ if(info.service.getClassName().equals(className)){ ret = true; } } return ret; }
isServiceRunning()メソッドはServiceの実行状態の確認用のメソッドです。 Serviceへの通知にはService側にpublic staticで実装したメソッド(PushService.startForegroundServiceなど)を利用しています。
さらに、Service側の実装。ぺたっと全部。
public class PushService extends Service { private static String LOG_TAG = "PushService"; private NotificationManager mNotificationManager; private final IBinder mBinder = new LocalBinder(); private static final String ACTION_START_FOREGROUND = "com.kobashin.sample.START_FOREGROUND"; private static final String ACTION_STOP_FOREGROUND = "com.kobashin.sample.STOP_FOREGROUND"; private static final String ACTION_SEND_MESSAGE_TO_SERVER = "com.kobashin.sample.SEND_MESSAGE_TO_SERVER"; private WebSocketClient mWebSocketClient; private HandlerThread mHandlerThread; private MsgHandler mMsgHandler; private enum NOTIFY_ID { NOTIFICATION_STARTED(100), NOTIFICATION_END(101), NOTIFICATION_RECEIVE_MESSAGE(102); NOTIFY_ID(int i) { id = i; } private int id; public int getId() { return id; } } private class MsgHandler extends Handler { public MsgHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case 0: //init handleInitAction(msg); break; case 1: //onMessage handleMessageAction(msg); break; case 2: //send message handleSendMessageAction(msg); default: break; } } private void handleSendMessageAction(Message msg) { try { mWebSocketClient.send((String) msg.obj); } catch (NotYetConnectedException e) { e.printStackTrace(); } } private void handleMessageAction(Message msg) { showNotification(NOTIFY_ID.NOTIFICATION_RECEIVE_MESSAGE.getId(), "GetMessage", (String) msg.obj, false); } private void handleInitAction(Message msg) { try { mWebSocketClient = new WebSocketClient(new URI((String) msg.obj)) { @Override public void onOpen(ServerHandshake handshakedata) { Log.i(LOG_TAG, "[WebSocketClient] onOpen"); } @Override public void onMessage(String message) { Log.i(LOG_TAG, "[WebSocketClient] onMessage: " + message); Message msg = mMsgHandler.obtainMessage(); msg.what = 1; // onMessage msg.obj = message; mMsgHandler.sendMessage(msg); } @Override public void onClose(int code, String reason, boolean remote) { Log.i(LOG_TAG, "[WebSocketClient] onClose: code(" + code + ") reason(" + reason + ") remote(" + remote + ")"); } @Override public void onError(Exception ex) { Log.i(LOG_TAG, "[WebSocketClient] onError", ex); } }; mWebSocketClient.connect(); } catch (URISyntaxException e) { e.printStackTrace(); // TODO: stop & notify } } } public static void startForegroundService(Context context, String serverUri) { Intent intent = new Intent(context, PushService.class); intent.putExtra("SERVER_URI", serverUri); intent.setAction(ACTION_START_FOREGROUND); context.startService(intent); } public static void stopForegroundService(Context context) { Intent intent = new Intent(context, PushService.class); intent.setAction(ACTION_STOP_FOREGROUND); context.startService(intent); } public static void sendMessageToServer(Context context, String message) { Intent intent = new Intent(context, PushService.class); intent.putExtra("SEND_MESSAGE", message); intent.setAction(ACTION_SEND_MESSAGE_TO_SERVER); context.startService(intent); } public PushService() { } public class LocalBinder extends Binder { PushService getService() { return PushService.this; } } @Override public void onCreate() { super.onCreate(); mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent.getAction().equals(ACTION_START_FOREGROUND)) { startForeground(NOTIFY_ID.NOTIFICATION_STARTED.getId(), createNotification( NOTIFY_ID.NOTIFICATION_STARTED.getId(), true)); mHandlerThread = new HandlerThread("push_service", Process.THREAD_PRIORITY_FOREGROUND); mHandlerThread.start(); mMsgHandler = new MsgHandler(mHandlerThread.getLooper()); Message msg = mMsgHandler.obtainMessage(); msg.what = 0; //init msg.obj = intent.getStringExtra("SERVER_URI"); mMsgHandler.sendMessage(msg); // init } else if (intent.getAction().equals(ACTION_STOP_FOREGROUND)) { stopForeground(true); stopSelf(); } else if (intent.getAction().equals(ACTION_SEND_MESSAGE_TO_SERVER)) { Message msg = mMsgHandler.obtainMessage(); msg.what = 2; //send message msg.obj = intent.getStringExtra("SEND_MESSAGE"); mMsgHandler.sendMessage(msg); } return START_STICKY; } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onDestroy() { super.onDestroy(); cancelNotification(NOTIFY_ID.NOTIFICATION_STARTED.getId()); mWebSocketClient.close(); mHandlerThread.quit(); } private void showNotification(int id, String title, String msg, boolean isOnGoing) { PendingIntent pendingIntent = PendingIntent .getActivity(getApplicationContext(), 0, new Intent(), 0); Notification.Builder builder = new Notification.Builder(getApplicationContext()) .setContentTitle(title) .setContentText(msg) .setSmallIcon(R.mipmap.ic_notificataion) .setColor(0x4fc3f7) .setPriority(Notification.PRIORITY_HIGH) .setFullScreenIntent(pendingIntent, true) .setOngoing(isOnGoing) // can't dismiss .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); mNotificationManager.notify(id, builder.build()); } private Notification createNotification(int id, boolean isOnGoing) { PendingIntent pendingIntent = PendingIntent .getActivity(getApplicationContext(), 0, new Intent(), 0); Notification.Builder builder = new Notification.Builder(getApplicationContext()) .setContentTitle("PushService") .setContentText("push service started") .setSmallIcon(R.mipmap.ic_notificataion) .setColor(0x4fc3f7) .setPriority(Notification.PRIORITY_HIGH) .setFullScreenIntent(pendingIntent, true) .setOngoing(isOnGoing) // can't dismiss .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)); return builder.build(); } private void cancelNotification(int id) { mNotificationManager.cancel(id); } }
handleInitAction()内でWebsocketのinit処理を行っています。
WebSocketClientはライブラリに用意されたクラスで、作成時にURI(ここでは ws://192.168.11.4:5000/socket_handset/bing
)を渡します。
Soceketを閉じるときはWebSocketClient.close()を呼び出します。
作成したSocketにメッセージを受けると、WebSocketClient.onMessage()を受けることができます。
Websocketからメッセージを受けると、Notificationを出すように実装してあるので、Socketにサーバーからメッセージを入れればPUSHっぽいことが実現できます。
Server側の実装
#!/usr/bin/env python # -*- coding: utf-8 -*- from flask import Flask, request from flask_sockets import Sockets from werkzeug.exceptions import abort from gevent import pywsgi from geventwebsocket.handler import WebSocketHandler from geventwebsocket.websocket import WebSocketError app = Flask(__name__) sockets = Sockets(app) ws_list = set() @sockets.route('/socket_handset/bind') def bind_sockets(ws): ''' handsetとのwebsocket接続用 :param ws: :return: ''' if not ws: abort(400) ws_list.add(ws) remove = set() print 'adding', len(ws_list) while True: try: msg = ws.receive() print "get message: ", msg for s in ws_list: try: s.send(msg) except Exception: remove.add(s) break except WebSocketError: remove.add(ws) break for s in remove: ws_list.remove(s) @app.route('/send/message', methods=['POST']) def send_message(): ''' JSONがPOSTされたら、つながっているWebsocketにデータを流す JSONは以下形式を期待している { "message": "message to send" } curlでのJSONのPOSTは以下でできる curl -vv -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"message": "message from server"}' http://localhost:5000/send/message :param message: :return: ''' if request.method == 'POST': print "send_message", request.json json_data = request.json print len(ws_list) remove = set() for s in ws_list: try: s.send(json_data["message"]) except Exception: remove.add(s) for s in remove: ws_list.remove(s) return 'ok' abort(400) @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': server = pywsgi.WSGIServer(('', 5000), app, handler_class=WebSocketHandler) server.serve_forever()
PUSH確認は以下curlで。
$ curl -vv -H "Accept: application/json" -H "Content-type: application/json" -X POST -d '{"message": "message from server"}' http://localhost:5000/send/message
Notificationが出たらOK!