開発備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CIに関するブログです

Google Cloud Platform Vision API を.NET で使ってみた

先日、Google Cloud Platform のVision API を使用してOCR を試したんですが、思いのほか簡単にできたのでその使い方についてメモメモ。

※なお、この記事は 2018年5月13日 時点の内容です。最新の情報とは異なる可能性がありますのでご注意ください。

Google Cloud Platform - Vision API について

Google Cloud Platform は、Google 社が提供するクラウドサービスで、Vision API はそのGCP で提供されているサービスの一つです。

GCP のサービスは様々な言語から使用することができ、そのサンプルも充実しています。
今回は.NET(C#) でその機能を試してみます。

OCR の種類

Vision APIOCR を行うにあたって文字認識機能は以下の2種類が存在します。

  • TEXT_DETECTION
  • DOCUMENT_TEXT_DETECTION

これらの2つの最大の違いは解析する文字単位の精度です。
TEXT_DETECTION は単語単位、DOCUMENT_TEXT_DETECTION は文字単位で解析されます。
なので、より高精度な解析が必要な場合は DOCUMENT_TEXT_DETECTION を選択することになります。

料金体系について

クラウドサービスを使用するうえで料金体系は重要な要素の一つです。
Vision API の料金体系は以下のページを参照してください。

https://cloud.google.com/vision/pricingcloud.google.com

料金支払いの単位は"ユニット"と呼び、これは一つの解析機能を使用した場合、"1"ユニットとなります。
そして、大体の機能がひと月1,000 ~ 5,000,000(500万)ユニットまで使用すれば、1,000 ユニットにつき$1.50 です。
計算めんどくせーって方は計算ツールも提供さているので以下のページからどうぞ。
cloud.google.com

続きを読む

(Android)APK をCI ツールで生成する方法メモ

Android アプリのAPK ファイルの生成をCI ツールを使用して自動化する際のメモです。
Android Studio だとメニューからAPK を生成できますが、例えばGit のmaster ブランチへpush されたらAPK を生成する といったことができれば、デプロイも簡単になります。

今回は以下のような環境下でどうすればAPK の生成を自動化できるか?という観点で調査・実践した結果をまとめています。

開発環境

開発用PC
  • OS : Windows10 Enterprise 64bit
  • 開発環境 : Android Studio 3.1.1
  • Gradle : 3.3
サーバー(オンプレミス)

目的

業務用のAndroid アプリ開発で、APK ファイルの生成を自動化したい。

アプリの署名について

Android アプリの全てのAPK は、デジタル署名されている必要があります。
署名に関する情報は以下のページを参照してください。

アプリの署名 | Android Studio

この署名はAndroid Studio から行うことができます。
上記URL の[キーとキーストアを生成する] の手順を行えば、キーストアが作成され、以降はパスワードを入力するとAPK ファイルを作成することができます。

ただし、これはあくまで"手動"でAPK を作成する方法です。

続きを読む

(Android)Lint 解析対象から特定のファイルを除外する方法

Android の開発中にデバッグ用のActivity やもう古くなって使用しないファイルをLint の解析対象外にしたいことがあります。 検索方法が間違っていたのか、調べても簡単には出てこなかったのでメモ。

開発環境

  • 開発環境 : Android Studio 3.0
  • Compile Sdk Version : API26 Android8.0 (O)
  • Min Sdk Version : API23 Android6.0 (Mashmallow)
  • Gradle : 2.3.3

Lint 解析の除外方法

特定のファイルをLint 解析から除外するには"lint.xml" ファイルに除外するファイルを記載する必要があります。
lint.xml を参照するには、/app/build.gradle に以下の内容を記述します。

android {
    lintOptions {
        // 任意のファイル名でOK
        lintConfig file('lint.xml')
    }
}

次に/app フォルダに"lint.xml" ファイルを追加します。 lint.xml の記載例は以下のとおりです。

<?xml version="1.0" encoding="UTF-8"?>
<lint>
    <!--  id="all" とすることで全ての警告カテゴリが対象となる。  -->
    <issue id="all">
        <!--  以下のファイルは全てのLint 警告を無視するものとする。  -->
        <ignore path="**/activity_debug_*.xml" />
        <ignore path="**/Debug*.java" />
    </issue>
</lint>

ポイントは以下の2つ

  1. issue タグの id 属性に"all"を設定する。
    今回はデバッグ用のファイルは全てのLint 警告を無視するようにしたかったのでこの様にしています。
    ここは各プロダクトによって適宜変更する必要があるかもしれません。

  2. ignore タグの path 属性に除外対象のフィアルパスを設定する。
    除外対象のファイルは、"**/" で全フォルダが対象となります。
    上記の設定では、activity_debug~.xml のようなデバッグActivity レイアウトファイルを解析対象外としています。
    また、デバッグ用のActivity クラスも同様に解析対象外としています。

何でもかんでもLint 検証対象外にしてしまうのは良くないので、開発が本格的に動き出す前に予めこのあたりのルールは決めておきたいところですね。

(Java)コレクションをプリミティブ型の配列に変換する方法

Java でコレクションをプリミティブ型の配列に変換する際に迷ったのでメモ

開発環境

変換方法

まず、やりたいことは以下の通り。

// 仮にList<Integer> を作る。
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}
// ここをどうにかしたい。
int[] result = target;

List などのコレクションは、Integer などの参照型しかジェネリックに指定できません。
その為、プリミティブ型のint 配列などへ変換することができません。
"java collection to array primitive" などで検索しましたが、先頭に引っかかったStackOverflow では以下のようなコメントが有りました。

stackoverflow.com

Unfortunately, I don't believe there really is a better way of doing this due to the nature of Java's handling of primitive types, boxing, arrays and generics. In particular:

・List.toArray won't work because there's no conversion from Integer to int

・You can't use int as a type argument for generics, so it would have to be an int-specific method (or one which used reflection to do nasty trickery).

I believe there are libraries which have autogenerated versions of this kind of method for all the primitive types (i.e. there's a template which is copied for each type). It's ugly, but that's the way it is I'm afraid :(

Even though the Arrays class came out before generics arrived in Java, it would still have to include all the horrible overloads if it were introduced today (assuming you want to use primitive arrays).

そうかー、良い方法は無いのか…
と、ネガティブな結果となりましたが、それでも2018年だし何かしらベターな方法があるのでは?
と思い調査しました。
その結果、Apache Commons Language3 にプリミティブ型配列への変換機能を見つけました。

Apache Commons Language3 を使う
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
} 

// org.apache.commons.lang3.ArrayUtils#toPrimitive(Integer[]) メソッドを使用して
// List<Integer> → Integer[] → int[] の順にキャストする。
int[] result = org.apache.commons.lang3.ArrayUtils.toPrimitive(target.toArray(new Integer[0]));

コレクションを使用した場合、一度参照型の配列へ変換する必要があります。
あまり美しくはないですが、使用頻度を考慮すればこれでも良い気がします。

ただ、

target.toArray(new Integer[0])

この部分がものすごく書き心地が悪いのでもう少し頑張りたいところ。

そこで、org.apache.commons.lang3.ArrayUtils#toPrimitive(Integer[]) メソッドの実装を確認したところ、以下のようになっていました。

public static int[] toPrimitive(final Integer[] array) {
    if (array == null) {
        return null;
    } else if (array.length == 0) {
        return EMPTY_INT_ARRAY;
    }
    final int[] result = new int[array.length];
    for (int i = 0; i < array.length; i++) {
        result[i] = array[i].intValue();
    }
    return result;
}

上記のStackOverflow の質問者も似たようなコードを書いていました。
なので、こちらを参考にした変換メソッドを作ってみました。

独自の変換メソッドを使う
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}

final int[] result = new int[target.size()];
for (int i=0; i<result.length; i++) {
    result[i] = target.get(i);
}

ほんとにそのまま持ってきただけです。
やっぱりfor 文を使用すると途端にわかりにくくなるのは否めません。

とはいえ、上記のどちらもUtils のstatic メソッドにしてしまえば使用者が見ることは無いのでどちらでも良い気がします。
どちらでも良いのですが、個人的事情もあり、今回は実行速度の早い方を採用したいと思います。

どちらが速いか?
  • for Apache Commons Language3
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}

long begin = System.currentTimeMillis();

int[] result = org.apache.commons.lang3.ArrayUtils.toPrimitive(target.toArray(new Integer[0]));

long finish = System.currentTimeMillis();
System.out.println((finish - begin) + "ms");
  • for 独自の変換メソッド
List<Integer> target = new ArrayList<>();
for (int i=0; i<100000; i++) {
    target.add(i);
}

long begin = System.currentTimeMillis();

final int[] result = new int[target.size()];
for (int i=0; i<result.length; i++) {
    result[i] = target.get(i);
}

long finish = System.currentTimeMillis();
System.out.println((finish - begin) + "ms");

実行結果は以下の通り。

  • for Apache Commons Language3 : 8ms
  • for 独自の変換メソッド : 1ms

厳密にベンチマークテストできているわけではないですが、明らかに差があります。
ということで、速度を求めた結果、以下のようになりました。

public final class ArrayUtils {
    
    public static int[] toArray(List<Integer> target) {
    
        final int[] result = new int[target.size()];
        for (int i=0; i<result.length; i++) {
            result[i] = target.get(i);
        }
        
        return result;
    }
}

ブログ用にエラー処理などは記述していません。

ちなみにC#だとどうなるか

C# だと、コレクションから配列への変換は以下のようになります。
といってもC# は言語仕様上、コレクションの型にプリミティブ型を使用することができるため、Java と全く同じではないですが。

var target = new List<int>();

for (int i = 0; i < 100000; i++)
{
    target.Add(0);
}

target.ToArray();

実行速度は1/10 ミリ秒 ~ 2/10 ミリ秒程度でした。

(Jenkins)Android アプリのビルド・ユニットテスト・Lint を実行する

最近、Android アプリの開発をメインにやってます。 Android でもCI やりたいなーと思って色々調べて何とか最低限のジョブは実行できるようになったので、その内容についてメモ。

開発環境

今回は、Windows Server 2012 R2 上にいるJenkins がSlave 兼開発環境であるWindows10 に対してジョブを実行する構成となっています。

実行するジョブ

今回は以下のジョブをPipeline で実行してみます。

Lint については、以下のページを参照してください。

Lint によるコードの改善 | Android Studio

また、Jenkins でLint のレポートを集計する場合は以下のプラグインを予めインストールする必要があります。

Android Lint Plugin - Jenkins - Jenkins Wiki

Pipeline Script

node('<スレーブ名>') {

    stage('Get Repository') {
        // リポジトリからソースコードを取得する。
        git branch: 'develop', credentialsId: '<認証ID>', url: '<対象のリポジトリ クローンURL>'
    }
    
    stage('Build Debug') {
        // コマンドラインからGradle を実行する。
        // ./gradlew 以降の引数は、Android Studio でビルドした際にGradle が実行するタスクを列挙している。
        bat './gradlew :app:generateDebugSources :app:generateDebugAndroidTestSources :app:mockableAndroidJar :app:prepareDebugUnitTestDependencies build'
    }
    
    stage('Lint Report') {
        // Lint を実行・集計する。
        androidLint canComputeNew: false, defaultEncoding: 'UTF-8', healthy: '', pattern: '**/lint-results*.xml', shouldDetectModules: true, unHealthy: '', unstableTotalAll: '0'
    }

    stage('Unit Test') {
        // ユニットテストを実行
        bat './gradlew clean test'

        // HTML で実行結果がレポートされる。(こっちが見やすい)
        // レポートはデフォルトでは"app/build/reports/tests/testDebugUnitTest/"に出力される。
        publishHTML([allowMissing: true, alwaysLinkToLastBuild: true, keepAll: true, reportDir: 'app/build/reports/tests/testDebugUnitTest/', reportFiles: 'index.html', reportName: 'JUnit Results'])

        // JUnit 結果をカウントするためにレポート結果を集計する。
        // レポートはデフォルトでは"/app/build/test-results/testDebugUnitTest/"配下にあるXML ファイルが対象となる。
        junit allowEmptyResults: true, testResults: '**/app/build/test-results/testDebugUnitTest/*.xml'
    }

}

ハマった箇所

  1. コマンドラインが実行できない。
    Windows 上で"bat"コマンドを実行できない場合があります。
    その場合は、スレーブの設定から環境変数にパスを設定する必要があります。
    コマンドプロンプトは通常以下の場所にあるので環境変数から"Path"を追加して値を"C:\Windows\System32;"としましょう。
  2. Lint の検査に引っかかるとビルドが停止する。
    Lint はGradle のデフォルト設定では検査結果に異常があればビルド失敗として扱われます。
    その為、1件でも検査エラーがあれば、Jenkins の以降のタスクが実行されません。
    これでは検査エラーが有った場合でもレポートを出力したいときなどに困ります。
    そこで、Android プロジェクトの/app/build.gradle ファイルからビルド失敗時にAbort させないように設定する必要があります。
    変更箇所は以下の通り。
android {
    lintOptions {
        abortOnError false
    }
}

Lint オプションについては以下のページを参照してください。

LintOptions - Android Plugin 3.0.0-dev DSL Reference

(Jenkins)Pipeline を使う

Jenkins のPipeline の使い方と環境設定についてメモ。

Pipeline について

Jenkins のPipeline 機能が追加されてからもう2年近くが経ちますが、今の今までGroovy が分からなくて触ってなかったのですが、最近使って便利だったので使い方と環境設定についてメモします。
Jenkins Pipeline の概要については以下のページが参考になります。

jenkins.io

www.buildinsider.net

開発環境

Pipeline を使う

Pipeline の使い方、Groovy の書き方についての日本語情報は以下のページが非常に参考になりました。

kimulla.hatenablog.com こうやってスクリプトとその結果を画像で並べていると見やすくていいですね~

これだけだと参考ページの紹介で終わってしまうので、実際に使ってみてハマった箇所の解決方法を幾つか挙げてみます。

  • 環境設定でハマった箇所
    Slave を作成する際、ノード プロパティの環境変数にSlave の端末の環境変数を設定しておく必要があります。
    これをしていないと、例えばWindows batch を実行できなくなります。
    特にキー:PATH の環境変数は必須です。

  • Pipeline Syntax で使用したいPlugin が表示されない
    Pipeline のコード スニペットを使用する際に、"Sample Step" から実行したいJob を選択してPipeline Script を生成することが大半だと思います。
    この時、Sample Step に表示されるJob はPipeline に対応したPlugin のみ列挙されます。
    Plugin はバージョンによってはPipeline に対応していないものも存在するため、一度バージョンアップを試してみましょう。
    私の場合は、"Task Scanner Plugin" でこの問題にハマりました。

軽く使用しただけでもPipeline 機能は非常に便利なので、今後も使用していきたいと思います。

(Android)スクリーンのON/OFFを検出する

Android端末でスクリーンのON/OFFを検出する方法についてメモ。

開発環境

  • 開発環境 : Android Studio 3.0
  • Compile Sdk Version : API26 Android8.0 (O)
  • Min Sdk Version : API23 Android6.0 (Mashmallow)

スクリーンのON/OFFを検出する

通常、スクリーンのON/OFFを検出するにはIntentFilter を設定したBroadcastReceiver を使用します。
今回はサンプルとしてWakeLockBroadcastReceiver というスクリーンON/OFFを検出するためのBroadcastReceiver クラスを作成し、スクリーンの状態が変わるとWakeLockListener というリスナーに通知します。
Intent のAction 文字列は、スクリーンの表示が"Intent.ACTION_SCREEN_ON"、スクリーンの非表示が"Intent.ACTION_SCREEN_OFF"となっています。
以下、コード例です。

<WakeLockBroadcastReceiver.java

package com.example.iyemon.wakelocksample;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

public final class WakeLockBroadcastReceiver extends BroadcastReceiver {

    private WakeLockListener listener;

    public WakeLockBroadcastReceiver(WakeLockListener listener) {
        this.listener = listener;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action.equals(Intent.ACTION_SCREEN_ON)) {
            this.listener.onScreenOn();
        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
            this.listener.onScreenOff();
        }
    }
}

<WakeLockListener.java

package com.example.iyemon.wakelocksample;

public interface WakeLockListener {
    
    void onScreenOn();

    void onScreenOff();
}


<MainActivity.java

package com.example.iyemon.wakelocksample;

import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.format.DateFormat;
import android.widget.TextView;

import java.util.Calendar;


public class MainActivity extends AppCompatActivity implements WakeLockListener {

    private WakeLockBroadcastReceiver wakeLockBroadcastReceiver;
    private TextView loggingTextView;

    private final String NewLine =  System.getProperty("line.separator");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.loggingTextView = (TextView)findViewById(R.id.loggingTextView);

        wakeLockBroadcastReceiver = new WakeLockBroadcastReceiver(this);
        registerReceiver(wakeLockBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));
        registerReceiver(wakeLockBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
    }

    @Override
    public void onScreenOn() {
        String text = loggingTextView.getText().toString();
        loggingTextView.setText(text + NewLine + getNow() + " Screen On.");
    }

    @Override
    public void onScreenOff() {
        String text = loggingTextView.getText().toString();
        loggingTextView.setText(text + NewLine + getNow() + " Screen Off.");
    }

    private String getNow(){
        return DateFormat.format("yyyy/MM/dd HH:mm:ss", Calendar.getInstance()).toString();
    }
}

これを実行すると以下のようになります。
f:id:iyemon018:20171228011742p:plain

ハマったところ

BroadcastReceiver にIntentFilter を設定する場合、manifest ファイルにフィルター名を設定すればいいのですが、ACTION_SCREEN_ON/ACTION_SCREEN_OFF はContext#registerReceiver を使用する必要があります。
その理由については以下のページが参考になります。

qiita.com


今回使用したプロジェクトはこちら↓↓↓↓↓

github.com