ランタイムパーミッションの対応をするとActivityがマッチョになるのなんとかならんかな
Android6.0からアプリのインストール時ではなく、パーミッションを必要となる操作を行う前に許可を求める形式になった。これ自体はとてもよいこと。インストール時にやたら権限を要求するアプリがなんだか怖くてインストールすらしなかったのが、これのおかげでとりあえず試して見れるようになったのはとてもよい。
Android6.0からアプリのインストール時ではなく、パーミッションを必要となる操作を行う前に許可を求める形式になった。これ自体はとてもよいこと。インストール時にやたら権限を要求するアプリがなんだか怖くてインストールすらしなかったのが、これのおかげでとりあえず試して見れるようになったのはとてもよい。
Android Wearのアプリを作成する際には、パッケージ名をスマホ側と同じにしなければなりません。
新規プロジェクトで初めからWear用のアプリも含めて開発する際は関係ない話です。Android Studioの新規プロジェクトウィザードでターゲットにWearを追加するだけなので、両者のパッケージ名を別のものにしようと思ってもできません。
気をつけなければならないのは、スマホ用のアプリを作成していて、途中からWear用のモジュールを追加する場合です。「Wear用のモジュールだからパッケージ名変えたほうがいいかな」なんて気を利かせると逆にハマります(私のように)。
(ちなみにここで言っているパッケージ名は、アプリのパッケージ名のことです)
開発時はWear用のモジュールを実行、スマホ用のモジュールを実行という感じでアプリをインストールして実行できるので問題に気づきにくいのですが、Wearとスマホで通信を行おうとした際にハマります。
具体的にはCapability APIを使って通信可能なノードを取得する際に、パッケージ名が違うとそのノードが取得できません。
https://stackoverflow.com/questions/35136881/cant-detect-node-using-the-capability-api
Capability APIはWearからの要求に応答可能なデバイスを探すための仕組みです。例えば自分で 「say_helloという要求を行う」と定義しておけば、Wearからメッセージを送信する際にsay_helloが定義されているデバイスだけを、Capability APIを使うことによって取得することが出来るのです。しかし、それはパッケージ名が同じアプリであればの話です。
ちなみにNode APIを使えばパッケージ名が異なっていようが関係なく、ペアリングされているノードが取得できます。パッケージ名が異なると接続されているノードが取得できないのはCapability APIを使った場合の話のようです。
Wearアプリを作成する場合、パッケージ名を変えることは基本的にはないと思いますが、Wearのパッケージ名とスマホのパッケージ名を異なるものにすると弊害があるぞというお話でした。特に後からWear用のパッケージを追加するときには気をつけましょう。
Android WearデバイスをBluetooth経由でデバッグする方法について。
Android WearもUSB経由でパソコンに接続してデバッグする方が何かと便利です。ですが、USB経由で接続しようと思うと、Wearデバイスに直接USBケーブルをつなぐタイプのものなら問題無いでしょうが、クレードル経由で接続するタイプの製品だと腕につけた状態でデバッグできません。
そんなときはBluetooth経由でデバッグすると便利です。
https://developer.android.com/training/wearables/apps/bt-debugging.html
Bluetooth経由でのデバッグを有効化しておく必要があります。
まず設定→端末情報→ビルド番号を7回タップして開発者オプションを有効にします。
すると開発者オプションを選択できるようになるので、そこからADBデバッグとBluetooth経由でデバッグを有効にします。
以上でWear側の事前準備はOK。
Android Wearとペアリングしているスマホ側でもBluetooth経由のデバッグを有効化してやる必要があります。
Android Wear companion app(日本語だと単にAndroid Wear)を実行します。Android Wearとのペアリングしたりするアプリです。
使いたいAndroid Wearデバイスとのペアリングした状態で、Appbarにある歯車アイコンを押します。
すると設定画面が開くので、その一番下にあるBluetooth経由のデバッグを有効にしてやります。
ホスト:未接続、ターゲット:接続済みとなっていると思います。
それができたら次のステップ。
以下のコマンドを実行。
adb forward tcp:4444 localabstract:/adb-hub
adb connect localhost:4444
adb connect localhost:4444でConnection Refuesedとなってしまう場合、localhostの部分を127.0.0.1とすれば接続できると思います。
接続できればスマホで「ホスト:未接続」となっていた部分が「ホスト:接続済み」となると思います。
そうすればAndroid StudioからAndroid Wearデバイスが見えるようになっていると思います。
adb disconnect 127.0.0.1:4444
adb forward —remove tcp:4444
ちなみにポートフォワーディングしているかどうかを確認するにはadb forward —listで確認可能。
私はLG G watch Rを持っていたのだけれども、長いこと使っていなかった。普段腕時計をしないし、するにしても薄くて軽いやつをずっと使っていたので、スマートウォッチの大きさが我慢できず、そのうちつけるのをやめた。
そんな放置していたスマートウォッチを久しぶりに使ってみた。
Androidアプリを書くのに、最近私はいっさいfindViewByIdを書かない。簡単なサンプルでも必ずDataBindingを使って書いている。
Application Contextを渡してはいけないという記事を読みました。
https://ytrino.hatenablog.com/entry/2016/05/26/033936
この記事を読んで改めて自分のContextに対する認識があまいことを実感したので、思ったことをまとめてみようと思います。
趣旨は「僕はContextに対してこういうふうに思ってるんだけど、違う・違わないが自信持ててないから誰か突っ込んでくれ」です。たぶんそんなに間違ってないと思うんですけど、相談できるような相手がいないんだ、察しておくれ。
「私もそういう認識です」と言ってもらえれば自信になるし、違う部分があれば指摘をもらって学びの機会になり私のためになります。また、こういう変遷を経て学んできたという情報が初学者の役に立てばなぁなんて思ってもいます。
私のAndroidレベルは初学者というわけでもないですが、Contextにまつわる話を聞くと理解が曖昧でモヤモヤするレベルです。上記記事に倣えばAndroid2級でしょうか。
私は最近でこそライフサイクル考えてContextを使い分けるようになりましたが、ちょっと前まで「困ったらApplication Context渡しとけば問題ない」という考えの人でした。前までというか、今もそういう部分があるんですけどね。だからこそ「Application Contextを渡してはいけない」というのを見て不安になってしまいました。
Application Contextを渡してはいけないというのは、どういう意味なのでしょうか。
実はApplication Contextをなるべく使おうというのも、Application Contextを渡してはいけないというのも、あるコンテキストにおいては間違ってないんじゃないかなと思います。それぞれ違うコンテキストで話しているので、そこを混同すると混乱してしまいます。
前者は「メモリリークを回避する」という文脈での話で、Activityのリークを防ぐにはApplication Contextを使えばいいのは正しい(はず)です。
一方後者はthemeの適用の観点での話で、Application Contextでは意図した動きにならない・変にハマることがあるからApplication Contextを安易に使ってはいけないという話で、これはこれで正しいわけです。
どっちも正しいのですが、残念ながらコードを書いていてContextを要求された時に、私たちは何らかのContextを渡さないと先に勧めません。何を渡せばいいかは「ケースバイケースなんで一概にどれを使えとは言えない」んで、Contextの要求に対しては、それぞれ適切なContextを渡す必要があります。
ではどのようにしたら、何を使うのが適切かを判断できるのでしょうか。ライフサイクルを考えろということでしょうか。そのライフサイクルはどうやって判断するんでしょうか。
私はその判断を、今はソースコードを読むことで行っています。Contextを要求するメソッドが、クラスが、いったい渡されたContextをどのように利用しているのか。それをもって何を渡すか考えています。
Androidを学び始めた当初は、Contextなんて意識していませんでした。本やサンプルコードの写経する分には意識しないですみますから。
しかし写経から脱し、自分でコードを書き始めるとそうもいきません。こういう処理を実装したいな→このメソッド使えばなんとかなりそう→Contextが必要らしい。はたとキーを打つ手が止まります。
そのメソッドを使うにはContextを渡すしかない。渡さないと先に進まない。とりあえずthisって書いたら動いた。よし、これで先へ進める。なんていうのが、Androidを学び始めた人が辿る道ではないでしょうか。私はそうでした。
学び始めの頃はActivityからなんかすることが多いので、this、すなわちActivityを渡していればとりあえずは動いてくれました。私はそうして「ああ、ContextっていうのはActivityを渡せばいいのだな」と認識するようになりました。
しかしActivity Contextを渡してしまうとメモリリークが発生するぞという話を耳にします。そこで登場するのがApplication Context。getApplicationContext()を使えばメモリリーク対策になるという話を聞いて、「ああ、じゃあとりあえずApplication Contextを渡しておけば問題ないのか」となりました。
しかし今度はApplication Contextを渡すと想定通りにViewが表示されないことがあるという。無思考にApplication Contextを渡すのもどうやらダメらしい。
一周回ってきてしまいました。もう一体どうしろと。このメソッドContext要求してくるのに僕は一体何を渡せばいいんだ。
そんな私が多少なりとも指針を持てるようになったのは、一言で言ってしまえば慣れてきたからなんだと思います。
とりあえずコード書いて、動かしてみて。それを繰り返すうちに慣れてきて。ソースコードを読むようになって「多分こうやれば大きく間違ってはいないはず」という、そんな指針を持てるようになりました。
Contextを要求されたら何を渡すか。重要なのは考え方、つきあい方だと思います。これが今回の記事の本題です。
私はまずライフサイクルを考えるより前に、Contextを要求するメソッドのソースコードを確認しに行きます。そいつがContextへの参照を保持するのか、それともContextを使ってなんかするだけなのか。それを確認するのです。
Contextへの参照を保持するなら、Activity Contextを渡していいのか、ライフサイクルを考えなければなりません。ライフサイクルを考えるというよりは、Activityとともに役目を終えるのか、そうでないのかで判断していますが、正直このあたりは曖昧です。
該当のメソッドが単にStringリソースを読みだすのに使っているとかだったら、別にActivity Contextを使っても問題ないですよね。そもそもActivityがリークするのは、Activityへの参照を持ち、渡した先のオブジェクトがActivityより長生きする場合だからです。
Contextへの参照を持たないのであれば、別にどっちを使おうが問題ないので、好きな方使えばいいじゃんって感じですが、Activityから呼び出すのであれば、わざわざApplication Context渡すのも大げさなので、Activity Contextでいいかと思います。
Androidと付き合っていくうちに、なんとくこんな感じに行き着きました。それでもメモリリークが怖いなら、Contextに何を使えばいいか迷うよりも、それでメモリリークするのかを確認する方に力を注いだほうが良いと思います。
でも、独学でやってるとこれで本当に問題ないのか確証が持てないので、いつもどこかで不安です。誰かに聞く機会もなくてつらい。
そもそもContextについて意識し始めるキッカケは、私の場合はActivityのメモリリークです。
今回の記事のサンプルコードは、GitHubで公開しています。
お絵かきアプリを作ろうと思って格闘中です。とりあえず線を描くだけでも学びがいろいろあったのでまとめておこうと思います。
線を描くにはPathを使うのがオーソドックスのようですが、何も考えずにパスを使った描画を行うと、線がカクカクしてしまいます。(サンプルコードのPathPaintView)
path.lineTo(e.getX(), e.getY());
drawCanvas.drawPath(path, paint);

これはなぜ起こるのでしょうか。
その理由はまず線をPathではなく点で描画してみると分かります。(DotPaintView)
drawCanvas.drawPoint(e.getX(), e.getY(), paint);

描画される点がまばらになっています。このドットはonTouch()が呼ばれるタイミングで描画されています。このドットの間隔がタッチイベントがViewに伝えられているタイミングだということです。これはスクリーンをタッチした情報が、逐一間断なくonTouch()に渡されているわけではないことを意味しています。
ではドットとドットの間のタッチイベントの情報は失われているのかというと、決してそうではありません。onTouchに渡されるMotionEventには、MotionEventが配信されていない時に生じた座標を保持しています。
その情報はMotionEvent.getHistoricalX()などで取得することができます。これを利用すれば、MotionEventの情報をより精細に取得することができます。(HistoricalDotPaintView)
int history = e.getHistorySize();
for (int h = 0; h < history; h++){
drawCanvas.drawPoint(e.getHistoricalX(h), e.getHistoricalY(h), paint);
}
drawCanvas.drawPoint(e.getX(), e.getY(), paint);

ドットの間隔が狭まりました。指をゆっくり動かせばキレイな線が描画できます。しかしこのHistorical情報にも限度があり、指を少しでも早く動かすとやはり間隔が空いてしまいます。
Historical情報を利用すれば、精度の高い座標情報を取得できることが分かりました。この座標情報をPathによる描画で利用してみます。(HistoricalPathPaintView)
int history = e.getHistorySize();
for (int h = 0; h < history; h++){
path.lineTo(e.getHistoricalX(h), e.getHistoricalY(h));
}
path.lineTo(e.getX(), e.getY());
drawCanvas.drawPath(path, paint);
DataBindingがアツいらしいと聞いて試してみました。簡単な使い方をするなら想像以上に簡単でした。
今までActivityなどでfindViewByIdを書きたくないから、ButterKnifeをどのプロジェクトでも使っていたのですが、DataBindingを使えば同じようなことができます。
両者を使ってみて感じたのは、ButterKnifeがレイアウトXMLをJavaコードに持ってくるイメージであるとすれば、DataBindingはJavaコードをレイアウトXMLに持っていくイメージであるということです。
DataBindingを使うことで、Javaで作成したコードを、レイアウトXMLに埋め込むことができるようになります。レイアウトXMLでどのデータを使うか指定しておけば、Activityで「このクラス(のインスタンス)を使ってくれ」と指定するだけでその内容を表示できたりします。
具体的な使い方はData Binding Guide – Android Developersを参照してください。
Android Studio 1.3以上であることが必須です。
Android Gradle Plugin 1.5.0-alpha1以上を使っていることが必須、でした。
Android Studio 2.0 betaになると、コード補完のサポートがより強力になってます。
build.gradleでDataBindingの設定を有効にすることで利用できます。
android {
....
dataBinding {
enabled = true
}
}
public class Character{
public String name;
public int age;
public String skill;
public Character(String name, int age, String skill){
this.name = name;
this.age = age;
this.skill = skill;
}
}
DataBindingを使ってアクセスするには、publicなフィールドであるか、privateなフィールドである場合publicなgetterがあることが必須です。
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<data>
<variable
name="chara"
type="jp.gcreate.sample.databinding.Character"
/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="jp.gcreate.sample.databinding.MainActivity"
>
<TextView
android:id="@+id/chara_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{chara.name}"
/>
<TextView
android:id="@+id/chara_age"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{Integer.toString(chara.age)}"
/>
<TextView
android:id="@+id/chara_skill"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{chara.skill}"
/>
</LinearLayout>
</layout>
ポイントはこんな感じ。
端末の画面をタッチした情報はMotionEventとしてActivityやViewに通知されます。
MotionEventはさまざまな情報を持っています。
MotionEvent – Android Developers
これらは全てポインタごと別々に識別されていて、全てポインタのインデックスでアクセスすることが出来ます。(ポインタのIDではありません)
その辺りをごっちゃにしてハマった結果、Stackoverflowに投稿した質問がこちらです。Androidでマルチタッチ時のポインターIDを検出する方法(ちなみに投稿後に勘違いが原因であることに気づいた)
ポインタのインデックスは必ず0から始まり、getPointerCount() - 1まで割り振られます。
例えば2本の指でタッチしている場合、getPointerCount()は2を返します。1本目の指がポインタインデックス0で、2本目がインデックス1となります。
さらにこの状態で1本目の指を離すと、2本目の指のインデックスが0に変わります。
指を離す順番によってインデックスはころころ変わるため、特定のポインタを識別するのには使えません。
例えば人差し指、中指、薬指を使ったタップを考えましょう。途中で人差し指、薬指は離したり触れたりしているとします。しかし常に中指はつけたままにして、これをトラッキングしたいとします。この場合にはポインタインデックスを使うことは出来ません。
特定のポインタを識別するにはポインタIDを利用します。
一度タッチするとポインタにはIDが割り当てられ、そのIDは指を離すまで変わりません。
上記の例で言うと、中指を画面から離さないかぎり中指を示すポインタのIDは常に同じです。
一方で注意しなければいけないのは、座標を取得したりするメソッドの引数はポインタインデックスであるということです。
ポインタはIDで識別するけど、そのポインタの情報を取得するために必要なのはポインタインデックスです。
そのため、特定のポインタIDの座標を取得したりするには、findPointerIndex()メソッドを使って、IDからポインタインデックスを引き出す必要があります。
ポインタインデックスは常に0から始まり、他のポインタが増減する度に再割当てされます。一方でポインタを識別するIDは、指が触れたときに割り振られ画面に触れている限りその値は変わりません。
例えばこんな感じになります。
インデックス0 ID0 人差し指 インデックス1 ID1 中指 インデックス2 ID2 薬指 ↓この状態で人差し指を離す インデックス0 ID1 中指 インデックス1 ID2 薬指 ↓人差し指でタッチする インデックス0 ID0 人差し指 インデックス1 ID1 中指 インデックス2 ID2 薬指
ポインタIDとポインタインデックスの値は、指を押した順番と反対に離す分には一致したままですが、押した順番とは異なる離し方をすると値がズレます。
タッチイベントはリアルタイムに配信されるわけではありません。
Android Testing CodelabはEspressoなどのテストツールの使い方を学べるサンプルです。
全篇Englishですが、大体雰囲気でわかるレベルだと思います。
このレッスンをやれば、アプリ開発におけるテストツールの使い方、
が学べます。
ちょっとお得だなと思ったのが、アプリがMVPパターンで作られていることです。テストツールの使い方の勉強のついでに、MVPパターンも学べるなんて一石二鳥だな、なんて思ったのでちょっとやってみました。
思っていたよりもざっくりとした解説なので、雰囲気をつかめるものくらいに考えるといいと思います。
それでもテストのやり方よく分かっていない私からすると、学びの多いレッスンでした。
このサンプルではパッケージをレイヤーごとではなく機能ごとに分けてありました。(機能ごとというよりはActivityごとに近い分け方だと思いましたが)
私はこれまでずっと、ModelはModelパッケージに、というレイヤーごとにパッケージを分けていましたが、「機能ごとに分けた方が見やすくていいだろ」と書かれていて目からうろこでした。
テストのためにモック用のクラスに差し替えるやり方の解説があります。
XMLのリソースファイル(strings.xmlなど)は異なるFlavorで同一のリソース名が存在した場合、Flavorのものが優先されmainで定義したリソースは上書きされます。
一方でJavaのクラスだと挙動が異なり、同一名のクラスが存在するとエラーになります。そのため、全てのFlavor共通で利用するクラスだけをmainに配置し、切り替えが必要なクラスはFlavorのディレクトリに配置するという工夫が必要になります。
Mockitoを使ったJUnit4のテストのやり方が勉強になりました。これは単純に私がMockitoの使い方がよく分かっていなかったからですが。
@Mockアノテーションに寄る初期化とか、ArgumentCaptorの使い方とか。
Espresso-IntentsによるIntentのモック方法、Espresso-Contribを使ったナビゲーションドロワーのUIテストなどが紹介されています。
Espressoテストの章になると、一部修正が必要な部分がありましたが、それもまた勉強になりました。(EditTextへ文字入力をエミュレートした後は、croseKeyboard()しないとエラーになるとか、上へボタンを参照する部分が英語以外の環境だとエラーになるとか)