20190816のJavaに関する記事は15件です。

javaを使って二人でゲーム開発

今回行うこと

コードを書く時の書き方や名前を統一する

名前

クラス名およびファイル名

  • パスカルケース

メソッド名

  • キャメルケース

変数名

  • スネークケース
  • メンバ変数は末尾にアンダースコアを付ける

定数名

  • すべて大文字でスネークケース

書き方

インデント

  • 空白文字2文字

括弧の位置

  • 括弧同士が縦に並ぶように
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

javaを使って二人でゲーム開発2

今回行うこと

コードを書く時の書き方や名前を統一する

名前

クラス名およびファイル名

  • パスカルケース

メソッド名

  • キャメルケース

変数名

  • スネークケース
  • メンバ変数は末尾にアンダースコアを付ける

定数名

  • すべて大文字でスネークケース

書き方

インデント

  • 空白文字2文字

括弧の位置

  • 括弧同士が縦に並ぶように
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

猿でもわかるAndroidの環境構築[ReactNative]

ReactNativeの環境構築について

超初心者でもわかる、Androidの環境構築がなかったので、記事にして残しておきます~~~!
(2019年8月更新)

開発環境

MacOS : Mojave/10.14.6
node : v10.15.3
react-native-cli : 2.0.1
react-native : 0.60.4
npm : 6.4.1

対象者

react-nativeを使用して、iOSを立ち上げることは成功したけど、Androidの立ち上げがうまくいかない人。

それでは、Start!!!!!

①JDK 8をインストール

まずは、Androidを動かすために必要なJavaを入れます。
※注意:Javaのバージョンは最新のものではなく、8を使用します!
(うp主が、他のバージョンだとうまく動かせないからです。)

  • Oracleからダウンロードしたいので、アカウントを作成する必要があります。

スクリーンショット 2019-08-16 16.59.20.png

  • アカウントが完成したならば、以下の記事を参考にして、Java8をインストールしていきます。

macOS に Oracle Java 8 (JDK) をインストールする手順

スクリーンショット 2019-08-16 17.12.23.png

  • インストールが完了したら、ターミナルやiTermsでJavaが8入っているか確認しましょう!
java -version
//下記が確認できればOK!
java version "1.8.0_221"

②パスを通す!

初心者だと、「パスってなんやねん」、「それしなくてもいけそう〜」みたいな感覚になりがちになるのですが、めっちゃ大事なので、絶対にして下さい!(今回はやり方の解説だけします)
※注意 : 今回は、bash(初心者の人は大抵これなので安心してこの注意文は無視して下さい)向けの人に解説します!
(zsh使ってる人は解説いらないでしょう)

  • ターミナル、iTermを開いて準備する
    スクリーンショット 2019-08-16 17.25.40.png

  • vimを使用して、ターミナル上で編集していきます。

//コピペする
vi ~/.bash_profile

そうすると以下の画面になると思います。

スクリーンショット 2019-08-16 17.36.43.png

  • キーボードの矢印を使用して、カーソルを移動することができるので、適当に空いてる行に移動し、編集していきます
//そのままでは編集できないので,「i」を押しましょう→編集可能になります。
//下のものをコピペしましょう。Javaだけでなく、後々使うAndroidSdkのパスも一緒に通しておきます。
export JAVA_HOME=`/usr/libexec/java_home -v 1.8`
export PATH=/usr/local:$PATH:$NODEBREW_HOME/bin:$ANDROID_HOME:$ANDROID_NDK_HOME:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools
export ANDROID_HOME=$HOME/Library/Android/sdk
export PATH=$PATH:$ANDROID_HOME/tools
export PATH=$PATH:$ANDROID_HOME/tools/bin
export PATH=$PATH:$ANDROID_HOME/platform-tools
  • コピペができたら、escボタンを押して、編集状態を解除し、編集した内容を保存していきます
//編集した内容を保存して、vimの編集を終了したいので以下のコマンドを今の画面のまま打ちます。
//注意:escボタンを押したあとに必ずやって下さい!
:wq
  • 上記ことを行うと元のターミナルの画面に戻ってきます。
  • 変更したないようを設定ファイルに反映したいので、ターミナルで以下のコマンドを実行します。
source ~/.bash_profile

→これにてパスの設定は完了!

③Android StudioのDownloadとinstall

スクリーンショット 2019-08-16 18.01.36.png

  • ダウンロードが完了したらインストールしていきます。
    インストールタイプを選択するように言われるので"Custom"を選択します。
    Android SDK
    Android SDK Platform
    Performance (Intel ® HAXM)
    Android Virtual Device
    この4つにチェックが入っていることを確認したら"next"をクリックしてインストールします。

  • インストールが完了すると以下の画面が現れます。(でてこなかったら、Android Studioを立ち上げましょう!)

スクリーンショット 2019-08-16 18.08.48.png

  • SDK等さらに必要なファイルをインストールしていきます。
  • 初期の画面の下の部分から、Configure -> SDK Manager を起動し、SDK Platforms → Android 6.0 (Marshmallow)をインストールします。以下の画面に沿って同じ箇所にチェックして下さい。

スクリーンショット 2019-08-16 18.13.11.png

スクリーンショット 2019-08-16 18.16.32.png

スクリーンショット 2019-08-16 18.20.00.png

  • 最後に、"Apply"をClickしてSDKやビルドツールをdownloadしてinstallします。

- 次にAVDの作成をしていきます!画像通りに進めて下さい。

スクリーンショット 2019-08-16 18.33.56.png
スクリーンショット 2019-08-16 18.36.06.png
スクリーンショット 2019-08-16 18.38.15.png
スクリーンショット 2019-08-16 18.38.51.png

スクリーンショット 2019-08-16 18.39.31.png
→AVD Nameの最後の2はいりません(うp主が既に、1つ作ってるので勝手に2と振られてしまっています)

⑤最後にエミュレーターを動かす

react-nativeでは、iOSはシュミレーター(エミュレーター)を立ち上げなくても、cliでアプリを起動することができるのですが、

ターミナル.
react-native run-ios

Androidは、先にエミュレーターを立ち上げておかないと、アプリを起動することができません!
ここポイント!!!!
なので、エミュレーターを立ち上げてから、アプリを起動していきたいと思います

  • エミュレーター起動 スクリーンショット 2019-08-16 18.33.56.png スクリーンショット 2019-08-16 18.50.25.png

スクリーンショット 2019-08-16 19.03.59.png

  • アプリを起動する
//サーバーを立ち上げる
npm start
//Androidでアプリを立ち上げる
react-native run-android

※注意 : サーバーを立ち上げるターミナルと、アプリを立ち上げるターミナルは分ける
[おすすめの方法]
ターミナル上でcmd+Dをすると分割することができるので、どちらでもプロジェクトのディレクトリに入って、1つはサーバーを立ち上げる、もう一つはアプリを立ち上げるにすることです。

参考記事

https://qiita.com/EBIHARA_kenji/items/2f6938c4fda7cecbeb19
https://qiita.com/takaishota/items/4db36a806a257582fa1f
https://qiita.com/tekoneko1997/items/ab1254e4472802514190#android-studio%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB
https://qiita.com/ponnjinnka/items/006b632a0b56369451b9

もし、わからないことがあれば、コメントで質問受け付けます〜〜〜

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android JavaでWAVデータをバイト配列で読み込む

この記事の内容

AndroidでWAVファイルの音を再生するだけであればMediaPlayerクラスなどが使えますが、
WAVデータを「事前に」読み込んでデータをいじりたい場合があります。

「事前に」というのは「音を再生する前に」という意味です。
というのもMediaPlayerクラスを使えばVisualizer.setDataCaptureListenerで生データとFFTを取得できますが、
これは再生中の音なのでリアルタイム処理になってしまいます。

外部リンク:VISUALIZERを使ってイコライザーや波形を表示する

そこで本記事ではWAVデータを事前にバイト配列で読み込んでヘッダやらに注意しながら扱う方法を紹介します。

※適当に調べてサクッと作ったものなので、もっと簡単な方法があれば教えてください。

WAVデータの読み込み

res/rawフォルダにWAVファイル(例:music.wav)が格納されていればInputStreamでバイト配列を取得できます。

        try {
            InputStream is = getResources().openRawResource(R.raw.music);
            wavData = new byte[is.available()];
            String readBytes = String.format(Locale.US, "read bytes = %d", is.read(wavData));
            Log.e(TAG, readBytes);
            is.close();
        } catch (Exception e){
            e.printStackTrace();
        }

参考外部リンク: WAV音声ファイルをAudioTrackで再生する

ヘッダの分析

WAVファイルはヘッダが44バイトほどあり、そのあとにデータが続くという形式になっています。
ただ、この44バイトというのが色々追加されたりすることがあるようなので、きちんとヘッダの中身を確認しながらデータを取得したほうがよさそうです。

参考外部リンク:音ファイル(拡張子:WAVファイル)のデータ構造について

ヘッダの種類はchunkと呼ばれており、'fmt'と'data'がわかればよさそうです。(詳細は上の外部リンクを見てもらえればと思います)
そこでこの'fmt ' == 0x6d7420と'data'==0x64617461を確認してそれぞれのインデックスを保存しておきます。

        int fmtIdx = 0;
        for(int i = 0; i < wavData.length - 4; i ++){
            if(wavData[i] == 0x66 && wavData[i + 1] == 0x6d
                    && wavData[i + 2] == 0x74 && wavData[i + 3] == 0x20){ // 'fmt ' chunk
                fmtIdx = i;
                Log.i("Test", "fmtIdx:" + fmtIdx);
                break;
            }
        }
        if(fmtIdx == 0){
            Log.e(TAG, "No fmt chunk");
        }

        int dataIdx = 0;
        for(int i = 0; i < wavData.length - 4; i ++){
            if(wavData[i] == 0x64 && wavData[i + 1] == 0x61
                    && wavData[i + 2] == 0x74 && wavData[i + 3] == 0x61){ // 'data' chunk
                dataIdx = i;
                Log.i("Test", "dataIdx:" + dataIdx);
                break;
            }
        }
        if(dataIdx == 0){
            Log.e(TAG, "No data chunk");
        }

次にチャネル数、サンプリングレート、ビット数(ここではバイト数)、データサイズを取得しましょう。
データサイズは基本的にはヘッダを除いた部分ということですが、WAVフォーマットはフッタも自由につけてよいということらしいので取得しておきます。
実際に私が試した音源ではフッタが結構な量で含まれていました。

        int wavChannel = (int)(wavData[fmtIdx + 10]);
        Log.i("Test", "wavChannel:" + wavChannel);

        int wavSamplingRate = ((int)(wavData[fmtIdx + 15]) << 24) + ((int)(wavData[fmtIdx + 14]) << 16)
                + ((int)(wavData[fmtIdx + 13]) << 8) + (int)(wavData[fmtIdx + 12]);
        Log.i("Test", "wavSamplingRate:" + wavSamplingRate);

        int wavByte = (int)(wavData[fmtIdx + 22]) / 8;
        Log.i("Test", "wavByte:" + wavByte);

        int wavDataSize = ((int)(wavData[dataIdx + 7]) << 24) + ((int)(wavData[dataIdx + 6]) << 16)
                + ((int)(wavData[dataIdx + 5]) << 8) + (int)(wavData[dataIdx + 4]);
        Log.i("Test", "wavDataSize:" + wavDataSize);

        int wavHeaderSize = dataIdx + 8;

        int[] musicDataRight = new int[wavDataSize / wavByte / wavChannel];
        int[] musicDataLeft = new int[wavDataSize / wavByte / wavChannel];

4バイトで格納されているところはシフト演算でintにしています。
また、あとで使うのでヘッダサイズを計算しています。
チャネルが2のときは左右の音源が取得できますので、これをmusicDataRight、musicDataLeftに入れていきます。

        if(wavByte == 1 && wavChannel == 1){
            for(int i = 0, j = wavHeaderSize; i < musicDataRight.length; i ++, j ++){
                musicDataRight[i] = (int)wavData[j];
                musicDataLeft[i]  = (int)wavData[j];
            }
        } else if(wavByte == 1 && wavChannel == 2){
            for(int i = 0, j = wavHeaderSize; i < musicDataRight.length; i ++, j += 2){
                musicDataRight[i] = (int)wavData[j];
                musicDataLeft[i]  = (int)wavData[j + 1];
            }
        } else if(wavByte == 2 && wavChannel == 1){
            for(int i = 0, j = wavHeaderSize; i < musicDataRight.length; i ++, j += 2){
                musicDataRight[i] = ((int)wavData[j + 1] << 8) + (int)wavData[j];
                musicDataLeft[i]  = ((int)wavData[j + 1] << 8) + (int)wavData[j];
            }
        } else if(wavByte == 2 && wavChannel == 2){
            for(int i = 0, j = wavHeaderSize; i < musicDataRight.length; i ++, j += 4){
                musicDataRight[i] = ((int)wavData[j + 1] << 8) + (int)wavData[j];
                musicDataLeft[i]  = ((int)wavData[j + 3] << 8) + (int)wavData[j + 2];
            }
        }

チャネル数とバイト数別にデータを格納しています。
やろうと思えば式をまとめることもできる(実際やってみた)のですが、可読性が落ちてしまうので分けて書いています。
チャネル数が1のときは左右に同じ音を入れています。
また、チャネル数やバイト数が3以上の場合は別途追加する必要があります。

以上でWAVデータをバイト配列で読み込む手順の説明を終わります。

おわりに

読み込んだ波形をグラフで表示したり、データを加工したりといったことがこれで可能になります。
ただ、冒頭にも述べましたがもっと簡単な方法があれば教えてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

51歳からのプログラミング 備忘 Service 編集中

https://developer.android.com/guide/components/services

Serviceとは

Service:ユーザーインターフェイスを使わず、バックグラウンドで長時間処理を実行できるアプリケーションコンポーネントです。

他のアプリケーションコンポーネントを開始することもできるし、ユーザーが他のアプリケーションに変更しても、バックグラウンドで処理し続けます。

さらに、コンポーネントとサービスをバインドして交信し、プロセス間通信もできます。例えば、ネットワーク処理、音楽再生、ファイルI/O、コンテンツプロバイダー通信など、すべてバックグラウンドで処理できます。

3種類のService

Foreground

ForegroundServiceは、ユーザーに通知されている処理を実行します。例えば、オーディオアプリはForegroundServiceを使って、オーディオトラックで再生します。ForegroundServiceでは通知を表示させます。ユーザーがアプリを操作してない時は、ForegroundServiceで処理し続けます。

Background

BackgroundServiceはユーザーによって直接気が付かない処理を実行します。例えば、アプリのストレージを圧縮するサービスの場合、それはバックグラウンド処理となります。

Note:
アプリがAPI26以降の時、アプリがforegroundになければ、システムはバックグラウンドサービスの実行に制約を課します。このようなケースでは、代わりにスケジュールされた作業を行います。
Bound

bindService()でサービスをアプリケーションコンポーネントにバインドします。バインドされたサービスは、コンポーネントと通信するため、クライアントサーバーインターフェイスを提供します。例えば、リクエスト送信、結果受信、プロセス通信など。

バインドされたサービスは、他のアプリケーションコンポーネントがサービスにバインドされている限り、終了することはありません。

サービスは、明示的な開始、バインド、どちらも使えますが、ドキュメントでは開始とバインドを分けて説明します。

実装は、onStartCommand()でサービス開始を要求するか、onBind()でバインドを許可するか、のどちらかで行います。

どのアプリケーションコンポーネントも、任意のアクティビティを使うのと同じ方法で、Intentを使って同じサービスを開始できます。マニュフェストに他のアプリからの利用禁止を定義することも可能です。詳しくは>https://developer.android.com/guide/components/services#Declaring

COUTION:
 特に方法を指定しない限り、Serviceが自身のスレッドを作成することなく、別のスレッドで実行されることもありません。
 なので、MP3再生やネットワーク処理など負担のかかる処理では、フリーズエラーを予防するために、サービス内に新しいスレッドを作成する必要があります。

ServiceとThreadどっちを使う?

・ Serviceはバックグラウンド処理する場合
・ Threadはメインスレッド以外で処理する必要がある場合

例えば、アクティビティを実行してる間だけ音楽再生する場合は、onCreate()にスレッドを作成し、onStart()で実行開始、onStop()で停止。 Threadの代わりに、AsyncTascやHandlerThreadも使えます。

Service作成にあたって重要なOverrideメソッド

onStartCommand() / onBind() / onCreate() / onDestroy()

onStartCommand()

アクティビティなどの他のコンポーネントが、startService()を呼び出して、Serviceの開始を要求したときに、システムがonStartCommand()を呼び出します。onStartCommand()が実行されるとServiceが開始されます。

onStartCommand()を実装する時は、Service完了時にstopSelf()かstopServece()で、自身でサービスを停止させます(バインドのみを提供する場合には、onStartCommand()は実装しなくてよい)。

onBind()

bindService()を呼び出して、他のコンポーネントをServiceにバインドさせるときに、システムがonBind()を呼び出します。

onBind()実装時にはIBinderを返して、クラインとがServiceとの通信に使うインターフェースを提供する必要があります。

onBind()の実装は常に必要だけれど、バインドを許可しない場合はnullを返す必要があります。

onCreate()

onStartCommand()かonBind()を呼び出す前で、サービスが始めて作成された時に、1度限りのセットアップ処理をするために呼び出されるメソッド。

onDestroy()

Serviceが長時間使われなかったり、破棄されたときに呼ばれるメソッド。スレッドや登録済みリスナー、レシーバーのリソースをクリーンアップするのに、Serviceはこのメソッドを実装します。このメソッドは、Serviceが受け取る最後の呼び出しです。

Serviceの終了

stopSelf() stopService()

onStartCommand()のstartService()によってコンポーネントがServiceを開始したら、サービスがstopSelf()で自身を終了するか、他のコンポーネントがstopService()を実行するまで、サービスは稼働し続けます。

unbind

コンポーネントが、onStartCommand()を呼ばずに、bindService()によってServiceを生成したら、コンポーネントがServiceにバインドされてる限り、Serviceは稼働し続けます。Serviceがクライアントから全てアンバインドされたら、システムはServiceを破棄します。

Android System によるServiceの破棄

メモリーが下がり、ユーザーが操作してるアクティビティのシステムリソースを回復させなければならない場合にのみ、アンドロイドシステムはServiceを停止します。ユーザーが使っているアクティビティにバインドされたServiceは、破棄される可能性は低くなります。Foregroundで実行するよう定義したServiceは滅多に破棄されません。

システムは、時間の経過とともに、Serviceのバックグラウンド・タスク・リストでの位置付けを下げます。開発者はシステムによるServiceの復帰処理をデザインしてください。onStartCommand()の戻り値にもよりますが、もしシステムがServiceを破棄したら、システムはリソースが回復するなり、Serviceを復帰させます。詳しくは>https://developer.android.com/guide/components/processes-and-threads.html

Service作成

マニフェスト宣言

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

~で、ServiceやServiceを実行するプロセスを開始するために必要なプロパティを定義します。

android:name

Serviceのクラス名を指定する。唯一の必須属性、変更不可
アプリの安全性を保つため、Serviceを開始したりバインドするときは、常に明示的Intentを使います。

COUTION:
アプリの安全性を確保するため、Serviceを開始するときには常に明示的Intentを使い、ServiceのIntentフィルタは宣言しません。暗黙的なインテントは危険です。開発者がインテントから返されるServiceを確認できなかったり、ユーザーがServiceの開始を認識できなかったりします。Android5.0(API21)以降では、bindService()を呼び出すと、システムは例外を投げます。

どのServiceを開始するかについて、あいまい性を残す場合には、ServiceにIntentフィルタを定義して、Intentからコンポーネント名を除外して、その後にターゲットのサービスのあいまい性を解消するsetPackage()でIntentのパッケージを設定する必要があります。

android:exported

android:exported属性を含めて"false"に設定すると、Serviceを自身のアプリでしか利用できないようにできます。たとえ明示的Intentを使っても、Serviceを開始できなくなります。

要求されたServiceを生成する

要求されたServiceとは:
他のコンポーネントがstartService()を呼び出して、onStartCommand()メソッドを使って、生成を要求されたService。

Serviceの終了
onStartCommand()で開始したServiceは、
 自身で終了する場合には、stopSelf()
 他のコンポーネントで終了する場合には、stopService()

Serviceの開始とIntent
アクティビティなどのアプリケーションコンポーネントは、startService()でServiceを指定しIntentでServiceが使うデータを渡してServiceを開始し、Serviceは、onStartCommand()を使ってIntentを受け取って処理します。

要求されたサービスを生成するための拡張可能なクラス

Service
全てのServiceの基本クラス
Serviceはデフォルトでアプリのメインスレッドを使用するため、Serviceの処理によって、アクティビティのパフォーマンスを低下させることがあります。

そのため、このクラスを拡張するときは、Service全ての作業のための、新しいスレッドを作成することが重要です。

IntentService
全てのServiceの生成要求を、一件ずつ処理するワーカースレッドを使った、Serviceのサブクラス。Serviceで同時に複数の生成要求を処理する必要がない場合に最適。

onHnadleIntent()を実装するだけで、それぞれの生成要求のIntentを受け取り、バックグラウンド処理ができるようになります。

IntentServiceクラスの拡張 省略
Serviceクラスの拡張 省略

Serviceの開始

アクティビティや他のアプリケーションコンポーネントからIntent(開始Serviceを指定)を、startService()に渡して、Serviceを開始。

Androidシステムが、ServiceのonStartCommand()メソッドを使ってIntentを渡します(絶対に、onStartCommand()を直接呼び出してはいけません)

<参考>

Intent intent = new Intent(this,HelloService.class);
startService(intent);

AndroidシステムがServiceのonStartCommand()を呼び出しています。
Serviceが実行されてないときは、システムはonCreate()を呼び出してから、onStartCommand()を呼び出します。

Serviceでバインドが提供されてないときは、startService()で配信されたIntentがアプリケーションコンポーネントとServiceの唯一の通信手段となります。

ただし、Serviceから結果を返す場合、Serviceを要求するクライアントが、ブロードキャスト用にPendingIntentを作成でき(getBroadcast()を使う)、要求されたServiceのIntentに配信できます。その後、Serviceはブロードキャストを使って結果を配信できます。

Service要請が複数ある場合、それに対応するために、onStartCommand()に複数の呼び出しがはっせいしますが、停止するためには、stopSelf()やstopServiceは1つのみでOK。

Serviceの停止

stopSelf()やstopServiceで停止要求すると、androidシステムはServiceを破棄します。

ServiceがonStartCommand()への複数要求を同時処理している場合は、新しい生成要求がある可能性があるので、複数要求の同時処理後もServiceは停止させないようにします。1つの停止で全て停止します。

複数要求の停止は、stopSelf(int)を使って、最新の要求に基づいてServiceの停止要求を行います。具体的には、停止したい要求にID(onStartCommand()に配信したstartId)を渡します。

COUTION:
Serviceのバインドが有効な場合でも、onStartCommand()への呼び出しを受け取ったときは、常に自身でサービスを停止する必要があります。stopSelf()を使ってね。

バインドされたServiceを作成

bindService()でServiceにバインドすると、アプリケーションコンポーネントが長時間接続できます(通常では、コンポーネントがstarService()を呼び出すことではServiceは開始できない)。

バインドされたServiceを作成

・ onBind()を実装
  Serviceとの通信用のインターフェイスを定義するIBinderを返す
・ アプリケーションコンポーネントがbindService()を呼び出
  インターフェイスIBinderを取得しServiceのメソッドの呼び出しを開始

バインドされたコンポーネントがなくなると、システムによってServiceが破棄されます(バインドされたサービスは、onStartCommand()でサービスが開始された時と同じ方法で停止する必要はありません)。

アンバインド

unbindService()
Serviceにバインドされているクライアントがなくなったら、システムがサービスを破棄。

Foreground Sercice

ForegroundServiceでは通知を表示する必要があり、Service停止か、ForegroundからServiceを除去しない限り、通知を消すことはできません。

Create Foreground Service

startForeground(Id,notification)を実行
・ Notification
・ Intent
・ PendingIntent
・ notification.setLatestEventInfo(this,title,text,PendingIntent)
・ startForeground(Id,notification)

<参考>

Notification notification = new Notification(
                                R.drawable.icon,
                                getText(R.string.ticker_text),
                                System.currentTimeMillis()
                                );
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(
                              this, 
                              getText(R.string.notification_title),
                              getText(R.string.notification_message), 
                              pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

startForeground()に渡すIDには、0はNG

Foregourd Serviceの除去

stopForeground()
・ ServiceをForegroundから除去
・ 通知を除去するかを示すbool値も受け付けます
・ Serviceは停止しない
  ただし、ServiceがまだForegroundで実行中に停止した場合は、通知は削除される

Serviceのライフサイクル管理

省略

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

51歳からのプログラミング 備忘 Service

https://developer.android.com/guide/components/services

Serviceとは

Service:ユーザーインターフェイスを使わず、バックグラウンドで長時間処理を実行できるアプリケーションコンポーネントです。

他のアプリケーションコンポーネントを開始することもできるし、ユーザーが他のアプリケーションに変更しても、バックグラウンドで処理し続けます。

さらに、コンポーネントとサービスをバインドして交信し、プロセス間通信もできます。例えば、ネットワーク処理、音楽再生、ファイルI/O、コンテンツプロバイダー通信など、すべてバックグラウンドで処理できます。

3種類のService

Foreground

ForegroundServiceは、ユーザーに通知されている処理を実行します。例えば、オーディオアプリはForegroundServiceを使って、オーディオトラックで再生します。ForegroundServiceでは通知を表示させます。ユーザーがアプリを操作してない時は、ForegroundServiceで処理し続けます。

Background

BackgroundServiceはユーザーによって直接気が付かない処理を実行します。例えば、アプリのストレージを圧縮するサービスの場合、それはバックグラウンド処理となります。

Note:
アプリがAPI26以降の時、アプリがforegroundになければ、システムはバックグラウンドサービスの実行に制約を課します。このようなケースでは、代わりにスケジュールされた作業を行います。
Bound

bindService()でサービスをアプリケーションコンポーネントにバインドします。バインドされたサービスは、コンポーネントと通信するため、クライアントサーバーインターフェイスを提供します。例えば、リクエスト送信、結果受信、プロセス通信など。

バインドされたサービスは、他のアプリケーションコンポーネントがサービスにバインドされている限り、終了することはありません。

サービスは、明示的な開始、バインド、どちらも使えますが、ドキュメントでは開始とバインドを分けて説明します。

実装は、onStartCommand()でサービス開始を要求するか、onBind()でバインドを許可するか、のどちらかで行います。

どのアプリケーションコンポーネントも、任意のアクティビティを使うのと同じ方法で、Intentを使って同じサービスを開始できます。マニュフェストに他のアプリからの利用禁止を定義することも可能です。詳しくは>https://developer.android.com/guide/components/services#Declaring

COUTION:
 特に方法を指定しない限り、Serviceが自身のスレッドを作成することなく、別のスレッドで実行されることもありません。
 なので、MP3再生やネットワーク処理など負担のかかる処理では、フリーズエラーを予防するために、サービス内に新しいスレッドを作成する必要があります。

ServiceとThreadどっちを使う?

・ Serviceはバックグラウンド処理する場合
・ Threadはメインスレッド以外で処理する必要がある場合

例えば、アクティビティを実行してる間だけ音楽再生する場合は、onCreate()にスレッドを作成し、onStart()で実行開始、onStop()で停止。 Threadの代わりに、AsyncTascやHandlerThreadも使えます。

Service作成にあたって重要なOverrideメソッド

onStartCommand() / onBind() / onCreate() / onDestroy()

onStartCommand()

アクティビティなどの他のコンポーネントが、startService()を呼び出して、Serviceの開始を要求したときに、システムがonStartCommand()を呼び出します。onStartCommand()が実行されるとServiceが開始されます。

onStartCommand()を実装する時は、Service完了時にstopSelf()かstopServece()で、自身でサービスを停止させます(バインドのみを提供する場合には、onStartCommand()は実装しなくてよい)。

onBind()

bindService()を呼び出して、他のコンポーネントをServiceにバインドさせるときに、システムがonBind()を呼び出します。

onBind()実装時にはIBinderを返して、クラインとがServiceとの通信に使うインターフェースを提供する必要があります。

onBind()の実装は常に必要だけれど、バインドを許可しない場合はnullを返す必要があります。

onCreate()

onStartCommand()かonBind()を呼び出す前で、サービスが始めて作成された時に、1度限りのセットアップ処理をするために呼び出されるメソッド。

onDestroy()

Serviceが長時間使われなかったり、破棄されたときに呼ばれるメソッド。スレッドや登録済みリスナー、レシーバーのリソースをクリーンアップするのに、Serviceはこのメソッドを実装します。このメソッドは、Serviceが受け取る最後の呼び出しです。

Serviceの終了

stopSelf() stopService()

onStartCommand()のstartService()によってコンポーネントがServiceを開始したら、サービスがstopSelf()で自身を終了するか、他のコンポーネントがstopService()を実行するまで、サービスは稼働し続けます。

unbind

コンポーネントが、onStartCommand()を呼ばずに、bindService()によってServiceを生成したら、コンポーネントがServiceにバインドされてる限り、Serviceは稼働し続けます。Serviceがクライアントから全てアンバインドされたら、システムはServiceを破棄します。

Android System によるServiceの破棄

メモリーが下がり、ユーザーが操作してるアクティビティのシステムリソースを回復させなければならない場合にのみ、アンドロイドシステムはServiceを停止します。ユーザーが使っているアクティビティにバインドされたServiceは、破棄される可能性は低くなります。Foregroundで実行するよう定義したServiceは滅多に破棄されません。

システムは、時間の経過とともに、Serviceのバックグラウンド・タスク・リストでの位置付けを下げます。開発者はシステムによるServiceの復帰処理をデザインしてください。onStartCommand()の戻り値にもよりますが、もしシステムがServiceを破棄したら、システムはリソースが回復するなり、Serviceを復帰させます。詳しくは>https://developer.android.com/guide/components/processes-and-threads.html

Service作成

マニフェスト宣言

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

~で、ServiceやServiceを実行するプロセスを開始するために必要なプロパティを定義します。

android:name

Serviceのクラス名を指定する。唯一の必須属性、変更不可
アプリの安全性を保つため、Serviceを開始したりバインドするときは、常に明示的Intentを使います。

COUTION:
アプリの安全性を確保するため、Serviceを開始するときには常に明示的Intentを使い、ServiceのIntentフィルタは宣言しません。

どのServiceを開始するかについて、あいまい性を残す場合には、ServiceにIntentフィルタを定義して、Intentからコンポーネント名を除外して、その後にターゲットのサービスのあいまい性を解消するsetPackage()でIntentのパッケージを設定する必要があります。

android:exported

android:exported属性を含めて"false"に設定すると、Serviceを自身のアプリでしか利用できないようにできます。たとえ明示的Intentを使っても、Serviceを開始できなくなります。

要求されたServiceを生成する

要求されたServiceとは:
他のコンポーネントがstartService()を呼び出して、onStartCommand()メソッドを使って、生成を要求されたService。

Serviceの終了
onStartCommand()で開始したServiceは、
 自身で終了する場合には、stopSelf()
 他のコンポーネントで終了する場合には、stopService()

Serviceの開始とIntent
アクティビティなどのアプリケーションコンポーネントは、startService()でServiceを指定しIntentでServiceが使うデータを渡してServiceを開始し、Serviceは、onStartCommand()を使ってIntentを受け取って処理します。

要求されたサービスを生成するための拡張可能なクラス

Service
全てのServiceの基本クラス
Serviceはデフォルトでアプリのメインスレッドを使用するため、Serviceの処理によって、アクティビティのパフォーマンスを低下させることがあります。

そのため、このクラスを拡張するときは、Service全ての作業のための、新しいスレッドを作成することが重要です。

IntentService
全てのServiceの生成要求を、一件ずつ処理するワーカースレッドを使った、Serviceのサブクラス。Serviceで同時に複数の生成要求を処理する必要がない場合に最適。

onHnadleIntent()を実装するだけで、それぞれの生成要求のIntentを受け取り、バックグラウンド処理ができるようになります。

IntentServiceクラスの拡張 省略
Serviceクラスの拡張 省略

Serviceの開始

アクティビティや他のアプリケーションコンポーネントからIntent(開始Serviceを指定)を、startService()に渡して、Serviceを開始。

Androidシステムが、ServiceのonStartCommand()メソッドを使ってIntentを渡します(絶対に、onStartCommand()を直接呼び出してはいけません)

<参考>

Intent intent = new Intent(this,HelloService.class);
startService(intent);

AndroidシステムがServiceのonStartCommand()を呼び出しています。
Serviceが実行されてないときは、システムはonCreate()を呼び出してから、onStartCommand()を呼び出します。

Serviceでバインドが提供されてないときは、startService()で配信されたIntentがアプリケーションコンポーネントとServiceの唯一の通信手段となります。

ただし、Serviceから結果を返す場合、Serviceを要求するクライアントが、ブロードキャスト用にPendingIntentを作成でき(getBroadcast()を使う)、要求されたServiceのIntentに配信できます。その後、Serviceはブロードキャストを使って結果を配信できます。

Service要請が複数ある場合、それに対応するために、onStartCommand()に複数の呼び出しがはっせいしますが、停止するためには、stopSelf()やstopServiceは1つのみでOK。

Serviceの停止

stopSelf()やstopServiceで停止要求すると、androidシステムはServiceを破棄します。

ServiceがonStartCommand()への複数要求を同時処理している場合は、新しい生成要求がある可能性があるので、複数要求の同時処理後もServiceは停止させないようにします。1つの停止で全て停止します。

複数要求の停止は、stopSelf(int)を使って、最新の要求に基づいてServiceの停止要求を行います。具体的には、停止したい要求にID(onStartCommand()に配信したstartId)を渡します。

COUTION:
Serviceのバインドが有効な場合でも、onStartCommand()への呼び出しを受け取ったときは、常に自身でサービスを停止する必要があります。stopSelf()を使ってね。

バインドされたServiceを作成

bindService()でServiceにバインドすると、アプリケーションコンポーネントが長時間接続できます(通常では、コンポーネントがstarService()を呼び出すことではServiceは開始できない)。

バインドされたServiceを作成

・ onBind()を実装
  Serviceとの通信用のインターフェイスを定義するIBinderを返す
・ アプリケーションコンポーネントがbindService()を呼び出
  インターフェイスIBinderを取得しServiceのメソッドの呼び出しを開始

バインドされたコンポーネントがなくなると、システムによってServiceが破棄されます(バインドされたサービスは、onStartCommand()でサービスが開始された時と同じ方法で停止する必要はありません)。

アンバインド

unbindService()
Serviceにバインドされているクライアントがなくなったら、システムがサービスを破棄。

Foreground Sercice

ForegroundServiceでは通知を表示する必要があり、Service停止か、ForegroundからServiceを除去しない限り、通知を消すことはできません。

Create Foreground Service

startForeground(Id,notification)を実行
・ Notification
・ Intent
・ PendingIntent
・ notification.setLatestEventInfo(this,title,text,PendingIntent)
・ startForeground(Id,notification)

<参考>

Notification notification = new Notification(
                                R.drawable.icon,
                                getText(R.string.ticker_text),
                                System.currentTimeMillis()
                                );
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(
                              this, 
                              getText(R.string.notification_title),
                              getText(R.string.notification_message), 
                              pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

startForeground()に渡すIDには、0はNG

Foregourd Serviceの除去

stopForeground()
・ ServiceをForegroundから除去
・ 通知を除去するかを示すbool値も受け付けます
・ Serviceは停止しない
  ただし、ServiceがまだForegroundで実行中に停止した場合は、通知は削除される

Serviceのライフサイクル管理

省略

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

51歳からのプログラミング 備忘 Service [写経]

https://developer.android.com/guide/components/services

Serviceとは

Service:ユーザーインターフェイスを使わず、バックグラウンドで長時間処理を実行できるアプリケーションコンポーネントです。

他のアプリケーションコンポーネントを開始することもできるし、ユーザーが他のアプリケーションに変更しても、バックグラウンドで処理し続けます。

さらに、コンポーネントとサービスをバインドして交信し、プロセス間通信もできます。例えば、ネットワーク処理、音楽再生、ファイルI/O、コンテンツプロバイダー通信など、すべてバックグラウンドで処理できます。

サービスには3種類あります。

Foreground

ForegroundServiceは、ユーザーに通知されている処理を実行します。例えば、オーディオアプリはForegroundServiceを使って、オーディオトラックで再生します。ForegroundServiceでは通知を表示させます。ユーザーがアプリを操作してない時は、ForegroundServiceで処理し続けます。

Background

BackgroundServiceはユーザーによって直接気が付かない処理を実行します。例えば、アプリのストレージを圧縮するサービスの場合、それはバックグラウンド処理となります。

Note:
アプリがAPI26以降の時、アプリがforegroundになければ、システムはバックグラウンドサービスの実行に制約を課します。このようなケースでは、代わりにスケジュールされた作業を行います。

Bound

bindService()でサービスをアプリケーションコンポーネントにバインドします。バインドされたサービスは、コンポーネントと通信するため、クライアントサーバーインターフェイスを提供します。例えば、リクエスト送信、結果受信、プロセス通信など。

バインドされたサービスは、他のアプリケーションコンポーネントがサービスにバインドされている限り、終了することはありません。

サービスは、明示的な開始、バインド、どちらも使えますが、ドキュメントでは開始とバインドを分けて説明します。

実装は、onStartCommand()でサービス開始を要求するか、onBind()でバインドを許可するか、のどちらかで行います。

どのアプリケーションコンポーネントも、任意のアクティビティを使うのと同じ方法で、Intentを使って同じサービスを開始できます。マニュフェストに他のアプリからの利用禁止を定義することも可能です。詳しくは>https://developer.android.com/guide/components/services#Declaring

COUTION:
 特に方法を指定しない限り、Serviceが自身のスレッドを作成することなく、別のスレッドで実行されることもありません。
 なので、MP3再生やネットワーク処理など負担のかかる処理では、フリーズエラーを予防するために、サービス内に新しいスレッドを作成する必要があります。

ServiceとThreadどっちを使う?

・ Serviceはバックグラウンド処理する場合
・ Threadはメインスレッド以外で処理する必要がある場合

例えば、アクティビティを実行してる間だけ音楽再生する場合は、onCreate()にスレッドを作成し、onStart()で実行開始、onStop()で停止。 Threadの代わりに、AsyncTascやHandlerThreadも使えます。

Service作成にあたって重要な@Overrideメソッド

onStartCommand() / onBind() / onCreate() / onDestroy()

onStartCommand()

アクティビティなどの他のコンポーネントが、startService()を呼び出して、Serviceの開始を要求したときに、システムがonStartCommand()を呼び出します。onStartCommand()が実行されるとServiceが開始されます。

onStartCommand()を実装する時は、Service完了時にstopSelf()かstopServece()で、自身でサービスを停止させます(バインドのみを提供する場合には、onStartCommand()は実装しなくてよい)。

onBind()

bindService()を呼び出して、他のコンポーネントをServiceにバインドさせるときに、システムがonBind()を呼び出します。

onBind()実装時にはIBinderを返して、クラインとがServiceとの通信に使うインターフェースを提供する必要があります。

onBind()の実装は常に必要だけれど、バインドを許可しない場合はnullを返す必要があります。

onCreate()

onStartCommand()かonBind()を呼び出す前で、サービスが始めて作成された時に、1度限りのセットアップ処理をするために呼び出されるメソッド。

onDestroy()

Serviceが受け取る最後の呼び出し。

Serviceの強制終了

androidシステムは、ユーザーが現に使ってるアクティビティ用のリソースを確保するため、サービスを強制終了させることがあります。

ユーザーが現に使ってるアクティビティにServiceがバインドされてる場合には、強制終了される可能性は低く、Foregroundが宣言されてる場合は、強制終了の可能性は、ほぼありません。

Serviceは長時間実行されていると、バックグラウンドタスクのリスト位置が低まり、強制終了される可能性が高まっていきますので、強制終了時に備えて、システムによる再起動処理をデザインする必要があります。

システムがServiceを強制終了すると、リソースが回復し次第、Serviceが再起動します(onStartCommand()の戻り値による)。

Service作成

マニフェスト宣言

<manifest ... >
  ...
  <application ... >
      <service android:name=".ExampleService" />
      ...
  </application>
</manifest>

android:name
唯一の必須属性、変更不可
アプリの安全性を保つため、Serviceを開始したりバインドするときは、常に明示的Intentを使います。

どのServiceを開始するかについて、あいまい性を残す場合には、ServiceにIntentフィルタを定義して、Intentからコンポーネント名を除外して、その後にターゲットのサービスのあいまい性を解消するsetPackage()でIntentのパッケージを設定する必要があります。

android:exported
android:exported属性を含めて"false"に設定すると、Serviceを自身のアプリでしか利用できないようにできます。たとえ明示的Intentを使っても、Serviceを開始できなくなります。

要求されたServiceを生成する

要求されたServiceとは:
他のコンポーネントがstartService()を呼び出して、onStartCommand()メソッドを使って、生成を要求されたService。

Serviceの終了
onStartCommand()で開始したServiceは、
 自身で終了する場合には、stopSelf()
 他のコンポーネントで終了する場合には、stopService()

Serviceの開始とIntent
アクティビティなどのアプリケーションコンポーネントは、startService()でServiceを指定しIntentでServiceが使うデータを渡してServiceを開始し、Serviceは、onStartCommand()を使ってIntentを受け取って処理します。

要求されたサービスを生成するための拡張可能なクラス

Service
全てのServiceの基本クラス
Serviceはデフォルトでアプリのメインスレッドを使用するため、Serviceの処理によって、アクティビティのパフォーマンスを低下させることがあります。

そのため、このクラスを拡張するときは、Service全ての作業のための、新しいスレッドを作成することが重要です。

IntentService
全てのServiceの生成要求を、一件ずつ処理するワーカースレッドを使った、Serviceのサブクラス。Serviceで同時に複数の生成要求を処理する必要がない場合に最適。

onHnadleIntent()を実装するだけで、それぞれの生成要求のIntentを受け取り、バックグラウンド処理ができるようになります。

IntentServiceクラスの拡張 省略
Serviceクラスの拡張 省略

Serviceの開始

アクティビティや他のアプリケーションコンポーネントからIntent(開始Serviceを指定)を、startService()に渡して、Serviceを開始。

Androidシステムが、ServiceのonStartCommand()メソッドを使ってIntentを渡します(絶対に、onStartCommand()を直接呼び出してはいけません)

<参考>

Intent intent = new Intent(this,HelloService.class);
startService(intent);

AndroidシステムがServiceのonStartCommand()を呼び出しています。
Serviceが実行されてないときは、システムはonCreate()を呼び出してから、onStartCommand()を呼び出します。

Serviceでバインドが提供されてないときは、startService()で配信されたIntentがアプリケーションコンポーネントとServiceの唯一の通信手段となります。

ただし、Serviceから結果を返す場合、Serviceを要求するクライアントが、ブロードキャスト用にPendingIntentを作成でき(getBroadcast()を使う)、要求されたServiceのIntentに配信できます。その後、Serviceはブロードキャストを使って結果を配信できます。

Service要請が複数ある場合、それに対応するために、onStartCommand()に複数の呼び出しがはっせいしますが、停止するためには、stopSelf()やstopServiceは1つのみでOK。

Serviceの停止

stopSelf()やstopServiceで停止要求すると、androidシステムはServiceを破棄します。

ServiceがonStartCommand()への複数要求を同時処理している場合は、新しい生成要求がある可能性があるので、複数要求の同時処理後もServiceは停止させないようにします。1つの停止で全て停止します。

複数要求の停止は、stopSelf(int)を使って、最新の要求に基づいてServiceの停止要求を行います。具体的には、停止したい要求にID(onStartCommand()に配信したstartId)を渡します。

COUTION:
Serviceのバインドが有効な場合でも、onStartCommand()への呼び出しを受け取ったときは、常に自身でサービスを停止する必要があります。stopSelf()を使ってね。

バインドされたServiceを作成

bindService()でServiceにバインドすると、アプリケーションコンポーネントが長時間接続できます(通常では、コンポーネントがstarService()を呼び出すことではServiceは開始できない)。

バインドされたServiceを作成

・ onBind()を実装
  Serviceとの通信用のインターフェイスを定義するIBinderを返す
・ アプリケーションコンポーネントがbindService()を呼び出
  インターフェイスIBinderを取得しServiceのメソッドの呼び出しを開始

バインドされたコンポーネントがなくなると、システムによってServiceが破棄されます(バインドされたサービスは、onStartCommand()でサービスが開始された時と同じ方法で停止する必要はありません)。

アンバインド

unbindService()
Serviceにバインドされているクライアントがなくなったら、システムがサービスを破棄。

Foreground Sercice

ForegroundServiceでは通知を表示する必要があり、Service停止か、ForegroundからServiceを除去しない限り、通知を消すことはできません。

Create Foreground Service

startForeground(Id,notification)を実行
・ Notification
・ Intent
・ PendingIntent
・ notification.setLatestEventInfo(this,title,text,PendingIntent)
・ startForeground(Id,notification)

<参考>

Notification notification = new Notification(
                                R.drawable.icon,
                                getText(R.string.ticker_text),
                                System.currentTimeMillis()
                                );
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(
                              this, 
                              getText(R.string.notification_title),
                              getText(R.string.notification_message), 
                              pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);

startForeground()に渡すIDには、0はNG

Foregourd Serviceの除去

stopForeground()
・ ServiceをForegroundから除去
・ 通知を除去するかを示すbool値も受け付けます
・ Serviceは停止しない
  ただし、ServiceがまだForegroundで実行中に停止した場合は、通知は削除される

Serviceのライフサイクル管理

省略

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

StringBufferの練習

”う”と”み”を交互に繰り返す処理

メソッド1は繰り返し処理のところで毎回new StringBufferで文章が追加されるため
メソッド2に比べて処理時間がかかる。

StringBuffer.java
package javaStudy;

public class StringBufferExam {
    public static String answer = "";

    public static void main(String[] args) {
        System.out.println(method1(50));
        System.out.println(method2(50));        
    }

    public static String method1(int n) {
        for(int i = 0 ; i < n; i++) {
            answer += ((i%2==0)?"う":"み");   
        }
        return answer;
    }

    public static String method2(int n) {
        StringBuffer sb = new StringBuffer();
        for(int i = 0 ; i < n; i++) {
            sb.append(((i%2==0)?"う":"み"));  
        }
        answer = sb.toString();
        return answer;
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AmazonSQSAsyncの呼び出し方

AmazonSQSAsync の呼び出し方です。

    AmazonSQSAsync sqs = SQS.getAsyncClient();
    try {
      CreateQueueResult queue =
        sqs.createQueue(
          new CreateQueueRequest().withQueueName(QUEUE_NAME));
      HashMap<String, MessageAttributeValue> map =
        new HashMap<String, MessageAttributeValue>();
      map.put(
        "foo",
        new MessageAttributeValue().withDataType("String").withStringValue(
          "aaa"));
      Future<SendMessageResult> result =
        sqs.sendMessageAsync(
          new SendMessageRequest(queue.getQueueUrl(), "Message")
            .withMessageAttributes(map));
      while (!result.isDone()) {
        Thread.sleep(10);
      }
    } catch (Exception e) {
      logger.error(e);
    } finally {
      sqs.shutdown();
    }

sqs.sendMessageAsync の返り値として Future が返ってきます。 isDone() メソッドが呼ばれるとSQSの送信が完了した判定になるので、それまでループで待って最後shutdownするのがお作法のようです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS S3 のJava SDKで転送完了時にshutdownNowを呼ばないとスレッドが残り続ける

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/HLuploadFileJava.html
にS3へのファイルアップの方法が記載されています。

ただここの処理の最後で shutdownNow を呼ばないと、s3Clientのスレッドが残り続けてしまうことがあるようです。(SDKのバージョンによるかも知れません。)

        AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
                .withRegion(clientRegion)
                .withCredentials(new ProfileCredentialsProvider())
                .build();
        TransferManager tm = TransferManagerBuilder.standard()
                .withS3Client(s3Client)
                .build();

        // TransferManager processes all transfers asynchronously,
        // so this call returns immediately.
        Upload upload = tm.upload(bucketName, keyName, new File(filePath));
        System.out.println("Object upload started");

        // Optionally, wait for the upload to finish before continuing.
        upload.waitForCompletion();
        System.out.println("Object upload complete");

        // 以下のようにして転送マネージャーを終了させる必要があります。
        tm.shutdownNow();

参考

https://www.tcmobile.jp/dev_blog/programming/%E3%80%90java%E3%80%91cse%E3%83%9E%E3%83%AB%E3%83%81%E3%83%91%E3%83%BC%E3%83%88%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%80%81s3%E3%81%B8/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java(SpringBoot)でのAPIの呼び方、使い方

前提

動的Webアプリケーションを
まぁようはServlet Service Daoとかを作り
サーバーとの通信でWebページを作れるようになって…
フレームワーク無しで勉強して、その後Springフレームワークを勉強して…
けどAPIがわからない!APIってなんだよ!

そんなあなたへ

Spring Bootを使った、APIの呼び方を
備忘録がてら書きます!

※SPAでフロント側からAPIを呼ぶやつも今後紹介します!今回はサーバー側だけ!

APIとは

アプリケーションプログラミングインターフェース
難しいことはないです。ただのメソッドです。
外部のサーバーのメソッドを呼ぶってだけです。
他の人の家にたずねて、野菜分けてもらうみたいな感じです。

いつもは自分の家の中の食材使って、自身のサーバー内のメソッドを使って、サービスをだしてたけれど
やっぱ他のも使いたい!ってことで、外部のメソッドを呼びたい時があるのです

やり取りはJSON

ヤモリじゃないよ 13日の金曜日でもないよ
JavaScript Object Notation
{ id:1245, name:'たろう', comment:'わっほい' }
こういうやつ!
変数の名前:中に入ってるやつ
をカンマで区切っていく書き方
これがJSONっていう書き方です

急にそんな事言われても…って感じだよね

そもそも…

APIもメソッド

なので、引数があって、戻り値があります。
基本API呼ぶのはServiceです!

自分の家→(トマトあげる!)→相手の家
自分の家←(おかえしにキュウリ!)←相手の家

つまり

自分のService→(リクエスト)→相手のService
自分のService←(レスポンス)←相手のService

このリクエストやレスポンスにはBeanだったりModelというかEntityというか、まぁそういうのを使います

つまりこういうやつ!

あげるやつ!トマトEntity!

TomatoEntity.java
@Component
public class TomatoEntity {
    //id
    private String id;
    //名前
    private String name;
    //コメント
    private String comment;

    //以下、getterとsetter
    public String getId() {
      return id;
    }
    public String setId(String id) {
      this.id = id;
    }

    public String getName() {
      return name;
    }
    public String setName(String name) {
      this.name = name;
    }

    public String getComment() {
      return comment;
    }
    public String setComment(String comment) {
      this.comment = comment;
    }

    //Eclipseだと
    //Alt+Shift+Sのメニューから
    //Alt+Rでgetter setter生成を選択
    //Alt+Aで全部選択
    //Alt+Rで作成!って
    //ずっとAlt押しっぱなしでやっていけばすぐに作れます!
}

レスポンス用のキュウリEntity!

KyuuriEntity.java
@Component
public class KyuuriEntity {
    //日付
    private Date day;
    //メッセージの配列
    private List<String> message;

    //以下、getterとsetter
    public Date getDay() {
      return day;
    }
    public Date setDay(String day) {
      this.day = day;
    }

    public List<String> getMessage() {
      return message;
    }
    public list<String> setMessage(List<String> message) {
      this.message = message;
    }

    //まぁSpringならアノテーションで
    //getter setterあるので
    //メソッド作らなくても大丈夫ですが...
}

なので

自分のService→(TomatoEntity)→相手のService
自分のService←(KyuuriEntity)←相手のService

てことです!

んで、これをJSONに変換してやり取りするので

自分のService→(TomatoEntity)→(JSONになったTomato)→相手のService
自分のService←(KyuuriEntityに戻す)←(JSONのKyuuri)←相手のService

とまぁこんな感じで認識しとけばおk

JSONでやり取りしてるよってことが分かれば特に意識する必要なし!

もっと具体的に!

まぁふわふわしてて、実態を捕えずらいというか、なんか分かりにくいって思うので
具体例をどうぞ(いろいろ変だけど分かりやすさ重視!)

CallApiService.java
@Service
public class CallApiService {
    //まずはリクエストを作る
    TomatoEntity tomato = new Tomatoentity();
    tomato.setId("831");
    tomato.setName("トマト");
    tomato.setComment("おいしいよ");

    //APIに送るリクエストを作る
    RequestEntity<TomatoEntity> request = RequestEntity
        .post(new URI("http://kyuuri.api"))
        .accept(MediaType.APPLICATION_JSON)
        .body(tomato);

    //APIからレスポンスが返ってくる
    ResponseEntity<KyuuriEntity> response = restTemplate
        .exchange(request, Kyuuri.class);
}

解説!!

・リクエスト

先ほど設定したトマトEntityを作り、外のAPIにリクエストとして投げます
RequestEntityはSpringフレームワークの提供する型
  これがAPIに送るトマトを包んでくれる箱です
  箱にトマトEntityを詰めて届けるのです
new URIはJava.net.URIです
  住所ですね。わかるっしょ
.accept()MediaType.APPLICATION_JSONでJSON型に変換します
  さっき書いたとおり、JSONでやり取りするよってこと!
.body()が、リクエストの中身の情報
  今回は上で作ったトマトEntity詰めてるよ

つまりこういうこと
RequestEntity<送るentityの型> 好きな名前 
    = 
    RequestEntity.post(送る先のurl).accept(jsonにする).body(送るentityの中身)

・レスポンス

※今回はRestTemplete使うバージョンです
見やすさのためにもっかい書いとくね

CallApiService.java
@Service
public class CallApiService {
    //まずはリクエストを作る
    TomatoEntity tomato = new Tomatoentity();
    tomato.setId("831");
    tomato.setName("トマト");
    tomato.setComment("おいしいよ");

    //APIに送るリクエストを作る
    RequestEntity<TomatoEntity> request = RequestEntity
        .post(new URI("http://kyuuri.api"))
        .accept(MediaType.APPLICATION_JSON)
        .body(tomato);

    //APIからレスポンスが返ってくる
    ResponseEntity<KyuuriEntity> response = restTemplate
        .exchange(request, Kyuuri.class);
}

先ほど設定したキュウリEntityに対し、外のAPIからレスポンスが返ってきます
だからAPIからの戻り値の形わかってないとだめだよね
ResponseEntityはSpringフレームワークの提供する型
  これがAPIから帰ってくるキュウリを包んでくれる箱です
  箱にキュウリEntityが詰まって届くのです
restTempleteっていう便利な奴があるんだよ
詳しくは
RestTemplete公式リファレンス
.exchangeはRestTempleteのメソッドで
  リクエストがAPIに入ったら、戻り値を、指定した型として出力するよっていう
  まぁようはこれがAPIの(引数, 戻り値)ってことだぬ

つまりこういうこと
ResponseEntity<戻るentityの型> 好きな名前 = restTemplete
                    .exchange(リクエストの変数名, 戻る型.class);

本来ならもうちょっとメソッドが続いて
接続やレスポンスのタイムアウト設定だったり
認証だったり、このあとにバリデーションが続いたり
エラーステータスコードだかなんだかがくっついてくるのですが

APIはこうやるんだ!ってことを理解するならこれだけで十分かと!

そしたら最後に

CallApiService.java
@Service
public class CallApiService {
    //まずはリクエストを作る
    TomatoEntity tomato = new Tomatoentity();
    tomato.setId("831");
    tomato.setName("トマト");
    tomato.setComment("おいしいよ");

    //APIに送るリクエストを作る
    RequestEntity<TomatoEntity> request = RequestEntity
        .post(new URI("http://kyuuri.api"))
        .accept(MediaType.APPLICATION_JSON)
        .body(tomato);

    //APIからレスポンスが返ってくる
    ResponseEntity<KyuuriEntity> response = restTemplate
        .exchange(request, Kyuuri.class);

    //返ってきた値を取り扱う
    KyuuriEntity kyuuriResponse = response.getBody();
}

.getBody()で、こっちで設定した通りのキュウリEntityが取り出せるし
  おまけにAPIからの戻り値が中に入っているので
  これで、よそのAPIをたたけたことになりますね!

で?

うん。具体的にはみんな実験していろいろ試してくれい。
あくまで備忘録がてら書いているので雑だったりするけれど参考になればうれしいです!

そいじゃまた!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OSX 10.5 (Catalina) βでJavaを使う

macOS Catalinaですが、32-bitアプリが動作しなくなります。β5の時点で個人的に実用上はあまり問題は無いのですが、唯一の問題は現在Javaが動かない事です。

OpenJDKなども現時点(2019/8/16)は非対応でインストーラーなどでインストールする事ができません。これによりJavaが必要なソフトが動かない問題が出ていました。

そのうち対応するとは思いますが、homebrewを使って下記の方法にてインストール可能でしたのでメモしておきます。

OpenJDKのインストール

brew tap homebrew/cask-versions
brew cask install java11

バージョン確認

$ java -version
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)

インストールパス確認

$ which java
/usr/bin/java

むにゃむにゃ

しばらくいろいろな方法でインストールを試みたのですが、ダメで必要な場合は切り替えていたのですが、これで対応する事ができました。

参考

https://apple.stackexchange.com/questions/363728/how-to-run-an-app-that-require-java-sdk-on-macos-catalina-10-15

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OSX 10.15 (Catalina) βでJavaを使う

macOS Catalinaですが、32-bitアプリが動作しなくなります。自分の使っているアプリは32bitの物は多くなく、βの時点で実用上はあまり問題は無いのですが、唯一にして最大の問題はJavaがインストールできない事でした。

JDK,OpenJDKなども現時点(2019/8/16)はインストーラーでインストールする事ができません。これによりJavaが必須のソフトが動かない問題が出ていました。

Catalinaの正式リリース時までには対応されるとは思いますが、homebrewを使って下記の方法にてインストール可能でしたのでメモしておきます。

OpenJDKのインストール

brew tap homebrew/cask-versions
brew cask install java11

初回起動時のみ確認が出ます。

スクリーンショット 2019-08-16 9.30.17.png

バージョン確認

$ java -version
openjdk version "11.0.2" 2019-01-15
OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.2+9, mixed mode)

インストールパス確認

$ which java
/usr/bin/java

むにゃむにゃ

しばらくいろいろな方法でインストールを試みたのですがうまくいかず、必要な場合はOSを切り替えて使っていたのですが、これで対応する事ができました。

参考

https://apple.stackexchange.com/questions/363728/how-to-run-an-app-that-require-java-sdk-on-macos-catalina-10-15

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Java Listをcloneする.

・(SetやMapでも同じですが)ArrayListはCloneableですが、ListはCloneableではありません。

・ArrayListではなくListで渡されたものをcloneしたいが、素直にはできないという状況があります。どうすればよいか?

・下記にサンプルを示しますが、実際のオブジェクトのクラスのcloneメソッドを取得してinvokeするという方法があります。

・このあたりは、Javaを使っていて悔しい思いをするところです。int⇔Integerは代入できるのにint[]⇔Integer[]は代入できないとかいうのも同じような悔しさです。

・蛇足ながら、例えばHashSetでkeySetをすると、HashMap$KeySetが戻りますが、これはCloneableではなく、サンプルで示した方法ではcloneできません。こういうの、困るよね....。

・もう一つ蛇足。サンプルでgetDeclaredMethodを使っています。
 ・getDeclaredMethod(getDeclaredMethods)は当該のクラスで宣言されたメソッドを取得します。
 ・getMethod(getMethods)は、基底クラスで宣言されたメソッドも含めて取得します。

★追記★
下記のfelisさんの指摘にあるように、Java10からはListのcopyOfというものが使えます。
下記のテストプログラムは、JDK1.6で実行しています。

package jp.avaj.lib.algo;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import jp.avaj.lib.test.ArTest;
import jp.avaj.lib.test.L;

/**
  Listをcloneする

  ArrayListはcloneableだが、Listはcloneableではない.SetやMapなどでも同じ.
 */
public class Tips0056 {
  public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

    List<String> list = ArList.construct("a,b,c");
    List<String> newList;

    // これはコンパイルエラー
    //newList = (List<String>)list.clone();
    L.p("これならできる"); // しかしListで渡された時はArrayListであるかどうかは不明.
    newList = (List<String>)((ArrayList)list).clone();
    L.p(newList.toString());

    L.p("実際のクラスからcloneメソッドを取得してinvokeする");
    // 実際のクラスでcloneメソッドを取得する.パラメータがある時は後ろに指定するが今は不要
    Method cloneMethod = list.getClass().getDeclaredMethod("clone");
    // メソッドがない時は例外が発生するので、ここに来ればメソッドはある.

    // メソッドを実行する.パラメータがある時は後ろに指定するが今は不要.
    newList = (List<String>)cloneMethod.invoke(list);

    // 結果を確認する.
    ArTest.equals("clone","expected",3,"size",newList.size());
    ArTest.equals("clone","expected","a","(0)",newList.get(0));
    ArTest.equals("clone","expected","b","(1)",newList.get(1));
    ArTest.equals("clone","expected","c","(2)",newList.get(2));
    // 目で見て確認する
    L.p(newList.toString());
  }
}

結果は次のとおり

これならできる
[a, b, c]
実際のクラスからcloneメソッドを取得してinvokeする
OK clone:expected=3:size=3
OK clone:expected=a:(0)=a
OK clone:expected=b:(1)=b
OK clone:expected=c:(2)=c
[a, b, c]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

エニグマを実装してみた

暗号解読 | サイモン・シン | Amazon

サイモン・シンの「暗号解読」を読んでいたら、エニグマの仕組みについて説明があり、意外と実装できそうだったので作ってみた。

ソースコードは GitHub にあげています。

※以降、識別がしやすいように平文は小文字、暗号文は大文字で表記している

単一換字式暗号

エニグマは、キーボードで入力された1文字を別の1文字に変換して暗号化する。

このように入力された文字を別の文字に変換する暗号を換字式暗号という。
同じ換字式暗号としては、シーザー暗号が有名。

シーザー暗号では、文字を一定数だけずらすことで暗号化する。
たとえば、3文字ずらすシーザー暗号では、「a -> X」「b -> Y」「c -> Z」「d -> A」のように文字を変換する。

3文字ずらすシーザー暗号の変換表
enigma.jpg

変換表が一種類しか存在しないので、シーザー暗号のような暗号は単一換字式暗号と呼ぶ。

シーザー暗号は単純に文字をずらすだけなので、平文の同じ文字は常に同じ文字で暗号化されることになる(平文中の「a」は、暗号文中では常に「X」になっている)。
この結果、単一換字式暗号には平文と暗号文で文字の出現頻度の傾向が変わらないという特徴が生まれる。

たとえば、英語では「e」の文字が最もよく現れる。
3文字ずらすシーザー暗号では「e」は「B」に変換される。
したがって、暗号文の中で最もよく現れる文字も「B」になる可能性が高い。

僅かな情報だが、このようなヒントに平文の言語の文法や単語の特徴などを加味して分析することで、多くの単一換字式暗号は解読できてしまう。

このように、文字の統計情報をもとに暗号を解読する手法を頻度分析と呼ぶ。
シーザー暗号のような単一換字式暗号は、頻度分析に脆弱となる。

多表換字式暗号

単一換字式暗号の問題点は、暗号化のための変換表が一種類しかない点にある。
その結果、平文の同じ文字は常に同じ文字に暗号化され、平文の統計的な特徴が暗号文に残ってしまう。

そこで、変換表を複数用意する暗号化の方法(多表換字式暗号)が考え出された。
多表換字式暗号では、複数の変換表を用意して、平文の文字ごとに使用する変換表を切り替える。

簡単な多表換字式暗号の例
enigma.jpg

ここでは、1文字ずつシーザー暗号のシフト量を増やして変換表を切り替えるようにしている。
(※実際はもっと推測しづらい複雑な変換表の切り替え方が必要)

結果として、平文「hello」から「GCIHJ」という暗号文が得られた。
ここで、平文で現れていた「l」という文字は、暗号文上では「I」と「H」という異なる文字に置き換えられている。
このように、文字ごとに変換表を切り替えることで、平文上の同じ文字を異なる文字に暗号化できるようになり、頻度分析による解読を困難にできる。

実際の多表換字式暗号としては、ヴィジュネル暗号という暗号が有名。

ヴィジュネル暗号は、当初は解読不可能と言われていた。
しかし、使用する変換表に周期性があるという弱点を突くことで解読されるようになった。

エニグマ

エニグマは1918年にドイツ人のアルトゥール・シェルビウスによって発明された暗号機で、第二次世界大戦の際にドイツ軍が採用した。

スクランブラー

エニグマは、キーボードで文字を入力すると、ランプボード上の暗号化後の文字が点灯するという形で動作する。
(※画像は「エニグマ」で検索すれば色々出てくるので、そちらを参照)

このキーボードとランプボードの間には、スクランブラーと呼ばれる暗号化用の装置が存在する。
スクランブラーは、キーボードで入力された文字をかき混ぜて(スクランブルして)ランプボードに渡す。
これにより、入力された文字は別の文字に変換(暗号化)されることになる。

スクランブラーのイメージ
enigma.jpg

つまり、このスクランブラーは暗号化のための変換表ということになる。

ローター

単純にスクランブラーを通しただけの暗号化は、単一換字式暗号にしかならない。
単一換字式暗号は簡単に解読されてしまうので、これだけでは強力な暗号装置にはならない。

エニグマのスクランブラーは、ローター (Rotor) とも呼ばれ、名前の通り回転することができる。
ローターは、文字が入力されるたびに1文字分ずつ回転するようになっている。

ローターが回転するイメージ
enigma.jpg

これにより、エニグマは文字を入力するたびに異なる変換表が利用される多表換字式暗号として動作することになる。

上図は簡略化のためa~eの5文字しかないが、実際はアルファベットは26文字ある。
したがって、スクランブラー1つで26種類の変換表を使うことになる。

しかし、これは言い換えると26文字ごとに同じ変換表が利用されるということになる。
この変換表の繰り返しはヴィジュネル暗号の弱点でもあり、同じ変換表が利用されることは極力避けることが望まれる。

そこで、実際のエニグマでは3つのローターが取り付けられており、2つ目のローターは1つ目のローターが1週したら1つずれ、3つ目のローターは2つ目のローターが1週したら1つずれるという動作をするようになっている。
(※ローターが3つなのは初期のエニグマで、大戦中にローターの数は増やされていった)

ローターが3つ繋がったイメージ
enigma.jpg

この結果、変換表のパターンは $26 \times 26 \times 26 = 17,576$ 通りとなる。

さらに、ローターにセットするスクランブラーは取り替えが可能となっている。
つまり、3つのスクランブラーを3つのローターに自由な組み合わせでセットできる。
そのパターンは6通りなので、変換表のパターンは実質 $17,576 \times 6 = 105,456$ 通りとなる。

リフレクター

前述の模式図では3つ目のローターの次はランプボードになっていたが、実際はリフレクターと呼ばれる特殊なローターが取り付けられている。

リフレクターは、入力された電気信号を反射(reflect)してローター3に返すように動作する。
反射された電気信号はローター2、ローター1と帰っていき、その後でランプボードにつながる。

リフレクターの反射の様子
enigma.jpg

上図の状態で、例えば「a」をキーボードで入力したとする。
矢印をたどっていくと、最終的にランプボードの「C」にたどり着くことが分かる。

ここで、次はキーボードで「c」を入力した場合を考える。
再び矢印をたどっていく間に気づくかもしれないが、これは先程「a」を入力したときの経路の逆をたどっていることに他ならない。
当然、最終的にはランプボードの「A」にたどり着く。

つまり、ローターの初期状態が暗号化時と一致していれば、暗号文を入力することで平文を得ることができるということになる。

プラグボード

エニグマには、さらにプラグボードと呼ばれる仕組みが用意されている。

プラグボードはキーボードで入力された特定の2つの文字を相互に入れ替える仕組みで、計6つの置き換えを設定できるようになっている。

プラグボードで文字が入れ替えられているイメージ
enigma.jpg

この例では、「a」と「c」、「e」と「f」の2つの置き換えを設定している。

26文字の中から6つのペアを選ぶパターンは、100,391,791,500 通りにも達する。
ここにローターによる変換表のパターンも掛け合わされることにより、人力での総当たり攻撃は極めて困難となっている。

エニグマの鍵

エニグマの暗号化と復号で必要となる情報は、次の3つになる。

  1. スクランブラーの配置
  2. ローターの初期状態
  3. プラグボードの設定

つまり、これがエニグマの鍵となる。

ドイツ軍は、この鍵を1日1回変更しながら運用していた(大戦の終わり頃は日に何度か変更するようになった)。
日ごとに変更するのでこれを日鍵と呼ぶ。
どの日にどの鍵を使用するかは、コードブックにまとめられ配布されていた。

鍵は毎日交換されるので安心に思える。
しかし、1日にやりとりされるメッセージの量は膨大になる。同じ日鍵で暗号化された暗号文を多く入手できると、それだけ解読できる可能性は高くなる。

このため、実際にはさらにメッセージごとに短い鍵(メッセージ鍵)が使用されていた。

メッセージ鍵はローターの初期状態を決めるもので、「XYZ」のように3文字で表される1
ローターには文字が刻印されており、文字で回転の位置を調整できるようになっている。
つまり、「XYZ」は1つ目のローターの位置を「X」に、2つ目の位置を「Y」に、3つ目の位置を「Z」にする、という意味になる。

メッセージをやり取りするときは、まず最初にこのメッセージ鍵をランダムに決定する。
そして、メッセージ鍵を日鍵で暗号化して通信相手に送る。
その後は、日鍵のうちローターの初期状態だけをメッセージ鍵で置き換えて暗号文をやり取りする。

こうすることで、日鍵によって作成される暗号文を最小限に抑えるようにしていた。

エニグマを実装する

ここまで説明したエニグマの仕組みを、プログラム(Java)で実装する。

モデル

クラス構成は次のようになった。

enigma.jpg

「エニグマ」クラスが入り口となって、入力された文字列を変換し、結果を文字列で返す。

入力された文字列の情報は、1つ1つ「キー」という単位で分解して、「プラグボード」や「暗号化ローター」に渡されていく。
エニグマのリフレクターが果たす回路の反射は、メソッドの戻り値という形で再現している。

最終的に「プラグボード」が返した「キー」は変換が完了した値で、これを文字列に戻して「エニグマ」の戻り値としている。

クラス図だと「暗号化ローター」が3つあることが表現できていないので分かりづらいが、オブジェクト図にすると次のような感じになる。

enigma.jpg

キーと基本型

enigma.jpg

プログラムで使用する基本的な型として、「キー」「法26の数」「回転量」「入出力位置」「オフセット」がある。

「キー」はエニグマのキーボードで入力されたキー1つ1つを表しており、 az の 26 文字のいずれかを持つ。

Key
public class Key {
    ...
    static Key of(char c) {
        return new Key(c);
    }

    static Key of(Modulo26 value) {
        char key = (char)(value.getValue() + 'a');
        return new Key(key);
    }

    private final char value;

    private Key(char value) {
        if (!('a' <= value && value <= 'z')) {
            throw new IllegalArgumentException("value は a-z のいずれか");
        }
        this.value = value;
    }

    char getChar() {
        return this.value;
    }

    Modulo26 toNumber() {
        return Modulo26.of(this.value - 'a');
    }
    ...
}

この 26 というアルファベットの文字数を表す数は、ローターの「回転量」や「入出力位置」、「オフセット」などの様々なところで登場する。
さらに、ローターは回転しているので、これらの数はどれも 26 になると再び 0 に戻る特性がある。

これは「26 を法とした数」と言えるので、扱いやすいように「法26の数」というクラスを用意した。

Modulo26
public class Modulo26 implements Serializable {
    private final int value;

    public static Modulo26 of(int value) {
        return new Modulo26(value);
    }

    private Modulo26(int value) {
        this.value = Math.floorMod(value, 26);
    }

    boolean isZero() {
        return this.value == 0;
    }

    int getValue() {
        return this.value;
    }

    Modulo26 plus(Modulo26 other) {
        return this.plus(other.value);
    }

    Modulo26 minus(Modulo26 other) {
        return this.plus(-1 * other.value);
    }

    Modulo26 plus(int n) {
        return new Modulo26(this.value + n);
    }
    ...
}

暗号化ローターとスクランブラー

enigma.jpg

「ローター」はインターフェースとして定義した。
そして、暗号化で使うローター(「暗号化ローター」)と「リフレクター」を同じ「ローター」として扱えるようにした。

Rotor
public interface Rotor {

    IOPosition input(IOPosition position);

    void rotate();
}
EncryptionRotor
public class EncryptionRotor implements Rotor {
    private final Offset offset;
    private final Scrambler scrambler;
    private final Rotor nextRotor;
    private RotateAmount rotateAmount;

    EncryptionRotor(Offset offset, Scrambler scrambler, Rotor nextRotor) {
        this.offset = Objects.requireNonNull(offset);
        this.scrambler = Objects.requireNonNull(scrambler);
        this.nextRotor = Objects.requireNonNull(nextRotor);
        this.rotateAmount = RotateAmount.ZERO;
    }

    @Override
    public IOPosition input(IOPosition position) {
        IOPosition scrambled = this.scrambler.scramble(position, this.offset, this.rotateAmount);
        IOPosition reflected = this.nextRotor.input(scrambled);
        IOPosition reversed = this.scrambler.reverse(reflected, this.offset, this.rotateAmount);

        this.rotate();

        return reversed;
    }

    @Override
    public void rotate() {
        this.rotateAmount = this.rotateAmount.rotate();

        if (this.rotateAmount.isZero()) {
            this.nextRotor.rotate();
        }
    }
}
Reflector
public class Reflector implements Rotor {

    @Override
    public IOPosition input(IOPosition position) {
        return position.plus(13);
    }

    @Override
    public void rotate() {
        // Reflector does not rotate.
    }
}

こうすることで、「暗号化ローター」が持つ「次のローター」を「ローター」型で抽象化できる。
すると、ローター2が持つ「次のローター(実体はローター3)」とローター3が持つ「次のローター(実体はリフレクター)」を区別する必要がなくなる。
結果、3つのローターをすべて1つの「暗号化ローター」のインスタンスとして作成できるようになる。

エニグマの仕様上「スクランブラー」の配置は自由に変更できる必要がある。
したがって、「ローター」と「スクランブラー」は別のクラスとして抽出した。

「ローター」は回転するものとして初期位置や回転の状態を管理し、「スクランブラー」は変換処理だけを担うようにした。

Scrambler
public class Scrambler implements Serializable {
    private final Map<IOPosition, IOPosition> map;
    private final Map<IOPosition, IOPosition> reverseMap;

    public static Scrambler newInstance(Random random) {
        ...
    }

    Scrambler(Map<IOPosition, IOPosition> map) {
        ...
    }

    IOPosition scramble(IOPosition input, Offset offset, RotateAmount rotateAmount) {
        IOPosition shifted = input.minus(offset, rotateAmount);
        IOPosition scrambled = this.map.get(shifted);
        return scrambled.plus(offset, rotateAmount);
    }

    IOPosition reverse(IOPosition input, Offset offset, RotateAmount rotateAmount) {
        IOPosition shifted = input.minus(offset, rotateAmount);
        IOPosition reversed = this.reverseMap.get(shifted);
        return reversed.plus(offset, rotateAmount);
    }

    public void store(Path path) {
        ...
    }

    public static Scrambler load(Path path) {
        ...
    }
}

「スクランブラー」は newInstance(Random) でランダムな配線のインスタンスを生成できるようにしている。
また、このクラスはシリアライズ可能なので、作成したインスタンスは store() メソッドでファイルに出力でき、 load() メソッドで復元することができるようになっている。

プラグボード

Plugboard
public class Plugboard {
    private final Rotor rotor;
    private final Map<Key, Key> exchangeMap = new HashMap<>();

    Plugboard(Rotor rotor) {
        this.rotor = Objects.requireNonNull(rotor);
    }

    public void exchange(Key one, Key other) {
        ...

        this.exchangeMap.put(one, other);
        this.exchangeMap.put(other, one);
    }

    Key input(Key inputKey) {
        Key exchanged = this.exchangeMap.getOrDefault(inputKey, inputKey);
        IOPosition position = IOPosition.of(exchanged.toNumber());

        IOPosition reversed = this.rotor.input(position);

        Key reversedKey = Key.of(reversed.getValue());
        return this.exchangeMap.getOrDefault(reversedKey, reversedKey);
    }
}

「プラグボード」は、 exchange() メソッドでキーの変換ルールをあらかじめ設定しておく。
そして、入力されたキーとローターが返したキーに対して、ルールに従って変換を行っている。

エニグマ

Enigma
public class Enigma {
    private final Plugboard plugboard;

    public static Enigma newInstance(
            Key firstKey,
            Scrambler firstScrambler,
            Key secondKey,
            Scrambler secondScrambler,
            Key thirdKey,
            Scrambler thirdScrambler,
            Consumer<Plugboard> plugboardInitializer) {

        Reflector reflector = new Reflector();

        EncryptionRotor thirdRotor = new EncryptionRotor(toOffset(firstKey), firstScrambler, reflector);
        EncryptionRotor secondRotor = new EncryptionRotor(toOffset(secondKey), secondScrambler, thirdRotor);
        EncryptionRotor firstRotor = new EncryptionRotor(toOffset(thirdKey), thirdScrambler, secondRotor);

        Plugboard plugboard = new Plugboard(firstRotor);
        plugboardInitializer.accept(plugboard);

        return new Enigma(plugboard);
    }

    private static Offset toOffset(Key key) {
        return Offset.of(key.toNumber());
    }

    private Enigma(Plugboard plugboard) {
        this.plugboard = Objects.requireNonNull(plugboard);
    }

    public String input(String text) {
        StringBuilder sb = new StringBuilder();
        for (char c : text.toCharArray()) {
            Key key = Key.of(c);
            Key result = this.plugboard.input(key);
            sb.append(result.getChar());
        }
        return sb.toString();
    }
}

「エニグマ」は入出力の受け付けだけでなく、ファクトリとしての役割も持っている。

newInstance() に初期化に必要な情報(エニグマの鍵)を渡すことで、各種インスタンスを生成し、 Enigma インスタンスを返す。
Consumer<Plugboard> plugboardInitializer は「プラグボード」の配線を初期化するための処理を渡すための引数で、次のように実装することをイメージしている。

Enigma encryption = Enigma.newInstance(
    Key.D, firstScrambler,
    Key.K, secondScrambler,
    Key.F, thirdScrambler,
    plugboard -> {
        plugboard.exchange(Key.A, Key.R);
        plugboard.exchange(Key.E, Key.K);
        plugboard.exchange(Key.O, Key.T);
        plugboard.exchange(Key.P, Key.Q);
        plugboard.exchange(Key.Z, Key.M);
        plugboard.exchange(Key.Y, Key.C);
    });

実際に動かす

import java.security.SecureRandom;

public class Main {
    public static void main(String[] args) {
        // スクランブラーを生成
        SecureRandom random = new SecureRandom();
        Scrambler firstScrambler = Scrambler.newInstance(random);
        Scrambler secondScrambler = Scrambler.newInstance(random);
        Scrambler thirdScrambler = Scrambler.newInstance(random);

        // 暗号化用に Enigma インスタンスを生成
        Enigma encryption = Enigma.newInstance(Key.D, firstScrambler, Key.K, secondScrambler, Key.F, thirdScrambler, Main::initializePlugboard);

        // 暗号化
        String encrypted = encryption.input("helloxxworld"); // "xx" は空白スペースを表すための仮の文字列

        System.out.println(encrypted);

        // 同じキー情報で復号用の Enigma インスタンスを生成
        Enigma decryption = Enigma.newInstance(Key.D, firstScrambler, Key.K, secondScrambler, Key.F, thirdScrambler, Main::initializePlugboard);

        // 復号
        String plainText = decryption.input(encrypted);

        System.out.println(plainText.replace("xx", " "));
    }

    private static void initializePlugboard(Plugboard plugboard) {
        plugboard.exchange(Key.A, Key.R);
        plugboard.exchange(Key.E, Key.K);
        plugboard.exchange(Key.O, Key.T);
        plugboard.exchange(Key.P, Key.Q);
        plugboard.exchange(Key.Z, Key.M);
        plugboard.exchange(Key.Y, Key.C);
    }
}
実行結果
rsonmcqycsjk
hello world

ちゃんと暗号化と復号ができたっぽい。2

おわりに

訳者あとがき
(中略)
いったいシンの才能の秘密はどこにあるのだろうか? (中略)専門家にとってさえ込み入った内容を、ずぶの素人にもわかりやすく、しかも単に上っ面をなでるのではなく、ずっしりと手応えを感じさせるように書けることである。
(中略)
たとえば暗号機エニグマを解説した本はこれまでに何冊も出版されているが、「仮にタイムトラベルで二十世紀初めに戻ったとして、これを読んでおけば自力でエニグマ機を組み立てられるのではないか」、とまで思わされた本は本書だけだった。

暗号解読(下)(新潮文庫) 訳者あとがき より

思わされた結果がこれである。

「暗号解読」はエニグマだけでなく中世の暗号から公開鍵暗号まで、幅広く暗号の歴史をカバーしています。
公開鍵暗号は現在のインターネットの通信を支える重要な技術ですが、この本は人類が公開鍵暗号にたどり着くまでの様々な暗号の歴史や、そこに関わった多くの暗号作成者・解読者のドラマを読むことができて非常に面白いです。オススメです。

参考


  1. 実際は通信状況によりデータが破損していないか確認できるように「XYZXYZ」のように2回連続したものを暗号化して送信した 

  2. 正直、しっかりテストはできていないので、どこかに問題はあるかも 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む