2016年6月4日

[esp8266]TLS/SSLの接続はできるが送信後に失敗したのは、私が悪かった

FirebaseのデータをPOSTするやり方がわかったので、あとはESP8266でやるだけだ。
TLS/SSLのサンプルをまねしてやってみよう。
NONOS SDK v1.5.3だ。


client handshake start.
client handshake ok!

うん、接続はうまくいっている。
が、espconn_secure_send()して、送信完了コールバックの後くらいにESP8266がエラーログを吐いている。

client's data invalid protocol
Error: SSL error 3

うーん。
ESP8266でたまに困るのだが、ESP8266自体がログを出すけれども、それが何なのかよくわからないことがしばしばある。
今回も「SSL error 3」と情報は出ているのだが、3が何なのかよくわからん。


ESP8266のTLS/SSLライブラリはaxTLS Embedded SSLのはずだから、ここを見ることにする。

「invalid protocol」で検索したが、出てくるのは「invalid protocol message」の方だった。
ということは、メッセージ自体はaxTLSではなく、アプリ側で出力させているということか。
一応、ライブラリの方も見ておこう。

$ find ./ -name "*.a" | xargs strings -f | grep  "invalid protocol"
./libssl.a: client's data invalid protocol
./libssl.a: server's data invalid protocol
./libssl.a: invalid protocol message

あー、これじゃわからんではないか。

 

では、もう片方のメッセージ「SSL error」で検索しよう。
こちらは、tls1.cで、ssl_display_error()に渡されたエラー値をマイナスしているだけだ。
SSL_OKなどと比較していることからすると、エラー値の3はこれか。

#define SSL_CLOSE_NOTIFY                        -3

クローズされたっていう通知で、その理由を示すものではないのね。。。


サーバに切断されて、その理由が「invalid protocol」ということか。
Handshakeはうまくいっているから、証明書とかの問題ではないと思うのだが。。。
まさか、TLSのバージョンとかか?
でも、それだとHandshakeがうまくいかないと思う。

一応Wiresharkで見ることにした。
Windowsだと見えないので、VirtualBox上で確認。。。
ネットの情報を見ながらやってみたが、そんなに悪くない気がする。

curlに、--tlsv1.1を付けても成功するし、--tlsv1.0でもいけた。
Cipher SuiteもESP8266で対応しているTLS_RSA_WITH_AES_256_CBC_SHAを指定したが、ちゃんと動いた。

curl --tlsv1.0 --ciphers AES256-SHA -X POST -d '{"test1":{"now":{".sv":"timestamp"},"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json?auth=ごにょごにょ'

まあ、Handshakeできてるからねぇ。


ここまでやっておいてなんだが、JSONとしてPOSTしているつもりなのだが、ヘッダが足りないとか、単純なところでサーバがはじいているだけじゃなかろうか?

あ、Content-Lengthを指定してないや。。。

 

POST /rest/saving-data/test.json?auth=ごにょごにょ HTTP/1.1
Host: xxxx.firebaseio.com
Accept: */*
Content-Type: application/json
Content-Length: 68

{"test1":{"now":{".sv":"timestamp"},"name":"hiro","date":"06/03"}}

はい、OKです。。。


今回の反省

  • もっと自分を疑ってよい!

 

以上でございます。

まあ、ESP8266がSSLエラー3を出してきたときはサーバ側で切断されているから、送信しているフォーマットが間違っていないかどうかを確認するといいんじゃないの、というところかね。

 

ソースは、こんなのです。
hirokuma/esp8266_firebase_post: ESP8266 NONSDK v1.5.3 Firebase POST

Firebaseをcurlであとちょっとだけ試す

あとちょっとだけ、ね。

 

認証有りにしたいのだ。

authentication - Firebase server side auth with HTTP POST request using the firebase secret - Stack Overflow

 

ああ、jsonの後ろに「?auth=認証シークレットID」を付けるだけか。

うん、できた。


しかしこれ、全部のセンサー側が同じ認証シークレットIDでよいものだろうか?
1つ解析されてしまったらおしまいではないか。
クエリー文字列って、平文で渡されそうな気がするので心配だ。

 

まずは、クエリー文字列はどうやって渡すのか調べておこう。

GET /index.html?query=xxx HTTP/1.1\r\n
Host:www.yahoo.co.jp\r\n
....

HTTPの場合は、普通に載せればいいようだ。
FirebaseはHTTPSだから。。。どうなるんだ?

SSLでHTTPメッセージはどの部分が暗号化されるの? - QA@IT
ヘッダも暗号化されるらしい。
セッション層の暗号化だから、それより上側にあるアプリケーション層のHTTPは暗号化されるということか。

よかったよかった。

2016年6月3日

Firebaseをcurlでもう少しだけ試す

つらつらと考えていたが、こっち側(センサ側)はデータをアップするだけだろう。
だから、クエリーの方法はサーバのことをやる気分になったときでいいや。

なので、もう少しだけデータのアップ方法を見ておこう。
私にとってサーバを立てるというのは、それはもう大ごとなので、簡単にやってもらえるのであればもうちょっと使ってみたいのだ。


Saving Data - Firebase

  • PUT
    • データの置き換え
  • PATCH
    • データの一部更新
  • POST
    • リストやデータの追加
    • クライアントは「messages/users/<unique-id>/<data>」のようなユニークIDを生成する
  • DELETE
    • 消す

 

PUTは、前回試したやつだ。
PATCHは、updateとあるから、一部だけ変えるのだろう。
DELETEも試したので、消すだけだとわかる。
問題はPOSTだ。

リストを保存する例だけ見ると、PUTがPOSTになっただけだ。
しかし、戻ってくるデータにユニークIDが含まれている。


やってみよう。

$ curl -X POST -d '{"test1":{"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json'
{"name":"-KJLSkVZbAkcX4hMjxEd"}

image

うん。
PUTしたときはtestの下にtest1があったけど、これはユニークIDが挟まっているな。
push IDと書いてあるけど、どう使えばよいのだ?
素直に考えると、階層が深くなるだけなのだが。。。

$ curl -X POST -d '{"test2":
{"name":"yoko","date":"11/22"}}' 'https://xxxx.firebaseio.com/rest/saving-data/-KJLSkVZbAkcX4hMjxEd/test.json'

{"name":"-KJLUx5Rr_yCiIadZVLi"}

image

ああ・・・そうなるのか・・・。
そうなるよな。

では、何も考えずに同じPOSTを2回やってみよう。

$ curl -X POST -d '{"test1":{"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json'
{"name":"-KJLVqUQNYqMzWKrwGp_"}
$ curl -X POST -d '{"test1":{"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json'
{"name":"-KJLVyLX0T7_y-8dMvBM"}

image

まあ、ユニークIDは毎回発行されるから、こうなるわな。


このIDは、時間情報があるわけでも無いと思うので、サーバ上には別々の場所に保存されてありがたいのだが、到着した順番はわからんだろう。

となると、どういう風に使う目的なのだ?
JavaScriptでは、これをpush()というAPIでやるらしいから、何かPUSH系のメッセージという扱いのように見えるのだが。

 

今頃知ったのだが、このFirebaseって今年のGoogle I/Oで発表されたばかりなんだな。
新しいドキュメントはGoogleで、前のドキュメントは買収前ということか。
Google I/O 2016 で「Firebase」の新バージョンが発表!プッシュ通知機能を iOS アプリで使ってみた | Developers.IO
ということで、プッシュ通知というのも売りとなる機能らしい。

iOSのNotificationとか出てくるから、これはスマホにとってのプッシュ通知用ということか。
そこは、もう少しスマホ側のアプリに慣れてからにしよう。


".sv"というキーを使うと、サーバ側の値が使えるらしい。
今はtimestampだけのようだが、これはありがたい!

 

$ curl -X POST -d '{"test1":{"name":"hiro","date":"06/03",".sv":"timestamp"}}' 'https://xxxx.firebaseio.com/rest/saving-data/test.json'
{"name":"-KJLZhC49FhjrRANKHdq"}

image

 

えー、他の項目と一緒にやってくれないのかい。。。
これならどうだ?

 

$ curl -X POST -d '{"test1":{"now":{".sv":"timestamp"},"name":"hiro","date":"06/03"}}' 'https://xxxx.firebaseio.c
om/rest/saving-data/test.json'

{"name":"-KJL_u_efy7lsP-O_Hxy"}~$

image

やれやれだぜ。

センサー側がどういう時間で思っていようと、サーバ側が受け取った時間で管理すればよい場合が多いから、これでよいだろう。
そうすると、RTCとかなくてもいいしね。
まあ、これを使うとなるとHTTPアクセスするくらいだから、NTPとかやってしまえばいいのだけど、時間みたいな繊細な情報は扱いたくないのだよ。

 

ともかく、こういう時間情報が値に含まれることで、ユニークIDがどうであっても時系列は把握できるだろう。
あとは、自分が誰か、というセンサーIDみたいな値があれば、クエリーで引っ張ってきやすいんじゃなかろうか。

Firebaseをcurlで少しだけ試す

Googleに統合されたFirebaseを使ってIoTとスマホの連携を実践する ~ BRILLIANTSERVICE TECHNICAL BLOG

 

よくわからんが、ESP8266でもできるらしい!
と思ってやってみようかと思ったが、Arduino版か・・・。

curlでやれるようなので、少しだけ試してみよう。


上記の記事を見ながら、「プロジェクト登録」と「FirebaseのプロジェクトURLと認証シークレットIDを取得」までやっておく。

 

まず、データの書き込みをやってみる。
が、認証がいるらしい。。。
めんどうなので、認証無しでやれるようにしておく。
テストだからそうしているけど、危ないだろうね。。。

image

FirebaseのRulesを理解する - Qiita
理解はできてないのだけど、"true"にすると認証が不要ということはわかった。

試しに、同じページにあるシミュレータでやってみると、認証済みをオフにしていても実行は成功する。


Saving Data - Firebase

データをサーバに保存してもらうにはいくつか方法があるらしいが、PUTが簡単そうなのでそれをやってみよう。

 

$ curl -X PUT -d '{"test1":{"name":"hiro","date":"06/03"}}' 'https://XXXXXXXX.firebaseio.com/rest/saving-data/test.json'
{"test1":{"date":"06/03","name":"hiro"}}

 

URLのところは、上記で取得したプロジェクトURLというやつになる。
ブラウザで見ると、こうなった。

 

image

JSONのところは、整形するとこうなる。

{
  "test1":{
    "name": "hiro",
    "date": "06/03"
  }
}

test.jsonというファイル名として保存されたということかな?

もう一度、JSON部分のパラメータだけ変更して実行すると、test.jsonは上書きされていた。
なので、追加したかったら既存のに足すか、JSON名を変えるのだろう。


Retrieving Data - Firebase

こっちは読む方だろう。

$ curl 'https://XXXXXX.firebaseio.com/rest/saving-data/test.json?print=pretty'
{
  "test1" : {
    "date" : "06/03",
    "name" : "hiro"
  }
}

orderByを試そうとしたが、Indexが定義されていない、というエラーが返ってくる。
Indexがいるとか何とか。


ドキュメントは、こっちが新しいようだ。
でも、古い方がREST APIなんかは探しやすいかも。
Firebase Database REST API  |  Firebase

 

では、最後に削除して終わろう。
サーバ側のrulesも忘れず元に戻しておこう。

$ curl -X DELETE 'https://XXX.firebaseio.com/rest/saving-data/test.json'
null

[android]ダイアログにListViewを載せよう - 後編

support.v4とかsupport.v7の数字はAPIバージョンかと思っていたのだが、履歴を見るとv7にAPI19以下向けが追加されていたので、言い切れないと思った。

Support Library Revision History | Android Developers

 

試しに全部外してみたのだが、Android 6.0以降で必要となる位置情報許可の確認で使うAPIが、v4かAPI23以降しか無さそうだった。
そう考えると、この数字は「APIレベルXX以降」ではあるのだが、XX未満というのがわからないのだな。
ActivityCompat.checkSelfPermission()も、API4以降だったら使う機会があるということなのか。

SDKのMINを設定して、それでもコンパイルが通るならsupportは使わなくてもよいと考えて良い気がする。


ダイアログの表示はできるようになったので、次はリストの表示だ。
リストを追加する

データが固定の場合は、こういうイメージでよいのかな。

image

データの見せ方はAdapterViewから派生したListViewなどが制御する。
AdapterViewが扱うデータはAdapterから派生したArrayAdapter<T>などが持つ。
まあ、持つといっても、実際はnewするときに引数で配列を渡すくらいだろう。

DialogFragmentの場合は、AdapterViewに相当する部分はDialogFragment側が準備してくれるので、データを準備してsetSingleChoiceItems()などで渡してやるとよい。

内容が可変でないならば、string-arrayで準備するのもよいだろう。


さて、内容が可変で、動的に更新したい場合はどうだろうか。

幸い、BDS Clientの生成ソースは通常のListViewを動的に更新するようになっているので、まねをするとよいと思われる。

BaseAdapterをextendsしたclassをまるまる持ってきた。
これのインスタンスをsetSingleChoiceItems()に設定すると、とりあえず動いた。
image
接続の実装は、次回だ。


あとは、画面外をタップしたらダイアログが閉じるのだけど、そのときにScanを停止させたい。

builderにsetOnCancelListener()というのがあったのでOnCancelListenerをnewしてみたのだが、ダイアログ表示時に落ちてしまった。
ドキュメントを読むと、onDismiss()をオーバーライドすると呼ばれるそうだ。
適当にやっちゃいかんな。

public void onDismiss(DialogInterface dialog);
うん、呼ばれた。

ここまでをコミットした。
サンプルそのままの箇所も多いが、数日前よりはましになったんじゃないかね。
hirokuma/AndroidTwoSpinners at 6136b8111d9978b800b4a68d1d90d3775cc1b42a

[android]ダイアログにListViewを載せよう - 前編

前回、BLE Peripheralを接続するにはユーザに選択してもらうのが一番よさそうだ、という結論に至った。
BDS ClientではListViewにデバイス名が並ぶようになっているのだが、

  • 文字を表示する
  • 何台あるかわからない
  • ユーザが選択できる

という条件になると、ListView形式くらいしか思いつかない。

もし、Peripheralの位置がはっきりわかるのであれば、地図風に表示させてタップするというやり方もあるだろうが、BLEだと位置も方向も距離もはっきりしない。

Spinnerも近いものがあるのだけど、台数が後から増えるので、期待するデバイス名があるかどうかを毎回閉じて開いてで確認するのは使いづらかろう。

Buttonが増えていってもいいけど、ボタンはあまり動的に増えたり減ったりするのはよくないように思うのだ。
物理的なボタンがそうだからだろうか?

もう、ListViewでいいよね?となってしまう。
ListViewにするのはよいとして、なるべく既存のActivityを変更せずに使えるようにしたい。
理由は、その部分だけコピーして使い回したいからだ。
Scanの開始までを独立させるのには成功していないのだが、そこまでであれば私でもコピーで対応できそうな気がする。
でも、既存のUIを変更させてListViewを追加するのは難しそうだから、ダイアログにして上にかぶせてしまえば、UIはそのままでいけるんじゃなかろうか。


古い記憶だが、Androidのダイアログは生成方法が途中で変わったような気がする。
きっと、今も変わり続けているに違いない。

ダイアログ | Android Developers
すごい、本家で日本語ページがある!
以前は日本語ページは情報が古かったので見ていなかったのだけど、自動で日本語ページが表示されているから、更新されるようになったのだろう(以前は、表示言語の選択ができた気がする)。
時代は変わっているんだねぇ。

 

デザインのガイドラインはこちらを読め、とのことだった。
Dialogs - Components - Google design guidelines
長い。。。
目次はこうなっている。

  • Behavior
  • Alerts
  • Simple menus
  • Simple dialogs
  • Confirmation dialogs
  • Full-screen dialogs
  • Specs

最後のSpcsは、ダイアログ自体の仕様みたい。

最初のBehaviorは、ダイアログはこういうものですよ、という振る舞いの説明だろうか。

Alerts、Simple menus、Simple dialogs、Confirmation dialogsは「ダイアログタイプ」のようだ。

そして、Full-screen dialogsはモバイルのみと書かれている。モバイル以外ってなんだろう?
うちのNexus7でも表示されているし。でも、Nexus7はモバイル扱いかもしれない。
Pixel Cとかだと除外されるのかな。

 

今回のイメージは、Confimation dialogsになるだろうか。
ユーザの明示的な確認を行うことになるからだ。
しかし、これだとOK/Cancelボタンを押さないといけなくなるみたいだ。
戻るボタンやCancelの場合は何もせずにダイアログを閉じるように、と。

デバイス名をタップするだけじゃなくて、ラジオボタン式にするのか。。。
うん、間違って触ってしまって接続するよりはよいのかな。


ダイアログのガイドラインを読んで、イメージが沸いたので、実装していこう。

Android Developerのページを読むと、AlertDialog+DialogFragmentを使う説明が書かれているから、それがよいのだろう。

Fragmentというと、ハードディスクとかのフラグメンテーションを思い起こしてしまうのだが、Androidでの意味は「小部分」のようなイメージみたい。
フラグメント | Android Developers
挙動を小部分に分けて、組み合わせて作り上げる、ということができるようだ。
その小部分は、Activityに由来したものを指すらしいので、UIに関する小部分、になるのかな。

試しに、サンプルソースをそのまま組み込んでみた。
Android Studioが、AlertDialogで「どっちにする?」と聞いてくるので、v7とついている方にしてみた。
classicとか書いてあるので、古いバージョンでも使えるようにとか、そういう意味なんじゃなかろうか。

今回は、Android5.0(API21)を下限にしている。
Windows版のAndroid Studioってバージョン表示が切れててわからんぞ。

image

名前だけPeripheralSelectDialogFragmentにして、まねしてみる。

DialogFragment dlg = new PeripheralSelectDialogFragment();
dlg.show(getSupportFragmentManager(), "fire");

Android Studioに怒られる。。。

Cannot resolve method 'show(android.support.v4.app.FragmentManager, java.lang.String)'

このgetSupportFragmentManager()で取得できるのが、v4ということなのだろう。
show()が期待するのは、FragmentManagerかFragmentTransactionらしい。
getSupportFragmentManager()はFragmentManagerを返すようになっているのだが、これはv4のFragmentManagerなのだ。
ということは、DialogFragmentもv4にしてやらないかんということか。

import android.support.v4.app.DialogFragment;
こうしたら直った。
この、v4とかv7とかが、あまりわかってないな。
v4, v7, v8, v13, v14, v17まであるみたいだ。
このバージョンは、APIレベルに合わせてあるように思うのだが。。。
はっ!
今回はAPI21以上と決めているから、v4とかを選んでいた時点で間違っていたのか!
 
とりあえず、ダイアログは表示されているからよしとしよう。

image

 

長くなったので、ここで区切ろう。

2016年6月2日

[android]BLEデバイスを指定せずに接続するのは無理だ

極力Bluetooth Developer StudioのAndroid Clientで生成されたソースファイルを使って、とりあえずBLEのスキャンだけ行えるようにした。

hirokuma/AndroidTwoSpinners at c3993a58d49eafd8b213014e4f4566f41509cd02

namespaceがcom.blogpost.hiro99maの直下だが、そこはいずれ整理しよう。
どうでもよいが、Package名はこのブログのURLとはちょっと違っている。
blogspotではなく、blogpostだ。
Bloggerに移動してから初めてAndroidアプリを作るときに打ち間違えて以来、そのままにしている。
合わせた方がよいのかな?

まだScanした結果は、デバイス名をlogcatに出力させるだけになっている。
タイムアウトも作っていないので、ひたすらスキャンしまくるので、電池の持ちは悪いだろう。

 

それはともかくとして、私はこれからどうするつもりなのだろう?
ペアリングしているわけでもないから、誰かを選んで接続しなくてはならない。
が、ListViewみたいなものもないので、選択することができないのだ。
最初に見つかったデバイスを接続する、だと、あまりにも狂犬過ぎると思う。

なので、もっとエレガントな方法がいるのだけど、そうなるとデバイス名の一覧を作ってユーザに選んでもらうしかない。
一覧を出すとなると、画像があるわけでもないので、リストになるだろう。
そしたら、ListView形式になるのは必然であった。。。

 

まあ百歩譲って、Peripheralを自分で作り、サービスも独自で、サービスのUUIDもAdvertisingしていて、それでフィルタし、デバイスが1台しかないのがわかっていれば、最初に見つけたデバイスに接続する、でもよいかもしれない。

その場合は、ScanFilterFactory.javaにフィルタする設定を実装することになる。
BDSで、GAPの設定にデバイス名を記載すると、自動的にデバイス名でフィルタするようになるのだ。
ScanFilterを使っているだけなので、そこの書き方を変更すれば、他のフィルタ方法も可能だと思う。
フィルタできるのは、以下だ。

  • フィルタ無し
  • デバイス名
  • デバイスアドレス
  • Manufacturer Data
  • Service Data
  • Service UUID

使い方はテストアプリを見るのがよさそうだ。

 

Advertisingするデータは、本編とScanRspをそれぞれ使ったとしても31バイト×2なので、そんなにたくさんのデータを入れることができない。
最近は、DeviceNameと128bitのService UUIDを流すのがよいかな、と思っている。
大きい会社だったら16bitのUUIDも取得できるかもしれないけど、うちは無理だからManufacturer Dataは使えないし。

IncompleteとCompleteのどっちがよいかと言われると、よくわからん。
Generic Accessみたいな標準サービスは載せなくてもいいような気がするが、「完全」を問われると自信がなくなる。
厳密さを問われるものでもない気がするので、メインのサービスが1つだけであればCompleteでいいんじゃないの?と個人的には思っている。
どうせ独自だから、相互接続性などきにしなくていいしね。

 

話がいろいろ脱線したが、ペアリングしてなくて特定できない機器の場合は、一覧を出してユーザに選ばせるのが一番よさそうだ、というお話でした。

[android]layout weightを空欄にしても収まるようになったが、そもそも再現しない

BLE PeripheralをScanして、デバイス名を出力させようと思う。
Bluetooth Developer StudioでAndroidのClientソースを吐かせるとListViewになるのだが、勉強を兼ねているのでそれは使わず、ログ出力っぽくだらだらとテキストとして流そうと思う。

Scanボタンの下に、layout heightをmatch_parentにしたTextViewを置いてみた。

ああ!はみでる!

image

こちらにあるように、layout weightを1にすると、収まった。
Androidのレイアウトで画面いっぱいに表示したい時の設定 « Androidアプリ覚え書き

image

 

でも、画面からはみ出ても仕方ないから、デフォルトでlayout weightは1でもいいんじゃないの?と思い、その上にあるScanボタンのlayout weightを1にした。
そしたら、高さがなくなった。
???
「余っている領域を重み付けが高い順に幅を取っていく」ということで、他の部品は0にしているから、このButtonとTextViewで2分すると思っていたのだが・・・。
Buttonのlayout heightがwrap_contentだからかと思ったが、match_parentにしても変わらない。
うーむ。。。


ごちゃごちゃいじっていたら、今度はweightを空欄にしてもTextViewが画面内に収まるようになってしまった。。。
いつの間にかLinearLayoutにweightSumというパラメータが追加されて1.0になっていたから、そのせいかとも思ったのだが関係なさそうだ。
部品を消して追加し直したのだが、最初のはみ出る現象自体が起きない。

 

AndroidStudioのWindows版は比較的バグが多めだという話もあったし、自分もスキルが全然ないしで、もはや何を疑っていいのかがわからぬ。。。

[nrf51]Bluetooth Developer Studioサンプル(nRF51822+S130)

AndroidやらnRF51やらESP8266やら、あれこれやっているように見えるが、やりたいのは対抗機になってくれるAndroidアプリを作れるようになろう、ということなのだ。

つくっているのがAndroidのテスト用アプリになっているのだが、それはデバイスを使った題材が思いつかないので、まずは通信するだけのしくみを自作したいだけである。

 

相手になってもらうnRF51822のサンプルがなかったので、適当に作った。
SDKがnRF51からnRF5になる前しかなかったので、また作り直した。

hirokuma/nrf51822v11_bds_sample: nRF5 SDK v11.0.0 Bluetooth Developer Studio sample

とうとうS130だ。
中身はまったく見ておらず、テンプレートにBDSが生成したソースを突っ込んで、Makefileを書き直した程度。
それでも動いてくれるのだから、Serviceの部分は作るだけだったらお仕事にならないですな。。。

まあ、その辺りはできる前提で、あとはどう周辺機器を動かすか、とかが腕の見せ所ですかね。

 

さて、相手はできたので、これにアクセスするAndroidアプリを作りましょうかね。

[android]Android Studioでプロジェクトを別のところで使う

いま、TwoSpinnersという、スピナーを2つ使ったAPIテスト用のプロジェクトを作っている。
実際に使うとしたら、これを別名でコピーして、Package名なんかも変更することだろう。

AndroidStudio 2.1.1(Windows10)でやってみる。


まず、コピー元のプロジェクトで、以下を削除しておく。

  • .gradle
  • build
  • app/build

.gradleは移動した後の方がよいのかな?
buildたちは、ファイルがたくさん入っているので、邪魔なだけだ。
あと、このあとで全検索するので、不要なファイルがあると邪魔なのだ。

 

別の場所にコピーして、フォルダ名を変更する。
ついでに、直下にあるxxx.imlファイルを削除する。
リネームしてしまえばよいのだろうが、.gitignoreにimlファイルが入っているので、きっと自動生成してくれるはず。

あと、忘れやすいけど、バージョン管理しているのなら管理ファイル(Gitなら.git)も削除しておこう。
隠し属性になっていて気付きにくいけどね。

 

フォルダ以下を、変更前の名前で全文検索する。
今回であれば「TwoSpinners」だ。
検索対象は「*.*」。全部だ。
今のところ、私の環境ではこれらのファイルが引っかかった。

  • .idea/.name
  • .idea/modules.xml
  • .idea/workspace.xml
  • app/app.iml
  • app/src/main/res/values/strings.xml

問題が無さそうであれば、新しい名前で置換していく。

app.imlは、同じルールで行けば自動生成してくれるはずだが、もう検索できてしまったので変更することにした。

 

さあ、Android Studioで開いてみよう!
・・・Unsupported Modules Detectedというエラーが出てきた。
が、Syncが終わるまで待っても、それ自体はエラーにならなかった。
プロジェクトを閉じて開き直すと表示されなかったので、たぶんimlファイルを削除していたせいじゃないだろうか。


あとは、Package名の変更。
名前だけならよいのだが、フォルダ階層の変更も発生するので、Refactor>Renameでは対応できなさそうだ。

ネットで検索すると、Refactor>Moveがよいらしい。
やってみると・・・、なるほど、これは「移動」だ。
フォルダがなければ作成してくれるし、参照しているPackage名なんかもRefactorするか確認してくれて、変更もするようなのだが、移動なのだ。

たとえば、今回だと「com.blogpost.hiro99ma.twospinners」というPackage名で、移動先を「jp.blogpost.hirokuma」とすると、全体としては「jp.blogpost.hirokuma.twospinners」というPackage名になる。
つまり、twospinnersを「jp.blogpost.hirokuma」に移動したのだ。

よって、一番最後のNamespace名(でよいのかな?)を変更したいのであれば、さらにRefactor>Renameをするとよいだろう。


ツールに全部任せるのは心配な気もするのだが、私くらいのAndroid/Javaスキルだったらツールでやってもらった方が間違いが少ないだろう。

いつかは「そんなのツールでやるより自分でやった方が安心だよ」と言えるくらいまでなりたいものだ。