ぴよぴよエンジニアの日記

クラウドベンダーに勤める見習いSEの日記です。発言は私自身の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

Bluemix の Push Notifications を試してみた

最近はプッシュ通知といえば mBaaS を使うのがスタンダードという風潮を感じます.

Amazon SNSAzure Notification Hubs など、各社プッシュ通知のサービスを提供しています.


Bluemix にもプッシュ通知のサービスが存在します.

console.bluemix.net


価格プランは"ライト"と"基本"の2つになります.

f:id:Santea:20170801213024p:plain

f:id:Santea:20170801213252p:plain


クライアント SDK は以下の4つが用意されています.

サーバー SDK は以下の3つが用意されています.

  • Java
  • Node.js
  • Swift


今回はクライアントサイドに Android SDK を、サーバーサイドに Java SDK を用いたパターンを試してみたいと思います.



クライアントサイド

github.com
github.com

上記が iOS, Android 用の SDK となります.
今回は Android の実装を行います.


github.com

サンプルアプリはこちらになります.
サンプルアプリの中でポイントとなるところを抜粋して説明します.


AndroidManifest.xml

<activity
    android:name=".MainActivity"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>

    <!--Notification Intent -->
    <intent-filter>
        <action android:name="com.ibm.hellopush.IBMPushNotification" />
        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

Activity に通知インテントを受けるための記述を加えます.

<service android:exported="true" android:name="com.ibm.mobilefirstplatform.clientsdk.android.push.api.MFPPushIntentService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT"/>
    </intent-filter>
</service>
<service android:exported="true" android:name="com.ibm.mobilefirstplatform.clientsdk.android.push.api.MFPPush">
    <intent-filter>
        <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
    </intent-filter>
</service>

IntentService の記述を加えます.
自前で IntentService を書かなくてもよいのは便利ですね.


MainActivity.java

MFPPushResponseListener registrationResponselistener = new MFPPushResponseListener<String>() {
            @Override
            public void onSuccess(String response) {
                // Split response and convert to JSON object to display User ID confirmation from the backend
                String[] resp = response.split("Text: ");
                try {
                    JSONObject responseJSON = new JSONObject(resp[1]);
                    setStatus("Device Registered Successfully with USER ID " + responseJSON.getString("userId"), true);
                } catch (JSONException e) {
                    e.printStackTrace();
                }

                Log.i(TAG, "Successfully registered for push notifications, " + response);
                // Start listening to notification listener now that registration has succeeded
                push.listen(notificationListener);
            }
            ...
};

トークン登録時のリスナーの記述です.
レスポンスの JSON は以下のようなものが返ってきます.

{"createdTime":"2017-07-27T13:45:33Z",
"lastUpdatedTime":"2017-07-31T09:18:04Z",
"createdMode":"API",
"deviceId":"XXXXXXX-01b0-36df-96d7-3cbc3c32118f",
"userId":"1234567890",
"token":"XXXXXXXXX-VrbN5G5QHqw9snkRhzNEf6-XXXXXX",
"platform":"G",
"href":"https:\/\/imfpush.ng.bluemix.net\/imfpush\/v1\/apps\/XXXXXX-43c2-8bfc-c5c2eb0e9124\/devices\/XXXXXX-36df-96d7-3cbc3c32118f"}

こちらに含まれる deviceId をサーバーサイドで指定することで、トークンを意識することなく通知を送れます.
この deviceId はアプリ再インストールなどでトークンが変わっても維持されるものです.
トークンのメンテナンスが必要ないのは大変便利ですね.

push.registerDeviceWithUserId("1234567890",registrationResponselistener);

先ほどの JSON に含まれていた userId はトークン登録時に明示的に指定することができます.
これにより、複数のデバイスを1人のユーザーに紐づけることが可能なり、ユーザー単位でのプッシュ通知送信を実現できます.


クライアントサイドの説明は以上になります.



サーバーサイド

今回は maven プロジェクトで実装しました.


pox.xml

<dependencies>
	<dependency>
		<groupId>com.ibm.mobilefirstplatform.serversdk.java</groupId>
		<artifactId>push</artifactId>
		<version>1.0.1</version>
	</dependency>
</dependencies>

上記の dependency の記述を追加します.



public static void main( String[] args ) throws SSLException, IOException
    {
        //(appGuid, appSecret)
        PushNotifications.init("XXXXXXXX-5ce7-43c2-8bfc", "XXXXXXXX-89d0-40b1-bdaf", PushNotifications.US_SOUTH_REGION);

        NotificationBuilder builder = new NotificationBuilder("This is the notification's text!");

        JSONObject notification = builder
    	.setTarget(new String[]{"XXXXXXX-01b0-36df-96d7-3cbc3c32118f"},
    			new String[]{"1234567890"},
    			new PushNotificationsPlatform[]{PushNotificationsPlatform.GOOGLE},
    			new String[]{"Tag1", "Tag2"})
    	.build();


    	PushNotifications.send(notification, new PushNotificationsResponseListener(){

    		public void onSuccess(int statusCode, String responseBody) {
    			System.out.println("Successfully sent push notification! Status code: " + statusCode + " Response body: " + responseBody);

    		}

    		public void onFailure(Integer statusCode, String responseBody, Throwable t) {
    			System.out.println("Failed sent push notification. Status code: " + statusCode + " Response body: " + responseBody);

    			if(t != null){
    				t.printStackTrace();
    			}
    		}
    	});
}

こちらがサーバーサイドのコードになります.

//(appGuid, appSecret)
PushNotifications.init("XXXXXXXX-5ce7-43c2-8bfc", "XXXXXXXX-89d0-40b1-bdaf", PushNotifications.US_SOUTH_REGION);

初期化の部分では、第一引数にサービス資格情報の appGuid を、第二引数に appSecret を指定します.

JSONObject notification = builder
    	.setTarget(new String[]{"XXXXXXX-01b0-36df-96d7-3cbc3c32118f"}, // deviceId
    			new String[]{"1234567890"}, // userId
    			new PushNotificationsPlatform[]{PushNotificationsPlatform.GOOGLE}, // Platform
    			new String[]{"Tag1", "Tag2"}) // Tag
    	.build();

上記がプッシュ通知オブジェクト生成部分です.
deviceId やクライアントサイドで登録した userId を指定することができます.
いづれか1つ以上を指定する必要があります(指定しない場合は Null または空文字をセットします).

builder.setMessageURL(urlToBeIncludedWithThePushNotification)
	.setTarget(deviceIdArray, userIdArray, platformArray, tagNameArray)
	.setAPNSSettings(badge, category, iosActionKey, payload, soundFile, APNSNotificationType)
	.setGCMSettings(collapseKey, delayWhileIdle, jsonPayload, GCMPriority.HIGH, soundFile, secondsToLive)
        .build();

また、各プラットフォームごとの設定も個別に行えるようになっています.


以上がサーバーサイドの実装でした.



まとめ

今回は Bluemix の Push Notifications を試してみました.


mBaaS を利用することで、プラットフォーム間の差異を吸収できたり、トークンを意識する必要がなかったりと、自前で実装するのに比べ優位な点が見えました.
気になるランニングコストも100万リクセストにつき1$であれば運用しやすいかと思います.


Bluemix Push Notifications ならではの特徴としては、やはり サーバーサイド Swift の SDK が提供されているところでしょうか.
サーバーサイドを Swift で書かれている方には有効な選択肢になるかと思います.


以上です.

Watson Developer Cloud .NET Standard SDK を Xamarin で使ってみた(Visual Recognition 編)

santea.hateblo.jp

前回に引き続き、Watson Developer Cloud .NET Standard SDK の内容です.


今回は、Visual Recognition の Detect Faces を使ってみます.




実装環境

  • macOS Sierra 10.12.5 (16F73)
  • Visual Studio for Mac 7.0.1 (build 24)
  • IBM.WatsonDeveloperCloud.VisualRecognition.v3 1.1.0
  • Xamarin.Forms 2.3.4.247



XAML

<?xml version="1.0" encoding="UTF-8"?>

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="WatsonSdkSample.VisualRecognition.VisualRecognitionPage1">
    <ContentPage.Content>
        <StackLayout>
            <Grid HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
                <ActivityIndicator x:Name="indicator" 
                                   IsRunning="True" 
                                   IsVisible="False" 
                                   VerticalOptions="Center"
                                   HorizontalOptions="Center" />
                <Image x:Name="image" 
                       HorizontalOptions="Fill" 
                       VerticalOptions="Fill" 
                       Margin="5,12,5,0" />
            </Grid>
            <Label x:Name="label" 
                   HorizontalTextAlignment="Center" />
            <Button BackgroundColor="#2196F3" 
                    TextColor="White" 
                    Text="Pick Photo" 
                    Clicked="Handle_Clicked" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Image に選択した画像を表示し、Label に画像認識の結果を表示します.



XAML.cs

using System;
using System.Diagnostics;
using System.IO;
using IBM.WatsonDeveloperCloud.VisualRecognition.v3;
using Plugin.Media;
using Xamarin.Forms;

namespace WatsonSdkSample.VisualRecognition
{
	public partial class VisualRecognitionPage1 : ContentPage
	{
		private const string ApiKey = "XXXXXXXXXXXXXXXXXXX";
		private readonly VisualRecognitionService _visualRecognition;

		public VisualRecognitionPage1()
		{
			InitializeComponent();

			_visualRecognition = new VisualRecognitionService();
			_visualRecognition.SetCredential(ApiKey);
		}

		public async void Handle_Clicked(object sender, EventArgs e)
		{
			image.Source = null;
			indicator.IsVisible = true;

			await CrossMedia.Current.Initialize();

			if (!CrossMedia.Current.IsPickPhotoSupported)
			{
				await DisplayAlert("No Auth", "Picking images denied.", "OK");
				return;
			}

			var file = await CrossMedia.Current.PickPhotoAsync();

			if (file == null)
				return;

			/** 画像認識 **/
			var imageByteArray = ReadFully(file.GetStream());

			var result = _visualRecognition.DetectFaces(imageData: imageByteArray, imageDataName: file.AlbumPath,
				imageDataMimeType: "image/jpeg");

			var text = "";
			foreach (var face in result.Images[0].Faces)
			{
				text += string.Format("Left: {0}, Top: {1}, Width: {2}, Height: {3}, " +
									  "Gender: {4}, GenderScore: {5}, AgeMax: {6}, AgeMin: {7}, AgeScore: {8}, " +
									  "IdentifyName: {9}, IdentifyScore: {10}, IdentifyHierarchy: {11}",
							face.FaceLocation.Left,
							face.FaceLocation.Top,
							face.FaceLocation.Width,
							face.FaceLocation.Height,
							face.Gender.Gender,
							face.Gender.Score,
							face.Age.Max,
							face.Age.Min,
							face.Age.Score,
							face.Identity?.Name,
							face.Identity?.Score,
							face.Identity?.TypeHierarchy) + System.Environment.NewLine;
			}

			image.Source = ImageSource.FromStream(() =>
			{
				var stream = file.GetStream();
				file.Dispose();
				return stream;
			});
			label.Text = text;
			indicator.IsVisible = false;
		}

		private static byte[] ReadFully(Stream input)
		{
			using (var ms = new MemoryStream())
			{
				input.CopyTo(ms);
				return ms.ToArray();
			}
		}
	}
}

Visual Recognition の部分を抜粋して説明します.

_visualRecognition = new VisualRecognitionService();
_visualRecognition.SetCredential(ApiKey);

VisualRecognitionService を初期化します.
SetCredential で VisualRecognition の API KEY を設定します.



var result = _visualRecognition.DetectFaces(imageData: imageByteArray, imageDataName: file.AlbumPath,
				imageDataMimeType: "image/jpeg");

imageData に Plugin.Media で取得した Stream を byte[] に変換したものを渡します.

imageDataName にファイルパスを、imageDataMimeType に "image/jpeg" を渡します.
(本来は、imageDataName, imageDataMimeType どちらかを指定すればいい気がしますが、この辺はもう少しソースを読んで確認します)



foreach (var face in result.Images[0].Faces)
{
	text += string.Format("Left: {0}, Top: {1}, Width: {2}, Height: {3}, " +
						"Gender: {4}, GenderScore: {5}, AgeMax: {6}, AgeMin: {7}, AgeScore: {8}, " +
						"IdentifyName: {9}, IdentifyScore: {10}, IdentifyHierarchy: {11}",
				face.FaceLocation.Left,
				face.FaceLocation.Top,
				face.FaceLocation.Width,
				face.FaceLocation.Height,
				face.Gender.Gender,
				face.Gender.Score,
				face.Age.Max,
				face.Age.Min,
				face.Age.Score,
				face.Identity?.Name,
				face.Identity?.Score,
				face.Identity?.TypeHierarchy) + System.Environment.NewLine;
}

取得できる要素として、以下の情報が取得できます.

  • 認識した顔の座標情報
  • 性別
  • 年齢
  • 有名人の場合は名前およびタグ情報



実装結果

f:id:Santea:20170717221840p:plain:w450

私の写真で恐縮ですが、実装結果です.

私は25歳なので、少し若めの分析結果ですね.


github.com
ソースコードはこちらでご確認いただけます.



まとめ

今回は Watson Developer Cloud .NET Standard SDK の Visual Recognition の中の DetectFaces を使ってみました.

SDK が使えることで、余計な POCO を実装しなくてよかったり、HttpClient 回りを書かなくてよいので、スピーディーに実装できますね.

一つ気になったところとしては、async メソッドがあるとより使い方の幅が広がる気がしました.




以上です.