【Java】調べて得た雑な知見

調べる前「え、Java?プログラミング言語の一種でしょ?JRE?JDK?SE?なにそれ?」

今「EEのJDK > SEのJDK > JRE」「個人開発者がJDKと言えばSEのJDK」「JDKをインストールすればJavaの開発ができる」「Javaで書かれたプログラムを動かすためだけならJREを入れればよい」「タダじゃないらしい」←どういうこと!?

Java

プログラミング言語の一種。Java語。

「どんなマシンでも動く!」で有名。実際はOSごとに仮想マシン(virtual machine、VM)が作られて、Javaのプログラムはその仮想マシン上で動く。JavaのVMなのでJVM。

JRE

Javaの実行環境。

OSは仮想マシンを作らないといけないが、どうやって?→JREをインストールすれば作れる(OSが勝手に作ってくれる)!

でも実行環境(誰かが作ったアプリをダウンロード等して自分のPCで実行する)を提供するだけで、開発(自分でコードを書き、コンパイルしてアプリを作る)ことはできない。

Javaバージョン9以降、JREのみの配布はされていない。数年前に「MinecraftのためにJRE入れたことあるよ」みたいな人はおそらくJavaバージョン8のJREの可能性あり。

JDK(SE)

Javaの開発環境。

開発するけどテストできない、なんてことはあり得ないので、JDKをインストールしたらJREも付いてくる。

SEは「Standard Edition」の略。普通の開発用のAPIが提供される。

JDK(EE)

Javaの開発環境。SEの上位版。

サーバー向けの機能を付けた拡張版。SEを拡張しているので、SEでできることはEEでもできる。

JREのEE版とかSE版とかあるのかないのかは気になるけど、もし仮にJREだけでは配布されないということなら、JDK(SE)をインストールしたらSE版のJREが、JDK(EE)をインストールしたらEE版のJREがインストールされる、ということなのかな?よく分からん。

8とか11とかのバージョン

Java SE – Downloadsによると2020年5月現在のSEの最新バージョンは14。

基本的に半年に一回メジャーバージョンアップをするらしく、半年ごとにバージョンが1つずつ上がっていく。

でも半年ごとにバージョンアップしてたら整合性チェックとか大変だから長期保証バージョンもある。それが8と11。

でもタダじゃない。

どういうこと?Javaって有償なの?

ここから少し話が深くなる。

Javaはサン・マイクロシステムズという会社によって開発されたんだけど、サン社はOracleという会社に買収されて、今ではOracleがJDKを提供している。

そのOracleが提供するJDKには2種類あって、OracleJDKと(Oracle)OpenJDKがある。

OracleJDKは商用で使わなければ無償、商用で使うならライセンス契約が必要とのこと。半年ごとのバージョンアップがあって今はバージョン14(サポートは半年で終了)だけど、バージョン11は8年のサポート付き。問い合わせると返事が返ってくるし、セキュリティアップデートもある。
ちなみにバージョン7やバージョン8も有償提供してきた分に関しては今もサポートがある。7は22年7月まで、8は25年3月まで。

OpenJDKは無償だけど、半年でアップデート。同じ会社が提供しているから、バージョンもOracleJDKと同じ14。でもこっちは11は長期サポート無し。セキュリティアップデートも無し。

以下の図が分かりやすい。

出典:JDKの新しいリリース・モデル、および提供ライセンスについて

白いところは今まで無償で提供していた部分、青いところはこれから無償で提供していく部分、赤いところは有償サポートで提供する部分。

  • 青 ・・・ OpenJDK
  • 赤 ・・・ OracleJDK

とみれば分かりやすいかな?

Oracleにしてみれば「半年でメジャーアップデートすることにしたよ。これまで通り無償だよ。だからこれからはみんなも半年ごとにJavaをアップデートしてね。最新バージョンしかセキュリティアップデートしないよ。でも有償のサポートを受けながら使っていきたい人やバージョンを変えたくない人がいることも知ってる。だからバージョン11の長期有償サポート制度、作っといたよ。最新バージョンでも有償サポート登録してくれた人はちゃんとサポートするよ。また今までの7とか8の有償サポート受けてた人もしばらくはサポート打ち切らないよ。」ってこと。

結局、ちょっと勉強したいという程度ならOpenJDK、商用には使いませんというのであればOracleJDKでも可?なのかな?

ちなみにJDKは他の会社が出しているものもあるけど、もうこれ以上は触れない。気になる人は自分で探して。

まとめ

「EEのJDK > SEのJDK > JRE」
「個人開発者がJDKと言えばSEのJDK」
「JDKをインストールすればJavaの開発ができる」
「Javaで書かれたプログラムを動かすためだけならJREを入れればよい」
「半年に一回アップデートせよ」

ちなみにこの記事書いた後にちょっと使う用事があったので、OracleJDK14入れました。特にややこしいことは言ってこず、普通にインストールできたので、ちゃんと気を付けておかないと本番で使ったら大変なことになりそう・・・

参考

Java JREとJDKの違いと関係性は!?環境変数設定と確認までの手順解説!!Javaマスターへの道 | Javaマスターへの道
Java SEとJDK、JRE、JVMの違いに関する解説 | Java入門
Java – Wikipedia
JDKの新しいリリース・モデル、および提供ライセンスについて
Java有償化とは?無償で利用できない?いや、できます。 | 30代後半、未経験からwebプログラマーに転職(実体験)
第1回:「Java」は昔も今もオープンです – 株式会社システムエグゼ
個人開発ユーザーのJAVA有償化への対応方法をまとめてみる – ジャズとエンジニア

【Git】GitHubから既存プロジェクトをダウンロード

毎回忘れるからメモ。

概要

  • コマンドプロンプト等を立ち上げ、適当なフォルダ(D:\work等)に移動。
  • git clone (ダウンロード用URLを貼り付け)でそのフォルダの配下に新フォルダを作成しその中にダウンロードする。

ダウンロード用URLの場所

ここ。

補足

git cloneで引っ張ってくると、そのプロジェクトの全ての履歴もコピーしてくる。

参考

Git – Git リポジトリの取得

【Boxstertar】GitHub Gistの使い方

Boxstarterとかあんまり関係ないけど、一応Boxstarterを使う時に使えると便利なので、そういう流れで書く。

アカウント作成

そんなに難しくないはず。自分はGitHubのアカウントがあったので、アクセスしたときにGitHubのアカウントでログインした。

やること

コードを書いて置いとく。非公開でもいいし、公開してもいい。

これがGist作成画面。「Gistを書く」的なリンクを探すとここに来るはず。もしくはこのリンクから飛ぶ。

  • Gist description… Gistの説明文。どういうコードなのかの説明を入れると良い。
  • Filename including extension… このコードを保存するときのファイル名。拡張子を付けて書く。
  • 書いたら「Create secret gist」または「Create public gist」をクリック。

はい完了。

作った後は

  • Editボタン 中身を編集できる。
  • Deleteボタン 作ったファイルを削除できる。
  • Rawボタン 生のtxtファイルを表示する(Boxstarterで使う)

こんな感じ。

「All Gists」をクリックすれば他の人が書いたコードが見える。Boxstarterに限らずありとあらゆるコードが公開されている。検索をかけて他の人のBoxstarterスクリプトを見ると色々発見がある。

「Boxstarter」で検索をするとよく分からないコードが出てくるので、「cinst」とか「Set-WindowsExplorerOptions」とかで検索すると、それが書かれているコード(=Boxstarter用である可能性が高い)がヒットする。

まとめ

パッとコードを書いてサッとシェアする。それがGitHub Gist。

【Boxstarter】スクリプトの育て方

WindowsのセットアップをBoxstarterで自動化した後、という前提での話。何それ?っていう人はこちら

Boxstarterのスクリプトの内容は大きく分けて「Windowsの設定」と「各ソフトのインストール」に分かれる。今回は「各ソフトのインストール」をどうやって増やしていくか、を見ていく。

そんなに難しくない。

概要

  • ここでインストールできるソフトを探す
  • PowerShellでcinstコマンドを使ってインストールしてみる
  • 良さそうだったらBoxstarterのスクリプトに追記

前回も書いたけど、BoxstarterはChocolateyというソフトを使ってインストールしている。だからChocolateyでインストールできるソフトを探せばよい、ということになる。

詳細

0 前提

Boxstarterを使った時点でChocolateyはインストールされているはず。確認の仕方は、PowerShellを立ち上げてchocoコマンドを打つと何か出てくる。

PS C:\WINDOWS\system32> choco
Chocolatey v0.10.15
Please run 'choco -?' or 'choco <command> -?' for help menu.

1 インストールできるソフトを探す

Chocolatey Software | Packagesで探す。試しに解凍ソフトの大御所「WinRAR」でも入れてみる。

検索ボックスにwinrarと入れてEnter。

コマンドをチェック

このコマンドからchoco install winrarというコマンドでインストールできるよ、ということが分かる。cinst winrarと打っても同じ。

参考
chocolatey 基本情報まとめ – Qiita

2 PowerShellで入れてみる

PowerShellを立ち上げてcinst winrarと打ってEnter。

PS C:\WINDOWS\system32> cinst winrar
Chocolatey v0.10.15
Installing the following packages:
winrar
By installing you accept licenses for the packages.
Progress: Downloading winrar 5.90.0.20200401... 100%

winrar v5.90.0.20200401 [Approved]
winrar package files install completed. Performing other installation steps.
The package winrar wants to run 'chocolateyInstall.ps1'.
Note: If you don't run this script, the installation will fail.
Note: To confirm automatically next time, use '-y' or consider:
choco feature enable -n allowGlobalConfirmation
Do you want to run the script?([Y]es/[A]ll - yes to all/[N]o/[P]rint):

なんか色々出てくるけど、ただのインストール確認なのでyと打ちEnter。画面に[Approved]と出てれば尚良し。この手順を省略したければ、最初の段階でcinst winrar -yというオプションを付ければ省略できる。

画面を見てるとWinRARをダウンロードしてインストールしてるのが分かる。作業が終わるとちゃんとスタートメニューに追加されてるし、Windowsの設定画面からアンインストールもできるようになってる。

ちなみにPowerShell上からWinRARをアンインストールしようと思ったらcuninst winrarでできる。

3 良さそうだったらBoxstarterのスクリプトに追記する

ちゃんと目的のソフトだったりバージョンだったりしたら問題ないということなので、Boxstarterのスクリプトに追記しておく。

まとめ

こういう風にちょっとずつ、自分の必要に応じて必要なソフトをcinstコマンドでインストールして、大丈夫そうならBoxstarterのスクリプトに追記すると、自分用の自動セットアップが組めるという訳。ほとんどはただインストールするだけだけど。

これらの流れはほとんどChocolateyの使い方なので、そっち方面で調べていくと色々分かっていくかも。

【ゼロから始める】Windowsのセットアップ自動化 with Boxstarter

先日surface Pro 4を修理に出して、完全に交換されて戻ってきたのでまっさらな状態。

で、これを機に以前から知っていた「Boxstarterで初期セットアップの自動化」というのにチャレンジしてみた。

結論から言うと、「専門的な知識無しで導入可。自動でやってくれる部分は多いが、自分のやってほしいことを全部やってくれるわけじゃない。でもやっておけばめっちゃ楽。」という感じ。やればハマる。

概要

Boxstarterがほとんどやってくれる。おおまかな手順としては

  • インストールしてもらうソフト、設定してもらうWindowsの設定を書いたスクリプトを準備(1度作れば後は追記のみ)
  • PowerShellかコマンドスクリプトを立ち上げて、Boxstarterをインストール(どうも公式からzipファイルを落としても、インストーラーが入っているわけでもなく、batファイルを実行せよってだけだから、結局は自分でコマンド打つ方が早い(後述))
  • 作っておいたスクリプトを読み込ませる

以上。あとは自動でやりきれなかった部分を手動でやるだけ。Officeの有効化とか。

詳細

1 スクリプトの準備

まずはスクリプトの準備。普通のテキストファイルでOK。自分はネット上で保存している(後述)。

以下は自分の非常にシンプルなスクリプト。

Set-WindowsExplorerOptions -EnableShowHiddenFilesFoldersDrives -EnableShowProtectedOSFiles -EnableShowFileExtensions
Enable-RemoteDesktop
Disable-GameBarTips

cinst GoogleChrome
cinst sublimetext3
cinst androidstudio
cinst avastfreeantivirus
  • 1行目 「隠しファイル・フォルダを表示する」+「OSのシステムファイルを表示する」+「拡張子を表示する」を同時に設定
  • 2行目 リモートデスクトップを有効にする
  • 3行目 ゲームに関するヒントを無効にする(Windows10が「これはゲームか」と思ったソフトを立ち上げると「録画できますよ」とか言ってくるやつ)
  • 4行目以降 各ソフトのインストール

どんなソフトがどんなコマンドでインストールできるのかはChocolatey Software | Packagesで探す。

>choco install ソフト名という部分が有効なコマンド。choco installcinstは同じ意味なので、このサイトで見つかればそのソフトはスクリプトに追記できるということ。逆に見つからなければ手動で入れないといけない。

  • どんなソフトがインストールできるのか探してみれば分かるかもしれないけど、ほとんどはプログラマーとかエンジニアが喜びそうなものばかり。
  • 最初はあれこれ欲張らず、ブラウザとアンチウィルスソフトぐらいでいいと思う。後から「cinstコマンドでインストールできるかどうか自分でやってみて確認→できたらスクリプトに追記」とすればよい。これを「スクリプトを育てる」と言う(今決めた)。スクリプトの育て方は別記事にまとめているのでそちらを参照。
  • スクリプトをネット上に保存する手段としてBoxstarter公式でも紹介されているのが、GitHub Gist。コードを書いて保存する、ただそれだけのサイト。ここから他の人が書いたコードが探せる。このサイトの使い方は別記事にまとめているのでそちらを参照。

2 Boxstarterインストール

管理者としてPowerShellを立ち上げる。方法は調べたら出てくるはず。簡単に言うと「Windowsのスタートボタンを右クリックすると出てくるメニューの中にある」。

立ち上げたら以下のコマンドをコピペしてEnter。

Set-ExecutionPolicy RemoteSigned

然る後に、以下のコマンドをコピペしてEnter。

. { iwr -useb https://boxstarter.org/bootstrapper.ps1 } | iex; Get-Boxstarter -Force
  • 1つ目のコマンドは「ローカルに保存された.ps1ファイル全ておよび外部から取得した署名付き.ps1ファイルの実行を許可する」という設定。デフォルトではRestrictedに設定されていて、このままでは外部はおろかローカルに保存されたps1ファイルすら実行できない。Unrestrictedに設定してもいいかもしれない(Unrestrictedはすべての.ps1ファイルを実行するけど、外部から取得したものについては一応実行してよいかどうかの確認がある)。-Scope Processというオプションを付けると、今実行しているPowerShellのウィンドウのみの適用となって、ウィンドウを閉じるとExecutionPolicyの設定は元に戻る。
  • 2つ目のコマンドはBoxstarterのインストールコマンド。おそらくネットから.ps1ファイルを取得してそのままよしなに実行せよ、ということ。詳しいことは不明。

3 作っておいたスクリプトを実行

Boxstarterのインストールが終わったら、以下のコマンドを実行。

Install-BoxstarterPackage -PackageName (スクリプトのパス、またはGistに保存したスクリプトのRawファイルのURLを貼り付け) -DisableReboots

スクリプトはローカル保存のやつでもいいけど、新品のPCにファイルを移すって意外と面倒なのでネットから取ってくるようにしている訳で、取ってくるファイルは生のtxtファイルが良い。これが簡単にできるのがGitHub Gist。そういうこと。

Gistに保存したスクリプトのRawファイルというのは、

ここをクリックすると出る。そのURLをコピペ。

まとめ

一度スクリプトを作っておけば、新PC上でやることはPowerShellで3つのコマンドを打つだけ。簡単すぎる。

ちなみにこの後手動でやった設定はこちら。

  • 自宅PCとの共有設定
  • PC起動時に立ち上がるソフトの確認
  • Officeのインストール&ライセンス認証

これを自動化しようと思うと手間がかかりそうだから、別に手動でいい。

参考

自動化を愛するWindows使いへ Boxstarterのすすめ | re-imagine
https://torumakabe.github.io/post/intro_boxstarter/
Boxstarter で快適なセットアップを – Qiita
https://qiita.com/mihochannel/items/b7d2bfd8ecc5abaf84a5
Boxstarter · GitHub
https://gist.github.com/ikkou/11243137
PCセットアップを自動化しよう!Boxstarter | cloud.config Tech Blog
https://tech-blog.cloud-config.jp/2019-12-20-pcsetup-automation-boxstarter/
Boxstarter
https://boxstarter.org/Learn/WebLauncher
Boxstarter Windowsの設定に関するコマンド
https://boxstarter.org/WinConfig
Boxstarter で快適なセットアップを – Qiita
https://qiita.com/mihochannel/items/b7d2bfd8ecc5abaf84a5

【AndroidApp】アプリタイトルの文字色の変更について

こいつの文字色の変更の仕方を調べてたんだけど、どうも出てくるのはややこしいのばかり。

でもスーパー簡単な方法を発見した。

以下res/values/styles.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <!-- タイトル文字列の色 -->
        <item name="titleTextColor">#000000</item>
    </style>

</resources>

以上。

【AndroidApp】スピナーのカスタムについて

概要

雑な図解ですみません。

以下コード。

mainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val list = mutableListOf(
        "option 1",
        "option 2",
        "option 3",
        "option 4",
        "option 5",
        "option 6"
    )

    val adapterForSpinner = ArrayAdapter<String>(this, R.layout.custom_spinner, list)
    adapterForSpinner.setDropDownViewResource(R.layout.custom_spinner_dropdown)
    spinner1.adapter = adapterForSpinner

    以下略

res/layout/custom_spinner.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="?attr/spinnerDropDownItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="?attr/dropdownListPreferredItemHeight"
    android:ellipsize="marquee"
    android:textColor="@android:color/holo_red_dark"
    android:background="@android:color/holo_green_dark"
    />

res/layout/custom_spinner_dropdown.xml

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="?attr/spinnerDropDownItemStyle"
    android:singleLine="true"
    android:layout_width="match_parent"
    android:layout_height="?attr/dropdownListPreferredItemHeight"
    android:ellipsize="marquee"
    android:textColor="@android:color/holo_blue_dark"
    android:background="@android:color/white"
    android:textAlignment="center"/>

結果

▼マーク消えちゃった。

参考

スピナーを作成する – Androidプログラマへの道 ~ Moonlight 明日香 ~
【Android Studio】Spinner スピナーの文字色・背景色のカスタマイズ方法

【翻訳API】IBMのWatson Language Translator APIの使い方

概要

  • IBMはIBM Cloudというプラットフォームの中で翻訳APIを提供している。(制限付き無料)
  • IBM Cloudのアカウントを作成しAPIを有効化。
  • APIキーをゲットして以下のリクエストを投げる。
(コマンドプロンプトの場合)
curl -X POST -u "apikey:(ここにAPIキーを貼り付け)" -H "Content-Type: application/json" -d "{\"text\": [\"Hello, world! \", \"How are you?\"], \"model_id\":\"en-es\"}" "(ここに専用URLを貼り付け)/v3/translate?version=2018-05-01"

(PowerShellの場合)
(まずはBASIC認証情報の作成)
$cred = Get-Credential
(これをするとダイアログが出てユーザー名とパスワードを求められるので、ユーザー名は"apikey"の6文字、パスワードはAPIキーを貼り付ける)
(然る後に以下のコマンド)
Invoke-WebRequest `
-Method POST `
-ContentType: "application/json" `
-Body '{"text": ["Hello, world!", "How are you?"], "model_id":"en-es"}' `
-Credential $cred `
-Uri "(ここに専用URLを貼り付け)/v3/translate?version=2018-05-01" | Select-Object -Expand Content

返ってくるデータはこちら。

{
  "translations" : [ {
    "translation" : "¡ Hola, mundo!"
  }, {
    "translation" : "¿Cómo estás?"
  } ],
  "word_count" : 5,
  "character_count" : 25
}

解説

アカウント作成

適当に「IBM translator api」で検索すると「Watson Language Translator」というのが出てくる。「登録する」または「今すぐ使う」をクリックすると、アカウント作成後すぐにTranslator APIを有効化するページに飛びます。

アカウント作成のためのメールアドレス確認。

アカウント作成。自分は実はすでにログインIDを持っていたけどアカウントは持っていなかったという謎な状態だったので、個人情報・パスワード等を入力するタイミングが無かった。でも作るにしてもそんなに難しくないはず。

API有効化ページ。右下のCreateをクリック。

クイックスタート的なページに飛ばされるので、APIキーをゲットするためにページ左部のManageをクリック。

APIキーと専用URLゲット。

コマンドについて

BASIC認証が必要。curl-uコマンドでやってくれるけど、Invoke-WebRequestは暗号化を自分でしないといけない。

↓これをすると

↓これが出て

ユーザー名 : apikey
パスワード : (APIキーを貼り付け)

で、以下のコマンドを打つとよい。

Invoke-WebRequest `
-Method POST `
-ContentType: "application/json" `
-Body '{"text": ["Hello, world!", "How are you?"], "model_id":"en-es"}' `
-Credential $cred `
-Uri "(ここに専用URLを貼り付け)v3/translate?version=2018-05-01" | Select-Object -Expand Content

参考
Invoke-WebRequest , Invoke-RestMethod で 基本認証 – Qiita

送信するJSONについて

見やすくするとこう。

{
    "text": [
        "Hello, world!",
        "How are you?"
        ],
    "model_id": "en-es"
}

model_idで翻訳元、翻訳先を同時に指定できるみたい。翻訳元を自動検出するなら、

{
    "text": [
        "Hello, world!",
        "How are you?"
        ],
    "target": "es"
}

参考
Language Translator – IBM Cloud API Docs

【翻訳API】Microsoft Azureの翻訳APIの使い方

概要

  • MicrosoftはAzureというプラットフォームの中で翻訳APIを提供している(制限付き無料)
  • Azureのアカウントを作成し、Translator Text APIを有効化する。
  • APIキーをゲットし、以下のリクエストを打つ。
(コマンドプロンプト)
curl -X POST "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&amp;to=de" -H "Ocp-Apim-Subscription-Key: (APIキーをここに貼り付け)" -H "Ocp-Apim-Subscription-Region: australiaeast" -H "Content-Type: application/json; charset=UTF-8" -d "[{'Text':'Hello, what is your name?'}]"

(PowerShell)
Invoke-WebRequest `
-Method POST `
-Headers @{ "Ocp-Apim-Subscription-Key" = "(APIキーをここに貼り付け)" ; "Ocp-Apim-Subscription-Region" = "australiaeast"} `
-ContentType: "application/json; charset=UTF-8" `
-Body '[{"Text":"Hello, what is your name?"}]' `
-Uri "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&amp;to=de" | Select-Object -Expand Content

返ってくるデータはこちら。

[{"detectedLanguage":{"language":"en","score":1.0},"translations":[{"text":"Hallo, wie heißt du?","to":"de"}]}]

Googleより数倍分かりやすい。

解説

アカウント作成

Microsoftアカウントを作った後にAzureのアカウントを作る。Microsoftアカウント作成はここでは割愛。

「Azure」で検索してそれっぽいLPにたどり着けば「無料で始める」というボタンがあるはずなのでそれをクリック。(LPなのでデザインはコロコロ変わるだろうし画像は省略)

個人情報登録。名前・メールアドレス・電話番号。クレジットカード情報。規約同意。数ステップでできた。

ログインしてからTranslator Text APIを有効にするまで

以下画像で説明。

初回ログイン時はどうもクイックスタートセンターとか言うところに飛ばされるみたいなので、Homeに移動。

「リソース作成」をクリック。

検索ボックスに「Translator Text」と入力すると目的の翻訳APIが出てくる。

Createをクリック。

次に詳細を入力。

  • リソースグループとは、他のAPIとかとまとめて、一括で設定したり一括で請求されたり。
  • 地域は自動で選択されてる(日本からだったらAustralia East)けど、ここでGlobal以外を選択したら、HTTPリクエストに地域指定を含めないといけない。ま、含めればいいんだけど。
  • 名前は適当に。
  • 料金体系もここでセットできる。2百万文字までなら無料。

入力が終わったらReview + createをクリック。

ちょっと待ったら認証されるので、改めてCreateをクリック。

またちょっと待つと「できたよ」みたいなことを言われるので、Go to resourceをクリック。

できてからすぐ行くと反映されてないっぽいけど、しばらく待つとTranslator Text APIのリソースが作られる。

後はAPIキーの取り方とか、クイックスタート的なものが表示されるのでそれに従う。APIキーは丸で囲んだページで取得できるみたい。APIキーは30文字程度。

リクエストについての詳細

Googleと違ってAzureではGETメソッドは受け付けてないみたい。無理矢理クエリパラメーター作ってGETで投げたら「そのメソッドは受け付けてません」って言われた。

なのでこれ↓がおそらく最低限のリクエストだと思われる。

(コマンドプロンプト)
curl -X POST "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&amp;to=de" -H "Ocp-Apim-Subscription-Key: (APIキーをここに貼り付け)" -H "Ocp-Apim-Subscription-Region: australiaeast" -H "Content-Type: application/json; charset=UTF-8" -d "[{'Text':'Hello, what is your name?'}]"

(PowerShell)
Invoke-WebRequest `
-Method POST `
-Headers @{ "Ocp-Apim-Subscription-Key" = "(APIキーをここに貼り付け)" ; "Ocp-Apim-Subscription-Region" = "australiaeast"} `
-ContentType: "application/json; charset=UTF-8" `
-Body '[{"Text":"Hello, what is your name?"}]' `
-Uri "https://api.cognitive.microsofttranslator.com/translate?api-version=3.0&amp;to=de" | Select-Object -Expand Content

必要そうなクエリパラメーター

  • to ・・・ (必須)何語に翻訳するのか
  • from ・・・ 何語から翻訳するのか

必要なヘッダー

  • Ocp-Apim-Subscription-Key ・・・ (必須)APIキー
  • Ocp-Apim-Subscription-Region ・・・ Translator Text APIを有効化するときの地域を選んだ場合は必須
  • Content-Type ・・・ (必須)application/json; charset=UTF-8のみ受け付け
  • Content-Length ・・・ ドキュメントには必須って書いてあるけどいらんのちゃう?

リクエストボディについて(JSON形式)

[
    {
        "Text" : "Hello, what is your name?"
    }
]

配列の中のオブジェクトは100個まで含められるけど、全体の文字数は1度にスペースを含めて5000文字までにする。

まあ複数の翻訳が欲しけりゃ複数回リクエスト投げればいいんだけど。

参考
Translator Text API V3.0 Reference – Azure Cognitive Services | Microsoft Docs
Translator Text API Translate Method – Azure Cognitive Services | Microsoft Docs

おまけ

調べるついでに分かったPowerShellの基本的なこと。

HtTPリクエストのヘッダーの複数指定

ハッシュテーブルを作って設定してやる。形式は以下。

@{
    "キー名" = "値"
    "キー名" = "値"
    "キー名" = "値"
}

または

@{ "キー名" = "値"; "キー名" = "値"; "キー名" = "値" }

参考
PowerShell のハッシュテーブルの使い方

【Android App】REST APIの呼び出し

概要

  • UI処理(メインスレッド、大体はMainActivity)とHTTPリクエストはスレッドを分けないといけない。そのため、AsyncTaskクラスを継承した自作クラスを作り、doInBackgroundメソッドをオーバーライドして、そこにHTTPリクエスト部分を書く。
  • 以下の処理は例外がいろいろ返ってくるかもしれないので、try{}の中に書く。
  • URLオブジェクトを作成。今回はGETメソッドで、パラメーターを付け足していくので、文字列のURIエンコードもする。
  • URLオブジェクトのopenConnection()メソッドでURLConnectionインスタンスをHttpURLConnectionとして取得し、connect()メソッドでリモートリソースに接続、データ取得。
  • 大体JSON形式で返ってくる(XMLもあり)ので、取得したデータを文字列として引数に渡したJSONObjectを生成してデータを取り出す。
  • 返ってくる例外はMalformedURLException、IOException、JSONExceptionの3つ。
  • 最後にHttpURLConnectionインスタンスのdisconnectメソッドで接続を切る。取得したデータを溜めているバッファーも閉じるけど、例外出るかも。

完成コード

以下全てKotlin。

AsyncHttpRequest.kt

import android.os.AsyncTask
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL

class AsyncHttpRequest() : AsyncTask<String, Void, String>() {

    override fun doInBackground(vararg urls: String?): String? {
        // The argument will be encoded URL strings

        var connection: HttpURLConnection? = null
        var reader: BufferedReader?
        val buffer: StringBuffer

        try {

            val url = URL(urls[0])
            connection = url.openConnection() as HttpURLConnection
            connection.connect()

            val statusCode: Int = connection.responseCode
            if (statusCode != HttpURLConnection.HTTP_OK) {
                System.err.println("Connection faild. statusCode: " + statusCode)
                return null
            }

            val stream = connection.inputStream
            val inReader = InputStreamReader(stream)
            reader = BufferedReader(inReader)
            buffer = StringBuffer()
            var line: String?
            while (true) {
                line = reader.readLine()
                if (line == null) {
                    break
                }
                buffer.append(line)
            }

            return buffer.toString()

        } catch (e: IOException) {
            e.printStackTrace()
        }
        finally {
            connection?.disconnect()
            try {
                reader?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }

        return null

    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {

    (省略)

    private fun getTranslationFromGoogle (originalText : String) : String {

        val myURLForGoogle : String = "https://translation.googleapis.com/language/translate/v2?target=en&amp;q=" + Uri.encode(originalText)
        val myResult : String? = AsyncHttpRequest().execute(myURLForGoogle).get()

        if (myResult != null) {
            try {
                val parentJsonObj = JSONObject(myResult)
                val myTranslatedText : String =
                    parentJsonObj.getJSONObject("data")
                        .getJSONArray("translations")
                        .getJSONObject(0)
                        .getString("translatedText")

                return myTranslatedText

            } catch (e: JSONException) {
                e.printStackTrace()
                return "Failed persing JSON."
            }


        } else {
            return "Translation failed."
        }

    }

}

AsyncTaskクラス

AsyncHttpRequest.ktというファイルを追加して

class AsyncHttpRequest() : AsyncTask<T1, T2, T3>() {

    override fun doInBackground(vararg params: T1?): T3 {
        
        (処理)

        return T3型の何か

    }

}

と書く。(要import

呼び出したいときはメインスレッドで

AsyncHttpRequest().execute(T1型の引数) // 実行されるだけで返り値は返ってこない

AsyncHttpRequest().execute(T1型の引数).get()  // 返り値(T3型)が欲しいとき

とする。

解説

  • T1 ・・・ executeメソッドの引数の型を指定
  • T2 ・・・ 途中経過を発行するメソッドの戻り値の型を指定(後述)
  • T3 ・・・ .execute(T1).get()の戻り値の型を指定

総称型なので、自分で決められる。例えば

class AsyncHttpRequest() : AsyncTask<String, Void, String>() {

    override fun doInBackground(vararg urls: String?): String? {

        (処理)

        return String型の何か または null
        
    }

}

こんな感じ。

プログレスバーなんかで途中経過を表示したいときはT2Intか何かを指定する。今回は途中経過は使わないので、知りたい人は「doInBackgroundメソッドの中でpublishProgressメソッドを実行するとonProgressUpdateメソッド(要override)が呼ばれる」という方向性で検索してね。

また、T3型の返り値を返すだけじゃなく、その返り値を使ってちょこちょこ処理を続けたいときはonPostExecuteメソッドをオーバーライドしてその中でちょこちょこ処理を続ける。これはメインスレッドで実行される。

また、doInBackground内でUIに変更を加えないようにしないといけない。UIの変更(例えばテキストビューに文字列をセットするとか)はメインスレッド上で行うので、バックグラウンドからちょっかいを出してはいけない。なぜなのかは参考サイトを参考に。どうしてもちょっかいを出したい場合はonProgressUpdateonPostExecuteの中で行う。

参考
AsyncTask  |  Android Developers
[Android] 非同期処理 AsyncTaskの使い方
android.os.AsyncTaskの正しい使い方 – 株式会社ライトコード

URLオブジェクトとURIエンコードとURLConnectionインスタンス

まずURLオブジェクトから。こいつは後述するURLConnectionインスタンスを生成するのに必要。

val url = URL(URLを文字列として代入)

ここで、URLにスペースや日本語を付加しないといけない場合、エンコードが必要。

val urlString = "https://translation.googleapis.com/language/translate/v2?q=" + Uri.encode("こんにちは")

// こんにちは → %E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF

val url = URL(urlString)

もしこのとき、URL文字列がイミフだった場合URLオブジェクトが作れず、例外MalformedURLExceptionが発生する。これはIOExceptionを継承したものなので、まとめてキャッチできるのかな?よく分からん。でも今回は起こり得なさそうなので放置。

で、生成したURLオブジェクトのopenConnectionメソッドでURLConnectionインスタンスを、HttpURLConnectionインスタンスとして作る。接続への道を開く感じ?

var connection : HttpURLConnection? = null

try {
    connection = url.openConnection() as HttpURLConnection

失敗すると例外IOExceptionが発生すると思われる。なのでtry-catch構文の中に書くこと。

HttpURLConnectionインスタンスを作ったタイミングでヘッダーやメソッドなどがセットできる。(任意)

// リクエストメソッド
connection.setRequestMethod("GET")
connection.setRequestMethod("POST")

// POSTメソッドの場合、これをしないとボディが使えない
connection.setDoOutput(true)

// タイムアウトの設定
connection.setConnectTimeout(100000)
connection.setReadTimeout(100000)

// ヘッダー
connection.setRequestProperty("User-Agent", "Android")
connection.setRequestProperty("Content-Type", "application/json; charset=UTF-8")

// リダイレクト設定
connection.setInstanceFollowRedirects(false) // リダイレクトしない

タイムアウトについて。setConnectionTimeoutsetReadTimeoutで設定した時間を過ぎても接続が完了しなかったりデータ読み込みが完了しなかった場合、例外SocketTimeoutExceptionが発生するって公式ドキュメントのじっちゃが言ってた。

そして接続。

connection.connect()

もしサーバーからエラーコード4xxとか5xxとか返ってきたらどうしよう。一応エラーコードが読み取れるなら以下の処理で対処可能

val statusCode: Int = connection.responseCode
if (statusCode != HttpURLConnection.HTTP_OK) {
    System.err.println("Connection faild. statusCode: " + statusCode)
    (return nullとか途中で処理を終了するようなコード)
}

でもエラーコードが返ってきた時点で、FileNotFoundExceptionなる例外が返る可能性があるみたいで、しかもそれはIOExceptionを継承したものなので、IOExceptionとして処理されてしまうかもしれない。うーん、知らん。

受信データの読み込みは次の節で解説するとして、いろいろ処理が終わったら接続を閉じる。

} catch (e: IOException) {
    e.printStackTrace()
}
finally {
    connection?.disconnect()
}

URLオブジェクトのopenConnectionメソッドで例外が発生した場合、変数connectionは初期値nullのまま放置されているはずなので、?.にすること。

参考
URL  |  Android Developers
Uri  |  Android Developers
URLを表す文字列を指定してURLオブジェクトを作成する – URLクラス – Swing
URLConnection(HttpURLConnection)と向き合おう~GETメソッドでデータを取得する~
URLConnection  |  Android Developers
HttpUrlConnection とエラーハンドリング – Qiita
HttpURLConnectionで嵌った話 | KATSUMI KOKUZAWA’S BLOG

InputStreamについて

無事接続が正しく行われたら、次はデータ読み込み。

val stream = connection.inputStream

これで、変数streamにサーバーからのレスポンスを読み取るためのInputStreamインスタンスがセットされる。

なんじゃそりゃと思わない人は読み飛ばし推奨。

Javaでは外部データをStreamという概念で扱う。InputStreamインスタンスが生成された時点でそれは「外部からアプリ内に入ってくる(Input) – データ(Stream)」を保持しているものと見なせる。オブジェクトの形をとっているけど、要はデータそのもの。

こいつに対して、一応データ読み出しをさせることはできるけどbyte単位。

stream.read() // 123
stream.read() // 10
stream.read() // 32
stream.read() // 32

readメソッドを繰り返すと勝手に次々読んでくれる。byte単位だけど。しかも出力は整数型。

なので、普通はリーダー(Reader)となるInputStreamReaderを被せてやる。

val inReader = InputStreamReader(stream)
val inReader = InputStreamReader(stream, "UTF-8") // 文字コードを指定するとき

これで文字ごとに読んでいくんだけど・・・

inReader.read() // 123
inReader.read() // 10
inReader.read() // 32
inReader.read() // 32

データが1byte文字(つまり半角)で書かれていたら結局一緒になる。もしデータが2byte文字(つまり全角)で書かれていたら2byteまとめて出力する。

ただ、こんなことは普通しないで、実際は一度にがばっと読み込んでメモリに溜めといてから必要な部分を取り出す。その役目をするのがBufferedReaderクラス。こいつでInputStreamReaderを包んでやる。

val reader = BufferedReader(inReader)

ここまでくると、readLineメソッドが使えるようになる。以下は一行ずつ読み込む処理。

var buffer: String
var line: String?
while (true) {
    line = reader.readLine()
    if (line == null) {
        break
    }
    buffer += line
}

ただ、これをやるとマズイ。というのもString型の変数は基本的に不変、つまり一度セットした値を変えることはできないので、bufferに変更があるたびに実は裏で新しい変数が作られる(ちょっと説明を端折ったけど大体そんな感じ)。もちろん変更前のものもそのまま放置。これだとメモリを食っちゃう(数万回やれば)。

どうするのかと言うと、StringBufferクラスを使う。これは可変長なString型と見なせる。最後に文字列を付けたしたい場合はappendメソッド、途中に文字列を追加したい場合はinsertメソッドを使う。

val buffer = StringBuffer()
var line: String?
while (true) {
    line = reader.readLine()
    if (line == null) {
        break
    }
    buffer.append(line)
}

ちなみに同様の働きをするStringBuilderというクラスもあって、StringBufferと同じメソッドで文字列を追加していく。違いはStringBuilderはシングルスレッド向き、StringBufferはマルチスレッド向きだそう。

今回はどちらでも良さそうな感じだけど、もしスレッドをまたいで参照される場合はStringBufferの方がいいのかな?

読み込みが終わったらBufferedReaderを閉じる。一番外側だけ閉じれば中も閉じられるみたい。

でもせっかくならfinallyの中に書いて確実に閉じられるようにする。例外が発生するかもだし。多分しないけど。

finally {
    connection?.disconnect()
    try {
        reader?.close()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

最後にdoInBackgroundメソッドの返り値として読み取ったデータを返しましょう。

return buffer.toString()

参考
ストリーム
InputStream  |  Android Developers
InputStreamReader  |  Android Developers
BufferedReader  |  Android Developers
【Java言語】StringBuilderの使い方とメリット | 侍エンジニア塾ブログ(Samurai Blog) – プログラミング入門者向けサイト
【5分で分かる】Java言語のStringBufferの使い方とStringBuilderとの違い | 侍エンジニア塾ブログ(Samurai Blog) – プログラミング入門者向けサイト
StringBuffer  |  Android Developers
Java:Reader/Writerにおけるclose()メソッド呼び出しの流儀: 愛ゆえにプログラムは美しい

JSONオブジェクト

やっとこさ読み込んだデータをゲット。中身は以下のJSON形式。

{
  "data": {
    "translations": [
      {
        "translatedText": "Hallo Welt",
        "detectedSourceLanguage": "en"
      }
    ]
  }
}

{ }で囲まれているものがオブジェクト、[ ]で囲まれているものは配列として取り出せる。

上記の内容が変数myResultに文字列として格納されているとする。

val parentJsonObj = JSONObject(myResult)

これで、上のJSONデータを丸ごとオブジェクトとして変数parentJsonObjに格納する。その後、dataと名前が付いたJSONオブジェクトを取り出すので、

val myData = parentJsonObj.getJSONObject("data")

次にtranslationsと名前の付いた配列を取り出すので

val myTranslations = myData.getJSONArray("translations")

その配列の中にはJSONオブジェクトが一つ格納されているので、1番目の要素を取り出すということで、

val myTranslation = myTranslations.getJSONObject(0)

さらに、そのJSONオブジェクトの中のtranslatedTextという名前の付いた文字列を取り出すので、

val myTranslatedText : String = myTranslation.getString("translatedText")

これを一気に書くと

val parentJsonObj = JSONObject(myResult)
val myTranslatedText : String =
    parentJsonObj.getJSONObject("data")
        .getJSONArray("translations")
        .getJSONObject(0)
        .getString("translatedText")

最後に、JSONオブジェクトや配列の取り出しに失敗するとJSONExceptionという例外が発生するので、try-catchの中に書く。

try {
    val parentJsonObj = JSONObject(myResult)
    val myTranslatedText : String =
        parentJsonObj.getJSONObject("data")
            .getJSONArray("translations")
            .getJSONObject(0)
            .getString("translatedText")

} catch (e: JSONException) {
    e.printStackTrace()
}

成し遂げたぜ。

参考
JSONで配列の入れ子構造や値の取得方法などをPythonを使って説明! | 侍エンジニア塾ブログ(Samurai Blog) – プログラミング入門者向けサイト
JSONObject  |  Android Developers
JSONArray  |  Android Developers
【Android(Java)】JSONデータをパースする方法 – TechBlog
AndroidでJSONの読み書き – Ararami Studio

全体の参照ページ

AndroidからAPIを叩いてJSON取って中身を表示させるまで – Qiita
Android Studioで天気情報を表示するアプリを作ってみた – RAKUS Developers Blog | ラクス エンジニアブログ