月別: 2015年3月

Androidアプリを開発する上で賢いLogの出力方法(とその確認の仕方)

この記事は最終更新から3ヶ月以上が経過しています。情報が古い可能性があります。

今までずっとLog.d("test",”デバッグメッセージだよ”);みたいな感じでLogを出力し、Logcatで確認しながらプログラミングしていたのですが、とあるサンプルを見ていた時にこんなコードに出くわしました。

    private static final String TAG = "DigitalWatchFaceConfig";

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "onConnected: " + connectionHint);
        }

プログラムを実行しても、このログはlogcatに出力されません。

「なんでだ?」と思って調べているうちに、この方法はAndroidアプリ開発していく上で賢い選択なのだなということが分かってきました。

ベストプラクティスなのかどうかまでは分かりませんが、少なくともいきなりLog.d()で出力したり、アプリ内でprivate boolean isDebug = true;みたいにしてデバッグログを出力させるよりは賢いなと思いました。

参考サイト

[Log.isLoggable – API Refference](http://developer.android.com/reference/android/util/Log.html#isLoggable(java.lang.String, int))

ログレベルを制御する – TechBooster

ログの出力はアプリのパフォーマンスを下げる

ログの出力はアプリのパフォーマンスに影響します。少なくともStringオブジェクトを作ってそれを出力するわけですからね。

リリース時にはLog出力する部分を全部削除するのが一番いいのでしょうが、さすがにそれは手間が大きすぎます。それにリリースしたからといって開発が終わるわけでもなく、メンテのためにまた1からLogを出力するように直すのはあまりにも馬鹿らしいです。

Logを使わず開発するのはそもそも無理です。

Log.isLoggableによるチェック

Log.isLoggableによるチェックは、端末に設定されているログ出力レベルを判定しています。デフォルトでは全てのタグについてINFOが設定されています。

つまり最初のコードのようなLog.DEBUGでチェックをかけるとfalseが返ってくるのでログが出力されません。

ログ出力レベルの変更

ではどうやってログが出力されるようにすればいいのかというと、ターミナルでadb shell setpropコマンドを使います。

adb shell stop
adb shell setprop log.tag.設定したいタグ名 ログレベル
adb shell start

最初の例のログを出力させようと思ったらadb shell setprop log.tag.DigitalWatchFaceService DEBUGとターミナルから打ち込んでやればOKです。(ちなみにadb shellで端末にログインしてからであれば、いきなりsetpropから初めてOKです)

ログ出力レベルの確認

タグごとのログ出力レベルを確認するには、adb shell getprop log.tag.タグ名を使います。何も設定していない状態であれば空白が返って来ます。setpropで設定してやると、現在設定されているログ出力レベルが返って来ます。

ログレベルの優先度

VERBOSE > DEBUG > INFO > WARN > ERROR > ASSERTとなっています。デフォルトではINFOになっているので、isLoggable()はINFO,WARN,ERROR,ASSERTでtrueを返します。

setpropでVERBOSEを設定するとあらゆるレベルでtrueが返るようになります。setprop SUPPRESSとすると逆にあらゆるレベルでfalseが返るようになります。

再起動したら元に戻る

ちなみに設定する端末が開発専用であれば問題ないでしょうが、日常的に使っている端末の場合はログ出力レベルをINFOに戻すのを忘れないようにしましょう。(パフォーマンスに影響するため)

別に忘れても再起動すれば元に戻る(設定が消える)ので気にしなくてもいいかもしれません。

ちなみに「再起動の度に設定するのは面倒くさい」という場合には、/data/local.propで設定することもできるようですが、どうやって設定するのかは分かりません。多分root権限ないとできないんじゃないかと思います。(やってみたけどPermission deniedって言われました)

リリース予定がないならいきなりLog.d()でも構わないのでしょうが、リリースを視野に入れているアプリならこういった方法でログを出力するように設定しておくとユーザに優しいと思います。

Android Studioでソースコードを読むのに覚えておくと便利なショートカット

この記事は最終更新から3ヶ月以上が経過しています。情報が古い可能性があります。

最近になってようやくサンプルなどのソースコードを読むようになったのですが、「あ、こんな便利な機能あったのね」というのを紹介します。

ショートカットキーはMacのものを書いてます。メニューにショートカットキーも表示してくれているので、Windowsの方はそこで調べてください。

Find Usages

調べたい変数やメソッドにカーソルを合わせてOpt+F7

Edit > Find > Find Usages

変数やメソッドがどこでどう使われているか調べるのに使います。今まで右クリック→Find Usagesで表示させてました。もしくはIdeaVim使っているので「/文字列」で検索してました。

メンバ変数(フィールド変数)だとValue readでどこで参照されているかが、Value writeでどこで変更されているかが表示されます。

フィールド変数でFind Usages

メソッドだと、どこで呼ばれているかが表示されます。

メソッドでFind Usages

プライベートフィールドに対して特に効果を発揮する機能です。

ちなみにFind Usagesで使われ方を表示した後は、Cmd+Shift+↓(↑)で次に出てくるところに飛んでくれます。さらにいうとCmdとOptを押し間違えると、表示させてる行の位置が入れ替わったりするので注意してくださいね。

戻る

Cmd+[

Navigate > Back

日本語キーボードだと[ではなく@になります。

Find Usagesなどを使ってジャンプした場合に、ジャンプ前に読んでた場所に戻ってくれます。今まで「前読んでたところどこだ・・・」とイライラしながら探してました。

戻りすぎたらCmd+](日本語キーボードなら[)で1つ先へ飛びます。

この機能を使わずしてソースコードは読めない。

とりあえずこの2つの存在を知っているだけで格段にソースコードを読む辛さが緩和されます。

Declaration

Cmd+b もしくはCmd押しながらクリック

Navigate > Declaration

私はDeclarationの意味をよく分かってないのですが、宣言先に飛ぶってことなんだと思います。

親クラスで定義されてるメソッドを呼んでる時に、そのソースコードに飛ぶときに使ってます。例えば何気なく使ってるfindViewById()はいったい何やってんだって調べたりする感じです。Cmdキー押しながらクリックするだけなんで、ショートカットキー覚えてなくても行けそうな気はします。

Android Support Libraryのクラスだとソースコードまで付属してないのでそのままでは見れないんですけどね(見れないわけではないようです)。

Android標準のクラスならソースコードもインストールしてあれば確認することができます。Android SDK ManagerでSources for Android SDKをインストールしておく必要がありますが、開発する上ではないと困ると思うのでインストールしておいた方がいいでしょう。

SDK ManagerでSources for Android SDKをインストールしておく

Javadoc表示

Javadocを確認したいメソッド等にカーソルを合わせてF1Shift+F1でブラウザで見れます)

View > Quick Documentation

私の場合、読む時より書くときによくお世話になってる機能です。

知らずに使っていると損

ソースコードを読むのはものすごく勉強になるのに、今まで避けてきていたのはひとえに読むのが大変だったからでした。こういった便利な機能を知らなければ、そりゃ億劫になるのも当然と言えます。

使いこなしている人にとっては当然の知識かもしれませんが、初心者にとってはこんなこともわからないことなのです・・・。

調べてみるともっと便利な機能があるのかもしれません。「こんな便利な機能もあるよ」というのがあったら教えていただけると助かります。

MatrixのpostScaleで画像を拡大縮小させる

この記事は最終更新から3ヶ月以上が経過しています。情報が古い可能性があります。

ImageViewなり、自分で作ったCustom Viewなりで表示させる画像を、動かしたり拡大縮小させたりするのに使えるMatrixをいじって学んだことのメモです。

特にpostScaleを使った拡大縮小がイメージ通りに動かなくてハマってしまいました。

ちなみにMatrixクラスを使ってBitmapを加工する – Techoboosterを参考に始めました。

使い方

ImageViewに設定するには、setImageMatrixメソッドでMatrixを渡してやるといいです。

Matrix matrix = new Matrix();
ImageView.setImageMatrix(Matrix);

CustomViewで使う場合は、オーバーライドしたonDraw()で描画するときにMatrixを渡せばいいです。

   @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(mImage, mMatrix, mPaint);
    }

こんな感じ。mImageはBitmapオブジェクトで、mMatrixとmPaintはそれぞれnew Matrix(),new Paint()したものを渡してます。

今回は下のCustom ViewでMatrixを操作していて分かったことを書きます。

タッチ操作で動かす

画面上を指でなぞると、その動きに応じて画像も移動するようにする場合はこうすればOK。

mMatrix.postTranslate(float X移動量,float Y移動量);

移動量を取得するにはTouchEventを自分で判定するなり、GestureDetectorを使うなりして取得します。

GestureDetectorの使い方はDetecting Common Gestures – Android Developers参照。また別途記事書こうと思います。

移動に関しては特に難しくはありませんでした。ただし、移動制限を設けようとするとこれはこれでまた大変そうです。

こちらの記事が移動制限を実装するのに非常に役立ちそうな予感です。実装できたらまた記事書きたいと思います(そればっか)。

拡大縮小

ピンチイン・アウトで画像を拡大縮小させる場合がクセモノでした。

mMatrix.postScale(float X拡大率, float Y拡大率, float 拡大の起点X, float 拡大の起点Y);

ハマったポイントはここで渡す拡大率の扱いです。

postScaleに渡す拡大率は、Matrixを指定した拡大率に変形させるのではありません。現在のMatrixを渡した拡大率で拡大縮小させます。Matrixの拡大率が0.1のときにpostScale(0.1f,0.1f)するとMatrixの拡大率は0.01になります。

画像が過剰に縮小・拡大されないように渡す拡大率の値を制限したとしても、制限した値をそのまま渡してしまったら制限が効きません。

指定した拡大率に画像を変形させたい場合は変化量を計算して渡すようにします。

ScaleGestureDetectorを使って拡大縮小させていて、頭の中がこんがらがっていました(現在進行形ですけど)。ScaleGestureDetectorを使うと、onScaleメソッド内でdetector.getScaleFactor()を使うことでピンチイン・アウトによる拡大率を取得することができます。

この拡大率は、ピンチ操作が始まった段階では1.0から始まります。そのためこの値をそのままMatrixのpostScaleに渡すと、拡大縮小の開始時に一旦元の縮尺に戻ってしまいます。そのこととごっちゃになっていて間違ったこと書いてました。

float deltaScale = targetScale / nowScale;
mMatrix.postScale(deltaScale, deltaScale);

postScaleではなくsetScaleを使う方法もあるのかもしれませんが、動き始めに画像が元のサイズに戻ってしまうため、この方法がスマートな気がします。

ちなみにピンチイン・アウトを検出するにはScaleGestureDetectorが使えます。Dragging and Scaling – Android Developers

拡大縮小の起点を指定する場合、detector.getFocusX()を使うとイメージに近い動きになりました。ただし画像の範囲外でやると当然ながら動きがおかしくなるので、画像が画面の範囲外に移動できないように制限を実装しないとダメそうです。

現在の拡大率を取得するには?

Matrixに設定された現在の拡大率を取得するのも一工夫必要です。getScaleXというような、Marixの現在の拡大率を直接取得するメソッドはありません。

Matrixは9つの値を保持していますが、その値を参照するためにはfloatの配列を渡してコピーしてもらうしか方法がありません。

float[] values = new float[9];
mMatrix.getValues(values);
nowScale = values[Matrix.MSCALE_X];

ちなみにAPIリファレンスにある並びで並んでいるわけではないので、定数を使ってアクセスしましょう。

ちなみに0から順にMSCALE_X,MSKEW_X,MTRANS_X,MSCALE_Y,MSKEW_Y,MTRANS_Y,MPERSP_0,MPERSP_1,MPERSP_2の順に並んでます。

なんで変な並びになってるのかと思ったら、英語のWikipedia見たらその意味が分かるかもしれません。Transformation matrix – Wikipedia

Android特有の概念ではなく、Matrixを使った変形の概念があるんですね。数式だらけでサッパリ分かりませんが。

日本語で解説してあるサイトもありますが、いずれにしても奥が深そうで難解です・・・。

Intentを発行して画像を選択orカメラで撮影して、画像を表示させる

この記事は最終更新から3ヶ月以上が経過しています。情報が古い可能性があります。

端末内に保存されている画像を表示したり、もしくはその場でカメラで撮影した画像を表示させる方法です。

例えばSNSへ投稿する画像を選択したりするのに使うことが考えられますかね。

やり方としてはIntentを発行して、startActivityResult()で結果を受け取って表示させるようになります。

画像の選択とカメラでの撮影は異なるアクションなので、1つのIntentで表現するにはIntent.createChooser()で複数のIntentをひとまとめにして発行することになります。

やってみると、カメラで撮影した画像を受け取るのにちょっと工夫が必要なだけで、割と簡単に実装できました。

Getting a Result from an Activity – Android Developers

Intentの発行

画像を選択するIntent

        Intent pickPhotoIntent = new Intent()
                .setType("image/*")
                .setAction(Intent.ACTION_GET_CONTENT);

カメラで撮影するIntent

カメラで撮影する場合、以下のIntentでも撮影→その画像を受取ることができますが、そのままでは画像サイズがとても小さくなってしまいます。(サムネイルサイズの小さな画像が返ってくる)

        Intent takePhotoIntent = new Intent()
                .setAction(MediaStore.ACTION_IMAGE_CAPTURE);

複数のIntentを埋め込む

        Intent chooserIntent = Intent.createChooser(pickPhotoIntent, "画像を選択");
        chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,new Intent[]{takePhotoIntent});

作成したIntentの1つを元にしてcreateChooser()を呼び出して作成したIntentに、Intentの配列を埋め込みます。

画像を受け取る

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode == REQUEST_GET_IMAGE && resultCode == Activity.RESULT_OK) {
            if (data != null) {
                Bitmap image = null;
                if (data.getExtras() != null && data.getExtras().get("data") != null) {
                    image = (Bitmap) data.getExtras().get("data");
                    mImageView.setImageBitmap(image);
                } else {
                    try {
                        InputStream stream = getContentResolver().openInputStream(data.getData());
                        image = BitmapFactory.decodeStream(stream);
                        mImageView.setImageBitmap(image);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

Intentによって選択されたファイルは、当該ファイルを一意に識別するためのUriがIntentに埋め込まれて返ってきます。これはdata.getExtra()で取得できます。上記の例ではBitmapファイルとして取得して、独自View(mImageView)に渡しています。

一方でカメラで撮影した画像は、直接画像データがdata.getExtras().get("data")で取得できます。ただしこの画像はサムネイル用の小さな画像です。

オリジナルの画像データは大きすぎて、Intentを経由しての受け渡しができないからです。カメラで撮影したオリジナルサイズの画像を受け渡しするには一工夫必要です。長くなるので別に書きます。