rksoftware

Visual Studio とか C# とかが好きです

城東.NET #14 勉強会で話をしてきました

先日開催した

にて Android アプリ → Xamarin.Android の書き換えを試す というタイトルで話をしました。

Android アプリ開発のサンプルを Xamarin.Android へ移植してみた際の作業手順などの概要です。

■ 次回予定

来月は 12月20日(水)に開催の予定です。

.NET に関心のある方、是非遊びに来てください。

Android アプリを Xamarin.Android に移植する (5) [完成]

前々々々回 Android アプリを Xamarin.Android に移植する (1) で UI を移植しました。
出来上がった画面。

今回 3 つ目のクラスの移植を行って行きます。一番のメイン、Activity クラスです。

■ 今回移植するクラス

今回は
com.example.android.mediarecorder.MainActivity クラス を C# で書き換えます。

■ MainActivity クラスの編集

MainActivity クラス

MainActivity クラスは既に生成されているクラスを書き換えて行きます。

using の追加

今回も必要な using は最初に追加してしまいます。ファイルの先頭の using の部分が次のようになるようコピー&ペーストしてください。

using System;
using Android.App;
using Android.Content.PM;
using Android.Hardware;
using Android.OS;
using Android.Views;
using Java.IO;
using Android.Media;
using Android.Widget;
using Java.Lang;
using Android.Util;
using System.Collections.Generic;
using String = System.String;

データメンバー の移植

Java のクラスの先頭で定義されているデータメンバーを移植します。
MainActivity.java の 46 行目からの次のコードを、C# の MainActivity.cs の同じ位置 (クラスの宣言と OnCreate メソッドの宣言の間) に追加します。
下記の C# のコードをコピー&ペーストしてください。
・Java のコード

private Camera mCamera;
private TextureView mPreview;
private MediaRecorder mMediaRecorder;
private File mOutputFile;

private boolean isRecording = false;
private static final String TAG = "Recorder";
private Button captureButton;

・C# のコード

private Camera mCamera;
private TextureView mPreview;
private Android.Media.MediaRecorder mMediaRecorder;
private File mOutputFile;

private bool isRecording = false;
private static readonly string TAG = "Recorder";
private Button captureButton;

コントロールのインスタンス取得の追加

OnCreate での処理を追加します。OnCreate は画面生成時の処理で、今回は画面上のコントロールの実態をデータメンバーに保持します。

SetContentView(Resource.Layout.sample_main);

の下に下記の C# コードをコピー&ペーストします。これまで同様、Java では小文字始まりだったメソッド名が、大文字始まりになります。 ・Java のコード

mPreview = (TextureView) findViewById(R.id.surface_view);
captureButton = (Button) findViewById(R.id.button_capture);

・C# のコード

mPreview = (TextureView)FindViewById(Resource.Id.surface_view);
captureButton = (Button)FindViewById(Resource.Id.button_capture);

OnCreate は次のようになります。

protected override void OnCreate(Bundle savedInstanceState)
{
    base.OnCreate(savedInstanceState);
 
    SetContentView(Resource.Layout.sample_main);
 
    mPreview = (TextureView)FindViewById(Resource.Id.surface_view);
    captureButton = (Button)FindViewById(Resource.Id.button_capture);
}

OnCaptureClick メソッドのコピー&ペースト

既に作成済みの OnCaptureClick メソッドに実装を追加します。作成済みの OnCaptureClick メソッドは次のようになっているはずです。

[Java.Interop.Export("onCaptureClick")]
public void OnCaptureClick(View view)
{
}

このメソッドの中に、MainActivity.java の 71 行目 onCaptureClick メソッドの内容をコピー&ペーストします。
いくらかのエラーが出ますが、構わず先へ進みます。

その他のメソッド等のコピー&ペースト

OnCaptureClick の下に、MainActivity.java の 105 行目 setCaptureButtonText メソッド以降のコードを最後の } を除いてコピー&ペーストします。

メソッド名の変更

Java ではメソッド名は小文字で始まりますが、C# では大文字で始まります。
小文字で始まっているメソッド名を、大文字始まりに一気に変更して行きます。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

mMediaRecorder.stop()mMediaRecorder.Stop()
mOutputFile.delete()mOutputFile.Delete()
mCamera.lockmCamera.Lock
mMediaRecorder.reset()mMediaRecorder.Reset()
mMediaRecorder.release()mMediaRecorder.Release()
mCamera.release()mCamera.Release()
mCamera.getParameters()mCamera.GetParameters()
CamcorderProfile.get(CamcorderProfile.Get(
parameters.setPreviewSize(parameters.SetPreviewSize(
mCamera.setParameters(mCamera.SetParameters(
mCamera.setPreviewTexture(mCamera.SetPreviewTexture(
mCamera.unlock()mCamera.Unlock()
mMediaRecorder.setmMediaRecorder.Set
mMediaRecorder.prepare()mMediaRecorder.Prepare()
mMediaRecorder.start()mMediaRecorder.Start()

string クラスへの変更

Java の String クラスは C# の string を使用するよう変更します。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

Stringstring

プロパティへの変更 (setter)

Java の setter メソッドはなるべく、プロパティに変更されています。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

captureButton.setText(title)captureButton.Text = title

メソッドの orverride の変更

Java ではスーパークラスのメソッドをサブクラスで orverride する場合はアノテーションを設定しました。C# では return 値の型の前に orverride キーワードを記述します。
スーパークラスは C# に移植されたクラスのため、メソッド名の先頭が大文字になる点に注意してください。
・Java のコード

@Override
protected void onPause()

・C# のコード

protected override void OnPause()

base への書き換え

Java ではスーパークラスのメソッドを呼び出す場合、super キーワードを使用します。C# では同様の機能は base になります。
スーパークラスは C# に移植されたクラスのため、メソッド名の先頭が大文字になる点に注意してください。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

super.onPause()base.OnPause()

bool への変更

Java では真偽値型は boolean キーワードですが、C# では bool になります。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

booleanbool
Booleanbool

IList への書き換え

List<> は Java のインタフェースですが、C# ではインタフェースは頭に I が付いた IList<> という名前になります。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

List<IList<

プロパティへの変更

Java の getter メソッドはなるべく、プロパティに変更されています。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

parameters.getSupportedPreviewSizes()parameters.SupportedPreviewSizes
parameters.getSupportedVideoSizes()parameters.SupportedVideoSizes
mPreview.getWidth()mPreview.Width
mPreview.getHeight()mPreview.Height
mPreview.getSurfaceTexture()mPreview.SurfaceTexture
e.getMessage()e.Message
mOutputFile.getPath()mOutputFile.Path

プロパティ名の変更

Java では公開データメンバは小文字で始まりますが、C# ではプロパティになり、大文字始まりになります。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

profile.videoFrameWidthprofile.VideoFrameWidth
profile.videoFrameHeightprofile.VideoFrameHeight
optimalSize.widthoptimalSize.Width
optimalSize.heightoptimalSize.Height

列挙体への変更

Java では公開フォールドはなるべく列挙体に変更されています。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

CamcorderProfile.QUALITY_HIGHCamcorderQuality.High
MediaRecorder.AudioSource.DEFAULTAudioSource.Default
MediaRecorder.VideoSource.CAMERAVideoSource.Camera

メソッド定義の変更

MainActivity.cs の 133 行目あたりの次の文でエラーが発生しているはずです。
これは、CameraHelper クラス移植の際に手数を減らすために、本来行うべき Java の List<> から C# の IList<T> への置き換えを行っていないために発生しました。
※まずは、CameraHelper を一度ビルドできる状態にするため省略しました。

Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes,
    mSupportedPreviewSizes, mPreview.Width, mPreview.Height);

メソッド名 getOptimalVideoSize にカーソルをあわせて F12 キーを押し、メソッドの定義へ移動します。
移動した先のメソッド定義を次のように変更します。

public static Camera.Size getOptimalVideoSize(IList<Camera.Size> supportedVideoSizes,
    IList<Camera.Size> previewSizes, int w, int h)

この書き換えにより、10 行程下の行でエラーが発生します。このエラーを正すために、エラー行の 3 つ上の行の型定義も IList<Camera.Size> に変更します。
・変更前

List<Camera.Size> videoSizes;

・変更後

IList<Camera.Size> videoSizes;

CameraHelper.cs にエラーがないことを確認し、MainActivity.cs に戻ります。

デバッグ出力メソッドの変更

デバッグ出力メソッドのメソッド名 dDebug になっているので変更します。
また、エラー出力のメソッド名 eError になります。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

Log.d(Log.Debug(
Log.e(Log.Error(

クラス継承のキーワードの変更

Java ではクラスの継承は extends キーワードですが、C# では : になります。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

extends:

型引数 Void の変更

AsyncTask<> の型引数 VoidJava.Lang.Void であることを明示します。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

AsyncTask<Void, Void, bool>AsyncTask<Java.Lang.Void, Java.Lang.Void, bool>

MediaPrepareTask クラスの変更

MediaPrepareTask クラスは Android API のクラスで、非同期処理を行う AsyncTask クラスのサブクラスです。
この MediaPrepareTask クラス内の変更も基本はこれまでのパターンで良いのですが、このクラスがインナークラスとして実装されているという大きな違いがあります。
Java のインナークラスは親クラスのインスタンスを参照できますが、C# ではそのようなことができません。
C# へ置き換える際に、MediaPrepareTask クラスが親クラスである MainActivity クラスのインスタンスを明示的に持つよう変更します。

少し複雑な書き換えを行うため MediaPrepareTask クラスは完成コードをまるごとコピー&ペーストします。
次のコードへコピー&ペーストで置き換えてください。

class MediaPrepareTask : AsyncTask<Java.Lang.Void, Java.Lang.Void, bool>
{
    private readonly WeakReference<MainActivity> _activity;
    public MediaPrepareTask(MainActivity activity)
    {
        _activity = new WeakReference<MainActivity>(activity);
    }
    protected override Java.Lang.Object DoInBackground(params Java.Lang.Object[] voids)
    {
        MainActivity activity;
        _activity.TryGetTarget(out activity);
        if (activity.prepareVideoRecorder())
        {
            activity.mMediaRecorder.Start();
            activity.isRecording = true;
        }
        else
        {
            activity.releaseMediaRecorder();
            return false;
        }
        return true;
    }
    protected override void OnPostExecute(bool result)
    {
        MainActivity activity;
        _activity.TryGetTarget(out activity);
        if (!result)
        {
            activity.Finish();
        }
        activity.setCaptureButtonText("Stop");
    }
    protected override bool RunInBackground(params Java.Lang.Void[] @params)
    {
        throw new NotImplementedException();
    }
}

MediaPrepareTask クラス使用の変更

MediaPrepareTask クラスを MainActivity クラスのインスタンスを保持するよう変更しました。
そのため、MediaPrepareTask クラスの使用されている場所で、MainActivity クラスのインスタンスを設定するよう変更します。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

new MediaPrepareTask().execute(null, null, null);new MediaPrepareTask(this).Execute(null, null, null);

■ MainActivity クラスの完成

これで MainActivity クラスは完成です。
ビルドを行いエラーが出ないことを確認してください。(警告は大量に出ますが、今回はそのままにします)
エラーが出た場合は、エラーの内容と上記の書き換えを見比べて頑張ってエラーに対処してください。

■ 実行

ここまでで、一度アプリを実行してみましょう。
Visual Studio (for Mac) のツールバーの 右三角 アイコンをクリックします。
次の画面が表示されるはずです。

前回出来上がった画面

ここで、[capture] ボタンをタップするとエラーが発生します。
これはまだこのアプリがカメラ等を使うためのパーミッションを設定していないためです。
パーミッションを正しく設定していない場合、該当する機能を使用しようとするとエラーが発生するのがスマートデバイスの特徴です。

■ 設定ファイルの設定

編集する設定ファイルは、Properties フォルダー内の AndroidManifest.xml ファイルです。
・ソリューションエクスプローラー上で AndroidManifest.xml をダブルクリックします。
・エディターで XML ファイルが開くので次のように編集します。ソースが開かない場合、[ソース]タブを選択してください。
<manifest> タグの中に次の要素を追加します。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_VIDEO" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
AndroidManifest.xml 全体は例えば次のようになります。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.example.android.XamMediaRecorder">
    <uses-sdk android:minSdkVersion="16" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.RECORD_VIDEO" />
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name"/>
</manifest>

■ 実行

これでアプリが正しく動作するはずです。
もし、activity.mMediaRecorder.Start(); で例外が発生する場合は、AndroidManifest.xml<uses-sdk の設定値を確認してみてください。

お疲れ様でした! これで Android アプリの Xamarin.Android への移植ができました。
この経験をもとにぜひ Xamarin で Android アプリを開発してください!

城東.NET #14 勉強会を開催しました。

■ 城東.NET

 城東.NET #14 を開催しました。

 城東.NET は東京の最近は秋葉原で毎月第3水曜日に開催している .NET 系の勉強会です。
 発表を中心として、発表でなくとも最近やった事や新しい情報などを参加者で共有している会です。

 今月の内容も、上記イベントサイトに資料やコメントが載っているものもあります。
 興味があったら覗いてみてください。

■ 次回予定

 来月は 12月20日(水)に開催の予定です。

 .NET に関心のある方、是非遊びに来てください。

Android アプリを Xamarin.Android に移植する (インデックス)

Android アプリを Xamarin.Android に書き換えるシリーズ (1〜5) の目次です。
※Android サンプルアプリ MediaRecorder ( https://github.com/googlesamples/android-MediaRecorder )

内容としては非常にタンポポ度が高いのですが、元のコードがサンプルにしては少々長編なので耐えてください。
この書き換えを通じて、Xamarin の
・「ネイティブ API カバー率 100%」や
・「ネイティブ経験者はネイティブ開発の経験が活かせる」
の一端を感じてもらえたらと思います。

なお、この移植は Android アプリと Xamarin しか出てきません。
クロスプラットフォーム開発には一切触れないのでその点はご了承ください。
クロスプラットフォームを体験したい場合は、文末に掲載するハンズオンがおすすめです。

■ もくじ

1.プロジェクトの新規作成・UI の移植

2.アプリケーションアイコンの設定

3.CameraHelper クラスの移植

4.MediaCodecWrapper クラスの移植

5.MainActivity クラスの移植 (完成)

この手順で Android アプリの Xamarin.Android への移植ができました。
これを参考にぜひ Xamarin で Android アプリを開発してください!

■ クロスプラットフォームを体験したい場合

Xamarin によるクロスプラットフォーム開発を体験したい場合は、以下のハンズオンがおすすめです。

また、このハンズオンはオフラインイベントでも開催しています。
もしご興味を持たれた場合、とりあえず参加登録がおすすめです。
日程などは、登録してから調整しても遅くはないですきっと!

Android アプリを Xamarin.Android に移植する (4)

前々々回 Android アプリを Xamarin.Android に移植する (1) で UI を移植しました。

今回 2 つ目のクラスの移植を行って行きます。

■ 今回移植するクラス

今回は
com.example.android.common.media.MediaCodecWrapper クラス を C# で書き換えます。

■ MediaCodecWrapper クラスの作成

Mac の場合

・ソリューションエクスプローラーの太字になっているプロジェクト名で右クリック (二本指タップ) し 追加 > 新しいファイル を選択します。
・[新しいファイル]ダイアログで General > 空のクラス を選択し、[名前] に 「MediaCodecWrapper」 を入力します。
・[新規]ボタンをクリックします。

Windows の場合

・ソリューションエクスプローラーの太字になっているプロジェクト名で右クリック (二本指タップ) し 追加 > 新しい項目 を選択します。
・[新しい項目の追加]ダイアログで Visual C# > クラス を選択し、[名前] に 「MediaCodecWrapper」 を入力します。
・[新規]ボタンをクリックします。

■ MediaCodecWrapper クラスの移植

上記で作成したクラスのソースのクラス宣言は次のようになっているはずです。

public class MediaCodecWrapper
{
    public MediaCodecWrapper()
    {
    }
}

ソースコードの コピー & ペースト

Java の MediaCodecWrapper クラスの package、import を除いた

public class MediaCodecWrapper {

...
中略
...

}

で C# の MediaCodecWrapper クラスの using、namespace を除いた

    public class MediaCodecWrapper
    {
        public MediaCodecWrapper()
        {
        }
    }

を上書きします。

using の追加

前回の CameraHelper クラスでは、1 つずつ確認しながら using を追加しました。
今回は必要な using は最初に追加してしまいます。

using System.Linq;
using Android.OS;
using Android.Media;
using System.Collections.Generic;
using Java.Nio;
using Java.Lang;
using Java.Util;
using Android.Views;

を追加します。最初からある using System; と合わせて次のようになります。

using System;
using System.Linq;
using Android.OS;
using Android.Media;
using System.Collections.Generic;
using Java.Nio;
using Java.Lang;
using Java.Util;
using Android.Views;

メソッド名の変更

Java ではメソッド名は小文字で始まりますが、C# では大文字で始まります。
小文字で始まっているメソッド名を、大文字始まりに一気に変更して行きます。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

codec.start()codec.Start()
mDecoder.stop()mDecoder.Stop()
mDecoder.release()mDecoder.Release()
codec.getInputBuffers()codec.GetInputBuffers()
codec.getOutputBuffers()codec.GetOutputBuffers()
Looper.myLooper()Looper.MyLooper()
trackFormat.getString(trackFormat.getString(
mimeType.contains(mimeType.Contains(
MediaCodec.createDecoderByType(MediaCodec.CreateDecoderByType(
videoCodec.configure(videoCodec.Configure(
input.remaining()input.Remaining()
buffer.capacity()buffer.Capacity()
buffer.clear()buffer.Clear()
buffer.put(buffer.Put(
mDecoder.queueInputBuffer(mDecoder.QueueInputBuffer(
mDecoder.queueSecureInputBuffer(mDecoder.QueueSecureInputBuffer(
extractor.readSampleData(extractor.ReadSampleData(
extractor.getSampleCryptoInfo(extractor.GetSampleCryptoInfo(
out_bufferInfo.set(out_bufferInfo.Set(
mDecoder.releaseOutputBuffer(mDecoder.ReleaseOutputBuffer(
mDecoder.dequeueInputBuffer(mDecoder.DequeueInputBuffer(
mDecoder.dequeueOutputBuffer(mDecoder.DequeueOutputBuffer(
mDecoder.getOutputBuffers()mDecoder.GetOutputBuffers()
mHandler.post(mHandler.Post(

プロパティ名の変更

Java では公開データメンバは小文字で始まりますが、C# ではプロパティになり、大文字始まりになります。
小文字で始まっているプロパティ名を、大文字始まりに一気に変更して行きます。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

mOutputBuffers.lengthmOutputBuffers.Length
mInputBuffers.lengthmInputBuffers.Length)
info.offsetinfo.Offset
info.sizeinfo.Size
info.presentationTimeUsinfo.PresentationTimeUs
info.flagsinfo.Flags
Locale.USLocale.Us

定数名の変更

Java では公開フォールドはすべて大文字・_区切りですが、C# では大文字始まり大文字区切りになります。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

MediaFormat.KEY_MIMEMediaFormat.KeyMime

列挙体への変更

Java では公開フォールドはなるべく列挙体に変更されています。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

MediaCodec.BUFFER_FLAG_END_OF_STREAM(int)MediaCodec.BufferFlagEndOfStream
, flags), (MediaCodecBufferFlags)flags)
MediaCodec.INFO_TRY_AGAIN_LATER(int)MediaCodec.InfoTryAgainLater
MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED(int)MediaCodec.InfoOutputBuffersChanged
MediaCodec.INFO_OUTPUT_FORMAT_CHANGED(int)MediaCodec.InfoOutputFormatChanged

final ローカル変数の変更

Java では変更不可のローカル変数の定義に final キーワードを使用しますが、C# では const になります。
さらに C# では文字列がリテラルでなければなりません。そのため、ここでは const が使用できません。final の指定は削除します。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

final String mimeType =string mimeType =

final 引数の変更

Java ではメソッドの引数にはなるべく final を付けますが、C# では同等の機能はありません。
引数の final を削除します。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

final → ``````

Queue への変更

Java の ArrayDeque<> クラスは Xamarin に移植されていません。代わりに C# の Queue<T> クラスを使用します。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

Queue<Integer>Queue<int>
ArrayDeque<>Queue<int>

また、クラスが変わりのでメソッドも変わります。Queue<T> の同等のメソッドを使用するよう変更します。

!mAvailableInputBuffers.isEmpty()mAvailableInputBuffers.Any()
mAvailableInputBuffers.remove()mAvailableInputBuffers.Dequeue()
!mAvailableOutputBuffers.isEmpty()mAvailableOutputBuffers.Any()
mAvailableOutputBuffers.peek()mAvailableOutputBuffers.Peek()
mAvailableOutputBuffers.remove()mAvailableOutputBuffers.Dequeue()
mAvailableInputBuffers.add(mAvailableInputBuffers.Enqueue(
mAvailableOutputBuffers.clear()mAvailableOutputBuffers.Clear()
mAvailableOutputBuffers.add(mAvailableOutputBuffers.Enqueue(

throws の削除

Java のメソッドが返す場合、返す例外の種類を throws で宣言しますが、C# では同等の機能はありません。
throws の宣言を削除します。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

throws IOException
throws MediaCodec.CryptoException, WriteException

bool への変更

Java では真偽値型は boolean キーワードですが、C# では bool になります。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

booleanbool

Java.Lang.String の使用

Xamarin にする場合、文字列は C# の string を使用するべきですが、Java の String も使用できます。
今回は Java の String のメソッドが使用されている箇所は、手数を少なく書き換えのミスを減らすために、Java から移植されたクラスを使用します。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

String.format(Java.Lang.String.Format(

プロパティへの変更

Java の getter メソッドはなるべく、プロパティに変更されています。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

mDecoder.getOutputFormat()mDecoder.OutputFormat

ラムダ式への変更

Java でのイベントリスナーの匿名クラスは、C# ではラムダ式で実装します。
ラムダ式を使用すると匿名メソッドを短い手数で記述することができます。

Java の実装

mHandler.post(new Runnable() {
    @Override
    public void run()
    {
        mOutputFormatChangedListener
        .outputFormatChanged(MediaCodecWrapper.this,
            mDecoder.getOutputFormat());

    }
});

C# での実装

mHandler.Post(new Runnable(() =>
{
    mOutputFormatChangedListener
    .outputFormatChanged(this,
        mDecoder.OutputFormat);

}));

クラス継承のキーワードの変更

Java ではクラスの継承は extends キーワードですが、C# では : になります。

置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

extends:

コンストラクタの変更

Java ではコンストラクタ内でスーパークラスのコンストラクタを呼ぶキーワードは super ですが、C# では base になります。
また、記述する場所を変更になります。

Java の実装

private WriteException(String detailMessage)
{
    super(detailMessage);
}

C# での実装

public WriteException(string detailMessage) : base(detailMessage)
{
}

# ■ MediaCodecWrapper クラスの完成 これで MediaCodecWrapper クラスは完成です。
ビルドを行いエラーが出ないことを確認してください。(警告は大量に出ますが、今回はそのままにします)
エラーが出た場合は、エラーの内容と上記の書き換えを見比べて頑張ってエラーに対処してください。

■ 次回予定

ここまでで二つ目のクラスの移植は完了しました。
次回で最後のクラスである Activity の移植を行って行きます。

Android アプリを Xamarin.Android に移植する (3)

前々回 Android アプリを Xamarin.Android に移植する (1) で UI を移植しました。
出来上がった画面。

前回出来上がった画面

今回からロジックの移植を行って行きます。

■ Android サンプルの構成

サンプルアプリは次の3つのクラスで構成されています。
com.example.android.common.media.CameraHelper クラス
com.example.android.common.media.MediaCodecWrapper クラス
com.example.android.mediarecorder.MainActivity クラス
この3クラスを C# で書き換えます。

C# の クラスと Java のクラス

C# のクラスと Java のクラスは記述方法、機能ともに概ね変わりません。
そのため、今回は C# でも3クラスを作成します。

ファイルの作成場所

Java ではクラスのソースファイルは、そのクラスのパッケージに合わせたフォルダーに作成しなければなりません。
C# でも Java のパッケージに当たる名前空間という機能がありますが、この名前空間とフォルダーは合わせる必要はありません。
合わせる必要はありませんが、一般的には名前空間とフォルダーは合わせて作成します。
ただし、今回は手順の簡略化のため、ファイルはすべてプロジェクトの一番浅いフォルダーに置いてしまいます。

■ CameraHelper クラスの作成

Mac の場合

・ソリューションエクスプローラーの太字になっているプロジェクト名で右クリック (二本指タップ) し 追加 > 新しいファイル を選択します。
・[新しいファイル]ダイアログで General > 空のクラス を選択し、[名前] に 「CameraHelper」 を入力します。
・[新規]ボタンをクリックします。

Windows の場合

・ソリューションエクスプローラーの太字になっているプロジェクト名で右クリック (二本指タップ) し 追加 > 新しい項目 を選択します。
・[新しい項目の追加]ダイアログで Visual C# > クラス を選択し、[名前] に 「CameraHelper」 を入力します。
・[新規]ボタンをクリックします。

■ CameraHelper クラスの移植

上記で作成したクラスのソースのクラス宣言は次のようになっているはずです。

public class CameraHelper
{
    public CameraHelper()
    {
    }
}
クラスの宣言は Java と大きく変わらないことがわかります。
このように C# と Java の構文はかなり似た部分が多くなっています。

ソースコードの コピー & ペースト

C# と Java の構文は似ているので、まずは Java のソースコードを C# へコピー & ペーストしてしまいます。
Java の CameraHelper クラスの package、import を除いた

public class CameraHelper {
...
中略
...
}
で C# の CameraHelper クラスの using、namespace を除いた
public class CameraHelper
{
    public CameraHelper()
    {
    }
}
を上書きします。
大量のエラーが出ますが、これはコードの大枠が C# として認識できるものということでもあります。
このエラーを潰して行くだけで、Xamarin.Android として成立するコードになる簡単な作業です。

static final の置き換え

大量のエラーの最初は

public static final int MEDIA_TYPE_IMAGE = 1;
という一文のエラーです。これは Java で定数を定義している文ですが、C# では finalreadonly になります。
public static readonly int MEDIA_TYPE_IMAGE = 1;
すぐ下の MEDIA_TYPE_VIDEO の定義も同様に書き換えます。

using の追加

次のエラーは

public static Camera.Size getOptimalVideoSize(List<Camera.Size> supportedVideoSizes,
で、Camera が見つからないというエラーです。これは Camera クラスを使用するための、Java では import に当たる宣言を行っていないために発生しています。
C# では using というキーワードになります。
using はエラーの発生している Camera にカーソルを合わせて、Mac の場合は option+enter、Windows の場合は ctrl+. から自動で追加することもできます。
using Android.Hardware; を追加します。

List への書き換え

次のエラーは

public static Camera.Size getOptimalVideoSize(List<Camera.Size> supportedVideoSizes,
で、List<> が見つからないというエラーです。List<> は Java のインタフェースですが、C# ではインタフェースは頭に I が付いた IList<> という名前になります。
正しく置き換えるのであれば IList<T> に置き換えるべきですが、今回は書き換えの手数を省略するために C# での実装クラスである List<T> に置き換えます。 Camera クラスと同様に、using を追加します。
using は Mac の場合は option+enter、Windows の場合は ctrl+. から自動で追加することもできます。
using System.Collections.Generic; を追加します。

const ローカル変数への書き換え

次のエラーは

final double ASPECT_TOLERANCE = 0.1;
という一文で、これはローカル定数を宣言しています。C# では finalconst になります。
const double ASPECT_TOLERANCE = 0.1;

Double.MaxValues への書き換え

次のエラーは

double minDiff = Double.MAX_VALUE;
doubleMAX_VALUE が見つからないというエラーです。Java では定数はすべて大文字・_区切りで定義されていますが、C# では定数は大文字始まり・大文字区切りで定義されています。
double minDiff = Double.MaxValue;
C# では Doubledouble は別名の関係でどちらで書いても同じです。
この書き換えは何度か出てくるので、覚えておくかここで一括置換してしまいましょう。
置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

foreach への書き換え

次のエラーは

for (Camera.Size size : videoSizes)
で、: がおかしいですというエラーです。これは Java でコレクションの要素全てに対する繰り返しを行う記述です。 C# では foreach (... in ...) になります。
foreach (Camera.Size size in videoSizes)
この書き換えは何度か出てくるので、覚えておくかここで一括置換してしまいましょう。
置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

size.Width、size.Height への書き換え

次のエラーは

double ratio = (double)size.width / size.height;
width が見つからないというエラーです。Java の public データメンバーは C# ではプロパティになります。public データメンバーとプロパティは利用する側のコードは同じですが、C# の流儀では、頭が大文字になります。
double ratio = (double)size.Width / size.height;
同様に同じ文の size.heightsize.Height に変更します。

これら書き換えは何度か出てくるので、覚えておくかここで一括置換してしまいましょう。
置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

Math.Abs への書き換え

次のエラーは

if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE)
`abs が見つからないというエラーです。Java のメソッド名の流儀は頭を小文字で始めますが、C# では頭が大文字で始まります。
if (Math.Abs(ratio - targetRatio) > ASPECT_TOLERANCE)
ここで、Math クラスは Java の標準ライブラリのクラスですが、今回書使用しているのは C# の標準ライブラリの Math クラスです。
このように基本的なクラスには Java と C# で近いものがあることが珍しくありません。

この書き換えは何度か出てくるので、覚えておくかここで一括置換してしまいましょう。
Math.absMath.Abs と置換します。
置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

previewSizes.Contains への書き換え

次のエラーは

if (Math.Abs(size.Height - targetHeight) < minDiff && previewSizes.contains(size))
contains が見つからないというエラーです。 Java のメソッド名の流儀は頭を小文字で始めますが、C# では頭が大文字で始まります。
if (Math.Abs(size.Height - targetHeight) < minDiff && previewSizes.contains(size))

この書き換えは何度か出てくるので、覚えておくかここで一括置換してしまいましょう。
previewSizes.containspreviewSizes.Contains と置換します。
置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

Camera.Open() への書き換え

次のエラーは

return Camera.open();
open が見つからないというエラーです。Java のメソッド名の流儀は頭を小文字で始めますが、C# では頭が大文字で始まります。
return Camera.Open();

この書き換えは何度か出てくるので、覚えておくかここで一括置換してしまいましょう。
Camera.openCamera.Open と置換します。
置換は Mac の場合は option+command+F、Windows の場合は ctrl+H で行えます。

(int)Camera.CameraInfo.CameraFacingBack への書き換え

次のエラーは

return getDefaultCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
CAMERA_FACING_BACK が見つからないというエラーです。これは Java の定数ですが、C# では列挙体になります。
Xamarin(C#) ではこのような種類を表す定数はなるべく列挙体にする方針になっています。
また、列挙体のメンバーも定数なので、C# では大文字始まりの大文字区切りのスタイルになります。
return getDefaultCamera(Camera.CameraInfo.CameraFacingBack);
ただし、ここでこの書き換えを行うと、またこの一文がエラーになります。
getDefaultCamera メソッドは今回作成している CameraHelper クラスで宣言しているメソッドで、引数が int になっています。
正しく書き換えるのであればメソッドの定義を変えるべきですが、ここでは手数を少なく書き換えのミスを減らすために、列挙体メンバーを int にキャストします。
return getDefaultCamera((int)Camera.CameraInfo.CameraFacingBack);

同様に 9 行程下の

return getDefaultCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
return getDefaultCamera((int)Camera.CameraInfo.CameraFacingFront);

属性への書き換え

次は

@TargetApi(Build.VERSION_CODES.GINGERBREAD)
のエラーです。これは Java のアノテーションの宣言ですが、C# では属性になります。次のように書き換えます。
[Android.Annotation.TargetApi(Value = (int)Android.OS.Build.VERSION_CODES.Gingerbread)]

Camera.NumberOfCameras への書き換え

次は

int mNumberOfCameras = Camera.getNumberOfCameras();
のエラーです。Xamarin(C#) では決まった値を取得するメソッドはプロパティになっています。
また、プロパティは大文字で始まります。
int mNumberOfCameras = Camera.NumberOfCameras;

Camera.GetCameraInfo への書き換え

次は

Camera.getCameraInfo(i, cameraInfo);
のエラーです。C# ではメソッド名は大文字始まりになります。
Camera.GetCameraInfo(i, cameraInfo);

cameraInfo.facing への書き換え

次は

if (cameraInfo.facing == position)
のエラーです。C# 列挙体になっています。また、変数 position を int で宣言しているのでキャストします。
本来であれば変数の型を変更するべきですが、ここでは手数を少なく書き換えのミスを減らすために、列挙体メンバーを int にキャストします。
if ((int)cameraInfo.Facing == position)

using Java.IO の追加

次は

public static File getOutputMediaFile(int type)
File が見つからないというエラーです。
ファイルの先頭に
using Java.IO;
を追加します。 using は Mac の場合は option+enter、Windows の場合は ctrl+. から自動で追加することもできます。
この時、using System.IOusing Java.IO が選択肢に出てくるので注意してください。
System.IO 名前空間は .NET(C#) のクラスライブラリーのもので、Xamarin にするならば本来こちらを使用するべきですが、ここでは手数とミスを減らすために、Java から移植されたクラスを使用します。

Environment の using エイリアスの追加

次は

if (!Environment.getExternalStorageState().equalsIgnoreCase(Environment.MEDIA_MOUNTED))
getExternalStorageState が見つからないというエラーです。これは、C# と Android API に同名の Environment クラスが存在し、現在は C# のクラスを参照しています。これを Android API のクラスを参照するように、using エイリアスという機能で EnvironmentAndroid.OS.Environment の別名として定義します。
ファイルの先頭に
using Environment = Android.OS.Environment;
を追加します。また、C# では getter メソッドはプロパティになるので、get と () を削除します。 また、Environment.MEDIA_MOUNTED 定数も先頭大文字・大文字区切りに変更します。
if (!Environment.ExternalStorageState.equalsIgnoreCase(Environment.MediaMounted))
これでも、まだ equalsIgnoreCase が見つからないというエラーになっています。
equalsIgnoreCase メソッドに相当する機能は、C# ではオプション引数付きの Equals メソッドになります。
if (!Environment.ExternalStorageState.Equals(Environment.MediaMounted, StringComparison.OrdinalIgnoreCase))

Environment.GetExternalStoragePublicDirectory への書き換え

次は

File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_PICTURES), "CameraSample");
getExternalStoragePublicDirectory が見つからないというエラーです。メソッド名を大文字始まりに変更します。
また、Environment.DIRECTORY_PICTURES 定数も先頭大文字・大文字区切りに変更します。
File mediaStorageDir = new File(Environment.GetExternalStoragePublicDirectory(
    Environment.DirectoryPictures), "CameraSample");

mediaStorageDir.Exists()、mediaStorageDir.Mkdirs() への書き換え

次は

if (!mediaStorageDir.exists())
{
    if (!mediaStorageDir.mkdirs())
exists が見つからないというエラーです。メソッド名を大文字始まりに変更します。
mkdirs も同じように変更します。
if (!mediaStorageDir.Exists())
{
    if (!mediaStorageDir.Mkdirs())

using Android.Util の追加

次は

Log.d("CameraSample", "failed to create directory");
Log が見つからないというエラーです。
ファイルの先頭に
using Android.Util;
を追加します。 using は Mac の場合は option+enter、Windows の場合は ctrl+. から自動で追加することもできます。
また、d メソッドは Xamarin では Debug になっているので変更します。
Log.Debug("CameraSample", "failed to create directory");

using Java.Text、using Java.Util の追加

次は

String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
SimpleDateFormatLocaleDate が見つからないというエラーです。
ファイルの先頭に
using Java.Text;
using Java.Util;
を追加します。 using は Mac の場合は option+enter、Windows の場合は ctrl+. から自動で追加することもできます。
本来であればここは C# の DateTime 構造体など変更するべきですが、ここでは手数を少なく書き換えのミスを減らすために、Java から移植されたクラスを使用します。
また、US 定数は Xamarin では Usformat メソッドは先頭が大文字になっているので変更します。
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.Us).Format(new Date());

mediaStorageDir.Path、File.Separator への書き換え

次は

mediaFile = new File(mediaStorageDir.getPath() + File.separator +
getPathseparator が見つからないというエラーです。これまでのように、プロパティへの変更と、先頭大文字への変更を行います。
mediaFile = new File(mediaStorageDir.Path + File.Separator +

同じようにすぐ下の

mediaFile = new File(mediaStorageDir.getPath() + File.separator +
mediaFile = new File(mediaStorageDir.Path + File.Separator +
と変更します。

■ CameraHelper クラスの完成

これで CameraHelper クラスは完成です。
ビルドを行いエラーが出ないことを確認してください。(警告は大量に出ますが、今回はそのままにします)
エラーが出た場合は、エラーの内容と上記の書き換えを見比べて頑張ってエラーに対処してください。

■ 次回予定

ここまでで一つ目のクラスの移植は完了しました。
次回以降でさらに別のクラスの移植を行って行きます。

Android アプリを Xamarin.Android に移植する (2)

前回 Android アプリを Xamarin.Android に移植する (1) で UI を移植しました。

前回出来上がった画面

アプリが起動している間の画面は出来上がりましたが、ホームやマルチタスクボタンを押すした際のアイコンが Xamarin のデフォルトになっています。
今回はまずはこのアイコンを設定します。

■ アプリケーションのアイコンの設定

前回、UI を含みリソースは Android のプロジェクトから Xamarin.Android へ移植しています。
アイコンはこのリソースの中に含まれている画像ファイルです。
今はまだ、ファイルを追加しただけでアイコンとして設定されていないのでデフォルトの画像になってしまっています。

アプリケーションのアイコンはアプリケーションのプロパティから設定できますが、今回は設定のファイルを直接編集していきます。
GUI は分かりやすいですが、IDE によって変わってしまうという面があります。
今回編集する設定ファイルは、内容が簡単である点とサンプルアプリというお手本があるため直接編集してしまう方が間違いがないです。
お手本がない場合は、GUI から設定すると良いでしょう。

設定ファイルの設定

編集する設定ファイルは、Properties フォルダー内の AndroidManifest.xml ファイルです。
・ソリューションエクスプローラー上で AndroidManifest.xml をダブルクリックします。
・エディターで XML ファイルが開くので次のように編集します。
<application> タグを中に android:icon 属性がある場合は次のように編集します。ない場合は書き足します。

<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name">
</application>
これで、Resources フォルダー内の drawable フォルダー内の ic_launcher.png ファイルがアイコンとして使用されます。

アイコン確認

これだけで、アイコンの移植が完了しました。
F5 キーで実行し、画面を確認しましょう。
アプリケーションのアイコンがビデオカメラ?のような画像になっていれば成功です。

■ 次回予定

ここまでで UI の移植は完了しました。
次回以降でロジックの移植を行って行きます。