Android FCMを始めてみる② 個別のスマホにサーバーからメッセージ送信するサンプルコード

Android FCMを始めてみる① 一番簡単なNotification Composerでのメッセージ送信を実装してみる

上記の続きで、今度はみんなやりたい!単体のスマホに個別にメッセージを送る編です。サンプルコードも載せておきます。

とっても簡単ですが!落とし穴もいくつかあったので、書いておきます。

 

前回作ったAndroidアプリに、Tokenを取得するサービスを追加します。

MyFirebaseInstanceIDService.java

public class MyFirebaseInstanceIDService extends FirebaseInstanceIdService {


    @Override
    public void onTokenRefresh() {
        // Get updated InstanceID token.
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();
        Log.d("MyFirebaseInstanceIDS", "Refreshed token: " + refreshedToken);
    }
}

 

メッセージを受け取ったら、Logに表示するサービスも追加します。

MyFcmService.java

public class MyFcmService extends FirebaseMessagingService {
    private static final String TAG = "MyFcmService";

    /**
     * Called when message is received.
     *
     * @param remoteMessage Object representing the message received from Firebase Cloud Messaging.
     */
    // [START receive_message]
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {

        Log.d(TAG, "From: " + remoteMessage.getFrom());

        // Check if message contains a data payload.
        if (remoteMessage.getData().size() > 0) {
            Log.d(TAG, "Message data payload: " + remoteMessage.getData());

            if (/* Check if data needs to be processed by long running job */ true) {
                // For long-running tasks (10 seconds or more) use Firebase Job Dispatcher.
                //scheduleJob();
            } else {
                // Handle message within 10 seconds
                handleNow();

            }

        }

        // Check if message contains a notification payload.
        if (remoteMessage.getNotification() != null) {
            Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());

        }
    }


    /**
     * Handle time allotted to BroadcastReceivers.
     */
    private void handleNow() {
        Log.d(TAG, "Short lived task is done.");
    }


}

この2つのサービスを、忘れないように、AndroidManifest.xmlに記載します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="smart.location.admin.fcmdemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".MyFcmService">
            <intent-filter>
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
        <service android:name=".MyFirebaseInstanceIDService">
            <intent-filter>
                <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
            </intent-filter>
        </service>

    </application>

</manifest>

これでアプリはOKです。
MyFirebaseInstanceIDService 内の onTokenRefresh() で、FCM用のトークンが取得できるのですが、罠1がこれです。

「うーん。全然、onTokenRefresh() 呼ばれないな~(´・ω・)」

と思っていたら、これはアプリのインストール時にしか走らないらしい!!

面倒ですが、一度アプリをアンインストールしてから、インストールして、onTokenRefresh() が呼ばれるのを確認しましょう。

 

えー、無事、トークンがLogCatで確認できたら、それはどっかにメモっておきます。

 

次はサーバーサイドを実装します。

これも、とっても簡単!!⊂(^-^)⊃

私はPHPで実装します。

function sendFCM($mess,$id) {
$url = 'https://fcm.googleapis.com/fcm/send';
$fields = array (
'to' => $id,
'notification' => array (
"body" => $mess,
"title" => "Title",
"icon" => "myicon"
)
);
$fields = json_encode ( $fields );
$headers = array (
'Authorization: key=' . "AAAAJI69Yqs:APA91bFA***",
'Content-Type: application/json'
);
$ch = curl_init ();
curl_setopt ( $ch, CURLOPT_URL, $url );
curl_setopt ( $ch, CURLOPT_POST, true );
curl_setopt ( $ch, CURLOPT_HTTPHEADER, $headers );
curl_setopt ( $ch, CURLOPT_RETURNTRANSFER, true );
curl_setopt ( $ch, CURLOPT_POSTFIELDS, $fields );

$result = curl_exec ( $ch );

var_dump($result);

if ($result === FALSE) {
die('Oops! FCM Send Error: ' . curl_error($ch));
}

curl_close ( $ch );
}

//2個目の変数は、さきほどAndroidアプリで取得したトークンです。
sendFCM("こんにちは!世界さん","f2aEszM_9qE:APA91bHNy5XZfyVoVOV**********");

落とし穴その2 !!!

‘Authorization: key=’ . “AAAAJI69Yqs:APA91bFA***”,

のところには、APIキーを入れますが、Firebaseのコントロールパネルにある、「設定」→「全般」で表示される、「ウェブ API キー」ではありません!!!

「設定」→「クラウド メッセージング」で表示されている、「サーバーキー 」が、ここに入るべきAPIキーです。

APIキーが正しくないと、401 unauthorized error が帰ってきます。(>_<)

 

落とし穴その3。

ま、これは落とし穴というほどでもないですが、SSL証明書があるサーバーじゃないと

SSL certificate problem: unable to get local issuer certificate

ってエラーになって送信できません。

気軽にローカルとかから試せないのは残念ですね。

 

さてさて、上記の落とし穴を潜り抜けて、先程のfcm_test.phpを実行すると、アプリにメッセージが届きます!

これ、このコードだと、アプリが前面にある場合は、LogCatに出力されます。

バックグラウンド時は、ノーティフィケーションに表示されます。

 

 

Android FCMを始めてみる① 一番簡単なNotification Composerでのメッセージ送信を実装してみる

さてさて、急きょ、AndroidにFCMを実装しています。

FCMとはFirebase Cloud Messagingの略です。Googleが最近力を入れている、Firebaseの製品群の一つで、手っ取り早く言うと、アプリに送信するPush通知ですね。

今までは、GCM(Google Cloud Messagingの略)というものを使っていましたが、ターゲットAPIが27以降に変更してから、Android8以降の機種でGCMを受信すると、

java.lang.RuntimeException: Unable to start receiver com.google.android.gcm.GCMBroadcastReceiver:
 java.lang.IllegalStateException: Not allowed to start service Intent
 { act=com.google.android.c2dm.intent.RECEIVE flg=0x1000010 pkg=hogehoge.com cmp=hogehoge.com/.GCMIntentService (has extras) }: app is in background uid

というエラーで、アプリを立ち上げていない時、再起動後などにGCMがクラッシュするようになってしまったのです。

回避する方法がどうもなさそうで、FCM実装したほうが早いかという結論になりました。どのみち、GCMは2019年4月に廃止が決まっているのです…。(>_<)

 

というわけで、今回はそのFCMの一番最初の実装方法を書いておきます。Firebaseのコントロールパネルから、Androidアプリで簡単なメッセージを受信する、というところがゴールです。

結構簡単ですが、Googleさんのドキュメントにあるやり方ではわかりにくいところもあったので、その点を補足しながらやります。

①Firebaseのアカウントを作っておきます。

②メッセージを受信するAndroidアプリを作っておきます。ここでは、FCMdemoという名前のアプリです。

③Android StudioのTooles→Firebase→Cloud Messaging のSet up Firebase Cloud Messagingをクリックします。

もうここまで来たら、できたも同然!!!

①と②を、指示通りにクリックします。流れでできると思います。

そうすると、gradleのファイルの変更とか、google-services.jsonのダウンロードとかの面倒な作業を、このAndroid StudioのFirebase Assistantがやってくれます!!なんて便利なんだ!!!.゚ヽ(*´∀`)ノ゚

アプリは、次のように実装します。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="smart.location.admin.fcmdemo">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

MainActivity

package smart.location.admin.fcmdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


    }

}

 

build.gradle(Module: app)

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.2'
        

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
        classpath 'com.google.gms:google-services:3.1.1'
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

build.gradle(Project: FCMdemo)

 

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "smart.location.admin.fcmdemo"
        minSdkVersion 14
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    implementation 'com.google.firebase:firebase-messaging:11.8.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}

apply plugin: 'com.google.gms.google-services'

とりあえずのゴールとしては、Firebaseのコントロールパネルからメッセージを送信するというところだったので、Googleのチュートリアルにあるような、トークンを取得してどうこう…という手続きは不要です!

アプリをインストールしたら、早速メッセージを送ってみましょう。

①Firebaseにログインします。

②FCMdemo(アプリの名前)をクリック→Cloud Messagingをクリック

③新規メッセージを送信、で次のような画面になりますので、メッセージを入力します。赤丸で囲った、アプリ名を指定しないと送信できませんので、(わかりにくかった)ご注意を。

アプリをバックグラウンドにしておき、送信します。

すると、Notificationを受信できますね!

すばらしい!.゚ヽ(*´∀`)ノ゚

スマホ単体の個体識別トークンがなくても、アプリをインストールしているユーザーにメッセージが送れるというすばらしさ!!

 

もちろん、普通は個スマホへのメッセージングに使うでしょうから、これだけではダメだと思いますが、FCMのなんたるかがつかめると思います。⊂(^-^)⊃

TensorBoard found more than one metagraph

Tensorflow で色々と試行錯誤しています。

以前、下記の記事でTensorBoardという、TensorFlowについている、計測ツールについて紹介しました。

WindowsでTensorBoardを使う

で、そのTensorBoardで下記のエラーが出ちゃいました。

tensor board found more than one metagraph

うーん。ググっても、TensorFlowの1.4では治っているよ~ という下記の記事などや、結構複雑なStack Overflowさんの記事などが出てきてしまいます(;´・ω・)。

https://github.com/GoogleCloudPlatform/cloudml-samples/issues/73

 

はたと思いついたのが、TensorBoardのためのログって、下記のようなファイルで出力されるじゃないですか。

events.out.tfevents.153294*****.3ec855******

それで、TensofBoardを動作させるときは、

tensorboard --logdir=C\hogehoge

みたいにディレクトリを指定しますよね。

C\hogehoge 内にある、TensofBoardのログファイルで、同じタイムスタンプのものが複数あったので、一つを残して消してみたら、普通にTensofBoardさんが動作しました!!

タイムスタンプでログファイル見てるのね。

タイムスタンプが同じものがあると、タイトルのエラーが出るようです。

私の場合は、別の機械学習用のサーバーでAIのコード動作させてて、TensofBoardのログだけ、自分のローカルPCで見ようと思って、ダウンロードしたので、同じタイムスタンプのログがいっぱいあった、ということです。

 

TensorBoard+Keras グラフの名前を変えてみる

昨日の

WindowsでTensorBoardを使う

の続きです。

TensorBoardにグラフ機能というのがあります。

上のメニューで、GRAPHSというのをクリックします。

わけのわからない図が出てきます。

 

図のところだけ拡大すると、こんな風。

TensorBoardの公式サイトに、チュートリアルの動画がありました。なかなかよい動画です。

余談ですが、いつも開発者の動画とかあると、見ちゃいますね。

結局、とあるプログラムがあったとして、その開発者以上にそのプログラムのことをわかっている人間はいないと思います。

 

で、この動画の中にもとりあえず、名前をつけてみることがよい!というお話がありましたので、名前をつけてみます。

名前をつけるやり方です。(非常に単純な話で申し訳ありませんが…。)

前述の記事内のコードに、名前をつける部分を足しただけです。

import keras
import tensorflow as tf

from keras.datasets import imdb
from keras.preprocessing import sequence

max_features = 2000  # number of words to consider as features
max_len = 500  # cut texts after this number of words (among top max_features most common words)

print('Loading data...')
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
print(len(x_train), 'train sequences')
print(len(x_test), 'test sequences')

print('Pad sequences (samples x time)')
x_train = sequence.pad_sequences(x_train, maxlen=max_len)
x_test = sequence.pad_sequences(x_test, maxlen=max_len)
print('x_train shape:', x_train.shape)
print('x_test shape:', x_test.shape)

from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop

model = Sequential()
model.add(layers.Embedding(max_features,
                            128,
                           input_length=max_len
                           ))
model.add(layers.Conv1D(32, 7, activation='relu', name="first_layer"))
model.add(layers.MaxPooling1D(5))

with tf.name_scope("second_layer"):
    model.add(layers.Conv1D(32, 7, activation='relu',name="hogehoge"))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))

model.summary()

model.compile(optimizer=RMSprop(lr=1e-4),
              loss='binary_crossentropy',
              metrics=['acc'])


callbacks = [
    keras.callbacks.TensorBoard(
        log_dir = 'my_log_dir',
        histogram_freq = 1

    )
]
history = model.fit(x_train, y_train,
                    epochs=5,
                    batch_size=128,
                    validation_split=0.2,
                    callbacks=callbacks)

最初の畳み込みレイヤー・Conv1Dに”first_layer”という名前をつけたのと、次の畳み込みレイヤー・Conv1Dにグルーピングの名称”second_layer”という名前をつけ、レイヤー自体には”hogehoge”という名前をつけています。

ただ単に、私がグルーピングを試してみたかっただけです。

赤丸のように、名前が変わりましたね!

マウスを当てて、右上にプラスが出てくる部分は、ダブルクリックで開けます。

より、詳しい内容がわかります。

前のレイヤーからのデータが、hogehogeレイヤーの中で、畳み込みネットワークに入り、カーネル(畳み込みレイヤーのフィルターのことです)とか、Biasが作用している感じがわかると思います。

 

WindowsでTensorBoardを使う

さてさて、私は今のところ、ディープラーニングのコードを

Keras+TensorFlow

で作っています。

TensorFlowには、TensorBoardという便利ツールがあると聞き、使ってみます。

勉強している元ネタの本はこちら。

PythonとKerasによるディープラーニング

無駄にややこしい部分があるので、オススメ本ではありませんw

とりあえずの~ TensorBoardを使ってみたいだけのテストコードを書きます。

import keras

from keras.datasets import imdb
from keras.preprocessing import sequence

max_features = 2000  # number of words to consider as features
max_len = 500  # cut texts after this number of words (among top max_features most common words)

print('Loading data...')
(x_train, y_train), (x_test, y_test) = imdb.load_data(num_words=max_features)
print(len(x_train), 'train sequences')
print(len(x_test), 'test sequences')

print('Pad sequences (samples x time)')
x_train = sequence.pad_sequences(x_train, maxlen=max_len)
x_test = sequence.pad_sequences(x_test, maxlen=max_len)
print('x_train shape:', x_train.shape)
print('x_test shape:', x_test.shape)

from keras.models import Sequential
from keras import layers
from keras.optimizers import RMSprop

model = Sequential()
model.add(layers.Embedding(max_features,
                            128,
                           input_length=max_len,
                           name='embed'))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.MaxPooling1D(5))
model.add(layers.Conv1D(32, 7, activation='relu'))
model.add(layers.GlobalMaxPooling1D())
model.add(layers.Dense(1))

model.summary()

model.compile(optimizer=RMSprop(lr=1e-4),
              loss='binary_crossentropy',
              metrics=['acc'])

#下記から下がTensorBoardで記録するための部分
callbacks = [
    keras.callbacks.TensorBoard(
        log_dir = 'my_log_dir',
        histogram_freq = 1

    )
]
history = model.fit(x_train, y_train,
                    epochs=10,
                    batch_size=128,
                    validation_split=0.2,
                    callbacks=callbacks)

映画のレビューデータである、IMDBの分析ですね。

これを実行しますと、実行したプログラムと同じディレクトリ(ここではC:\kerasStudy\my_log_dir)に

events.out.tfevents.1532589203.HOGEHOGEPC

というファイルなどができたりします。

私は、Anacondaを使っているので、Anaconda Promptを立ち上げます。

んで、

C:\Users\Hogehoge>tensorboard --logdir=C:\kerasStudy\my_log_dir

と打ち込みますと、

最後に

TensorBoard 1.8.0 at http://hogehogePC:6060

と出てきます。

後は、ブラウザから、

http://hogehogePC:6060

にアクセスすると、TensorBoardが開けます!!

感動ひとしお~

TensorBoardの使い方に関しては、改めて書きたいと思いますが、

下記の赤丸の部分にチェックが入っていないと、グラフが表示されません。