Python リスト内包表記2 リストの中に複雑な関数を書く

以前、

Python リストの中に関数を書く

って投稿を書きましたが、リスト内包表記の例文として出している

counts = [self.Nsa[(s, a)] if (s, a) in self.Nsa else 0 for a in range(self.game.getActionSize())]

について、掘り下げていなかったので、掘り下げますね!

分解すると、リスト内包表記は

[リストの中身になるもの for i in リストの中の要素の数]

です。

なので、一見複雑なこの式は

self.Nsa[(s, a)] if (s, a) in self.Nsa else 0

の三項演算子でのif文判定と

for a in range(self.game.getActionSize())

の要素の数の部分に分けられます。

なんというか、三項演算子の存在を忘れていたので、前半の書き方について、しばらくわかりませんでした(;^ω^)

この例題だとこの例題で完結しないので、簡単な例です。

s = 1
list = [1,2,3,4]

list2 = list if s in list else 0
print(list2) #[1, 2, 3, 4]が出力される

counts = [ list if s in list else 0 for a in range(10)]
print(counts) #[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], ...] [1,2,3,4]が10個あるリストが出力される

Python 可変性オブジェクトの引数は、関数に渡すと呼び出し側も変わってしまう

私は一番慣れている言語がJava, PHPなので、Pythonのこの流儀になれずによく間違ってしまうので、書いておきます。

def changer(x,y):
    x = 2
    y[0] = ["hogehoge"]

x = 0
L = ["hage", "moke"]

changer(x , L)

print(x) # 0 が出力されます
print(L) # [['hogehoge'], 'moke'] が出力されます

 

xは整数で不変性オブジェクトなので上書きされません。Lはリストで可変性オブジェクトなので変化があります。

ちなみに、下記のように丸ッとリストを変更してしまうと、呼び出し側が変更されませんので、気を付けましょう。

def changer(x,y):
    x = 2
    y = ["hogehoge", "ukiki"]

x = 0
L = ["hage", "moke"]

changer(x , L)
print(x) # 0 が出力されます
print(L) #['hage', 'moke']が出力されます

Android Buildできない Caused by: java.lang.RuntimeException: Job failed, see logs for details at com.android.build.gradle.internal.transforms.ProGuardTransform.transform

表題のエラーでビルドできずに四苦八苦 (>_<)

下記のStack Overflowさんに

https://stackoverflow.com/questions/51535419/taskexecutionexception-while-trying-to-generate-signed-apk

Proguardが

minifyEnabled true

だとむちゃくちゃする

と書いてあったので、

minifyEnabled trueをコメントアウトしたら、Buildできるようになりました…。

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のなんたるかがつかめると思います。⊂(^-^)⊃