開発備忘録

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

(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

(WPF)要素のスクリーンキャプチャーを保存するTriggerActionを作る

WPFで画面上の要素をキャプチャーして画像ファイルとして保存するような要件がワリとありますが、どうせならTriggerActionで作られたプロダクトは無いかなーと思って探してみたところ、すぐには見つからなかったので自作してみました。

以下、今回の開発環境です。

WPFでスクリーンキャプチャーを保存する方法は色々ありますが、今回は以下のページを参考にさせていただきました。

mseeeen.msen.jp

これをTriggerActionで実装してみた結果がこちら

<CaptureAciton.cs>

namespace CaptureTriggerSample.TriggerActions
{
    using System;
    using System.IO;
    using System.Windows;
    using System.Windows.Interactivity;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;

    public class CaptureAction : TriggerAction<FrameworkElement>
    {
        public static readonly DependencyProperty FileNameProperty =
                DependencyProperty.Register("FileName"
                                            , typeof(string)
                                            , typeof(CaptureAction)
                                            , new FrameworkPropertyMetadata(null));

        public string FileName
        {
            get => (string)GetValue(FileNameProperty);
            set => SetValue(FileNameProperty, value);
        }

        protected override void Invoke(object parameter)
        {
            Rect bounds = VisualTreeHelper.GetDescendantBounds(AssociatedObject);
            DrawingVisual drawingVisual = new DrawingVisual();
            using (DrawingContext drawingContext = drawingVisual.RenderOpen())
            {
                VisualBrush visualBrush = new VisualBrush(AssociatedObject);
                drawingContext.DrawRectangle(visualBrush, null, bounds);
            }

            RenderTargetBitmap renderTargetBitmap =
                    new RenderTargetBitmap((int)bounds.Width, (int)bounds.Height, 96, 96, PixelFormats.Pbgra32);
            renderTargetBitmap.Render(drawingVisual);
            renderTargetBitmap.Freeze();

            PngBitmapEncoder png = new PngBitmapEncoder();
            BitmapFrame bitmapFrame = BitmapFrame.Create(renderTargetBitmap);
            bitmapFrame.Freeze();
            png.Frames.Add(bitmapFrame);
            using (FileStream fileStream = File.Create(FileName))
            {
                png.Save(fileStream);
            }

            MessageBox.Show($"キャプチャーを保存しました{Environment.NewLine}"
                            + $"[ファイル名]{Environment.NewLine}"
                            + $" {FileName}"
                            , "キャプチャー"
                            , MessageBoxButton.OK
                            , MessageBoxImage.Information);
        }
    }
}

※ブログ用に可読性を考慮してエラー処理などはしていません。

FileName プロパティに出力するファイル名を指定できるようにしています。

これを使うにはこのようにします。

<MainWindow.xaml

<Window
    x:Class="CaptureTriggerSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:local="clr-namespace:CaptureTriggerSample"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:triggerActions="clr-namespace:CaptureTriggerSample.TriggerActions"
    Title="MainWindow"
    Width="525"
    Height="350"
    mc:Ignorable="d">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click" SourceObject="{Binding ElementName=CaptureButton}">
            <triggerActions:CaptureAction FileName="Resources\Capture.png" />
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid x:Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Button
            x:Name="CaptureButton"
            Grid.Row="0"
            Margin="8"
            Padding="16,8"
            HorizontalAlignment="Left"
            Content="Capture" />
        <Grid Grid.Row="1" Margin="8">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
            </Grid.RowDefinitions>
            <TextBox x:Name="TextBox" />
            <TextBlock
                Grid.Row="1"
                Margin="0,8"
                Text="{Binding Text, ElementName=TextBox, Mode=OneWay}" />
        </Grid>
    </Grid>
</Window>

これを実行すると以下のような画面が表示されます。
f:id:iyemon018:20171215142227p:plain

CaptureボタンをクリックするとWindow要素のキャプチャーが実行されます。
(実行フォルダに"Resources"フォルダを作成する必要があります。)
f:id:iyemon018:20171215142328p:plain

実際にキャプチャーされた画像ファイルはこちら。
f:id:iyemon018:20171215142417p:plain

このように簡単に指定した領域をキャプチャーすることが可能です。
TriggerActionにしたことにより、例えばKeyTrigger を使ってCtrl + Cキー入力時にキャプチャーを保存したりすることも可能です。

ただし、1点注意することがあります。
レアケースだとは思いますが、以下の様にキャプチャー対象の要素の外側にも子要素が配置されている場合、子要素を含む領域がキャプチャーされてしまいます。
以下はVisual StudioXAMLデザイナープレビューです。
右上にEllipseを配置していますが、Grid の領域外に配置しています。
f:id:iyemon018:20171215143722p:plain

これをキャプチャーすると以下の様になります。
f:id:iyemon018:20171215144027p:plain

原因はここ

Rect bounds = VisualTreeHelper.GetDescendantBounds(AssociatedObject);

VisualTreeHelper.GetDescendantBounds は指定したビジュアル要素の全ての子要素の和集合から領域を算出します。
つまり、AssociatedObject の子要素が配置された領域全てがその対象となります。(ただし原点はあくまでAssociatedObject を基準とする。)

これを回避するには、AssociatedObject もしくはその子要素にClipToBounds="True" を設定します。
こうすることで要素の範囲外にあるものはキャプチャーされなくなります。

画面領域外にコントロールを配置することってあんまりないと思うのですが、移動アニメーション中などに予期せずキャプチャー範囲が期待したもので無くなることはあり得るので注意が必要です。


今回使用したサンプルはこちら↓↓↓↓
github.com

Visual Studioのリモートデバッグ方法メモ

Visual Studioのリモートデバッグ機能を初めて使ったのでメモ

リモートデバッグ機能とは?

リモートデバッグとは、例えば客先から受領している端末上で実行しているアセンブリに対してブレークポイントやステップ実行するための機能です。
詳細は以下のページを参照してください。

リモート デバッグ

使い方

適当にググれば出てくるのですが、一番わかりやすかったのは以下のページでした。

ネットワーク経由でのリモート ユーザーモード デバッグ – Japan WDK Support Blog


と、ここまでですんなり実行できるのかと思いきやいくつかハマりました。
ちなみに実行環境は"Visual Studio2015"のWPF アプリケーションです。

Visual Studio2015 で開発していたのですが、リモートデバッグ ツールのページは404になっています…
以下のページからダウンロードしましょう。

qiita.com

  • 実行フォルダがホストとリモートで同じでなければならない

これは上記MSDNページにも記述されていますが、開発用環境とリモート環境でアセンブリの配置フォルダ パスが同一である必要があります。
私の環境はDドライブ上で実行していたので最初は実行に失敗しました。
仕方なくプロジェクトの[プロパティ] - [ビルド] - [出力パス] をCドライブに変更しました。

  • 毎回実行ファイルをコピーする必要がある

仕方ないことなのですが、実行環境にビルドしたアセンブリを毎回配置する必要があります。
最初はUSBメモリを使用してコピーしていたのですが、面倒なのでリモート環境に共有フォルダを作成しました。
そして、上記の出力パスを共有フォルダに設定することでアセンブリ コピーの手順が不要になりました。
つまり、以下のようなイメージ

Before
f:id:iyemon018:20171205185628p:plain

After
f:id:iyemon018:20171205185630p:plain

これで毎回、開発用PCと同じ感覚でデバッグすることが可能になりました。
リモートの設定は環境に依存するため、リモート デバッグ用の構成を追加しておくのがベターだと思います。

実行環境にリモートツールをインストールしたりネットワークに接続したりといくつか条件はありますが、実行環境でしか発生しない不具合もあるため条件さえ合致すれば有用なデバッグ方法だと思いました。
ただ、この仕組み上、複数人で同時にリモートデバッグを行うといった方法を取ることはできないでしょう。

(Android)ログ出力ライブラリ"Timber"を使ってみる

Androidのログ出力ライブラリ"Timber"でログ出力機能を使ってみたのでメモ。

Timberについて

github.com

APIリファレンスはこちら。
Timber 3.0.1 API

動作環境

パッケージを導入する

  1. [File] - [Project Structure...] からパッケージを追加したいモジュールの[Dependencies]タブを選択する。
  2. 画面右側の[+]をクリック[Library dependency]を選択する。
  3. "com.jakewharton.timber:timber:4.5.1"と入力し、検索する。
  4. "com.jakewharton.timber:timber:4.5.1"を選択して[OK]ボタンをクリックする。
  5. build.grableに以下が追加されていればOK
compile 'com.jakewharton.timber:timber:4.5.1'

Timberを使う

Timberは初期化する際にログ出力機構のTreeを指定します。
以下は予め用意されているデバッグ用のTreeを設定しています。
リリース用のTreeは自前で作成する必要があるようです。

Timber.plant(new DebugTree());

使用する場合は通常のLogクラスと使い方は同じです。


サンプルを作成がてらログ出力機能を作りました。
以下のLoggerクラスはTimberのログ出力機能をラッパーです。
gist.github.com

initializeで初期化しているログTreeは以下のようになっています。
gist.github.com

後はこれを以下の様に使用します。
gist.github.com

これを実行するとLogCatには以下のようなログが出力されます。
f:id:iyemon018:20170912165732p:plain


あとはログの出力先をファイルにするなりクラッシュレポートを送信するなりすると使えそうです。

*1:2017年9月12日時点で最新版です。