Xamarin.Androidでロボホンを動かす(Java Binding Library利用)

  • 11
    Like
  • 0
    Comment

この記事の概要

  • Java Binding Libraryを利用したXamarin.Android開発
  • 題材がロボホンとなっています

ロボホンって何?

  • Sharpさんが2016年5月に発売したロボット型の携帯電話です
  • Android5.0(Lolipop)を搭載しています
  • 公式の開発環境はAndroid Studioになります

なんでロボホン?

  • たまたま手元にあり最近使ったので

Xamarin使う目的は何?

  • C#erなのでC#を使いたいという個人的な趣味(メリットあまり無い)です
  • Advent Calendar向けのネタが欲しかったので

前提条件

  • 開発にはロボホン実機が必要となります
  • またロボホンSDKが必要です

Step1:ロボホンSDKの導入

ロボホンの開発は公式が提供するSDK(jarファイル)を利用して行います
Xamarin用のライブラリ提供はされていませんのでC#でjarを利用する必要があります

Xamarinで既存のJavaライブラリを利用する方法についてはエクセルソフトさんのHPにわかりやすい説明があります Java との統合:概要

一番簡単な方法は Binding a Java Library を利用する方法となり、今回はこの方法を採用しています
Binding a Java LibraryとはjarファイルのXamarin用のラッパーDLLを生成するプロジェクトです

Binding a Java Libraryについては下記の記事がわかりやすいです
Xamarin.AndroidにおけるJava相互運用の仕組みと、Javaバインディング・プロジェクト

その他の方法(JNI)の説明は割愛します
Working with JNI

Step2:Binding a Java Libraryプロジェクトの作成

公式チュートリアル:Binding a .JAR
以下、公式手順通りとなります。

  1. 新規プロジェクト作成で「Bindings Library (Android)」を選択
    名前をRobohonBindingとしました
  2. プロジェクトのJarsフォルダに対象となるJarファイルを追加
    ロボホン用に以下のファイルを追加しました
    • jp.co.sharp.android.rb.addressbook.framework.jar
    • jp.co.sharp.android.rb.projector.framework.jar
    • jp.co.sharp.android.voiceui.framework.jar
  3. Jarのビルドアクションを変更
    InputJarからEmbeddedJarに変更
    EmbeddedJar・・・dllにjarを埋め込む
    InputJar・・・jarを外部参照ロボホンで実行可能な状態にしておく

  4. プロジェクトのプロパティでAPIレベルを変更
    ロボホンなのでAndroid5.0 (Lolipop)にしました

  5. ビルドしてエラーが出なければ完了(DLLが作成されます)
    エラーが出ました。

Step3:Binding a Java Libraryプロジェクトのビルドエラー対応

発生したビルドエラーは6件ですべてCS0535でした。
CS0535 C# はインターフェイス メンバー を実装しません。

調べると解決策がありました。
stackoverflow:Xamarin jar binding error: Class does not implement interface member
xamarin.com:Java Bindings Metadata

Xamarin公式でも紹介されている代表的なエラーのようです。
原因はBindings GeneratorがJavaの戻り値の型を間違えて推測した場合に発生すると書いてあります。

Bindings GeneratorとはBinding a Java Libraryが内部で利用しているツールで、
Jarを解析してAPI一覧を自動生成してくれるものです。

解決方法は戻り値の型を変更しろというもので、
その方法はMetadata.xmlに修正方法を記載することになります。
Metadata.xmlは自動生成されたAPI一覧に変更を加えることができるものです。

実際に記載する内容は下記のようになります。
XPathでAPIリストの修正箇所を指定します。
name=managedTypeはメソッドの戻り値を変更する場合の指定で、
戻り値をJava.Lang.Objectに変更するという意味になります。

Metadata.xml
<metadata>
<attr path="/api/package[@name='jp.co.sharp.android.rb.addressbook.AddressBookVariable']/
      class[@name='AddressBookData']/method[@name='writeToParcel'
      and count(parameter)=2 
      and parameter[1][@type='android.os.Parcel']/parameter[1]
      and parameter[2][@type='Java.Lang.Integer']]/parameter[2]"
  name="managedType">Java.Lang.Object</attr>
</metadata>

しかし上記の方法では解決しませんでした。
むしろ余計にエラーが増えました。

そこでMetadata.xmlの記述方法を調査して2つの解決方法を考えました。
そもそもの原因は必要なメンバーを実装していないことです。

方法1:そんなメンバーはなかったんや
 remove-nodeを使ってエラーが出た箇所をごっそり削除します。

Metadata.xml
  <remove-node path="/api/package[@name='jp.co.sharp.android.rb.addressbook.AddressBookVariable']/class[@name='AddressBookData']" />

方法2:メンバーちゃんとありますよ
 add-nodeを使って必要なメンバーがあるように見せます

Metadata.xml
  <add-node path="/api/package[@name='jp.co.sharp.android.rb.addressbook.AddressBookVariable']">
    <class abstract="false" deprecated="not deprecated" final="false" name="AddressBookData" static="false" visibility="public" extends="java.lang.Object">
      <method name="describeContents" visibility="public" static="false" final="false" deprecated="not deprecated" abstract="false" synchronized="false" return="int" native="false"> </method>
    </class>
  </add-node>

どちらの方法でもとりあえずビルドエラーは無くなりますが
方法1では消してしまうので利用出来なくなります
方法2では使えますが、正しく動作するかわかりません(未確認ですみません)

その他の解決方法として自前でC#コードで追加処理を書くこともできますが
SDKの実装など知る由が無いので上記の対応にとどめています

Step4:作成したDLLを使う

Xamarin.Androidのプロジェクトに上記のプロジェクトを参照するだけです。
Using the Bindings Library

  1. Xamarin.Androidプロジェクトを作成
  2. 上記JarBindingプロジェクトを参照
  3. プロパティでAndroidのバージョンを変更

注意点としてはJavaからC#に変換されるにあたり
javaのgetter/setterがC#のプロパティに変更されていたり
名前空間が大文字に変更されていたりします
Adapting Java APIs to C

この点に関しては既存のXamarin.Androidと同じ具合なので問題ないかと思います

あとはロボホンのお作法にそってコーディングしていきます

Step5:ロボホンテンプレートをC#に変換

ロボホンにはAndroidStudio用のテンプレートプロジェクトが用意されています
今回はXamarin化にあたって、このテンプレートプロジェクトをC#化しました

単純にJavaのコードを導入したライブラリとC#コードに置き換えて行くだけとなります
ここでは困ったところなどを紹介します

  • IJavaObject.HandleとIDisposable.Dispose()が実装されていないというエラー
    クラスにJava.Lang.Objectを継承すればよいだけだったが最初全く分からなかった

  • privateなコンストラクタ定義
    C#でも使えるが普通使わないと思う
    単純に静的クラスとして利用したいクラスだったのでclassにstatic付けて終わり
    Javaはclassにstaticつけられないのでこんな実装の模様

  • メソッド引数の型の前についたfinal
    引数にfinalがついていた。final付けると値が変更できなくなる
    値渡し(参照渡し禁止)という意味だと思うがC#でちゃんと実装するのは面倒なのでfinalとって終了

  • Java.IO.InputStreamからSystem.IO.Streamへの変換
    JavaだとInputStreamを返却するメソッドがStreamに変わっていたので実装を修正する必要があった

  • Javaのインターフェース名称による混同
    例えばJavaのListだがC#にもListはある
    Javaのコードをそのままコピペして修正していたので最初気が付かなかった
    JavaのListは実はインターフェースなのでC#だとIListにしないといけない
    JavaのインターフェースにはIがついていないので注意

  • AndroidManifest.xmlの移植
    そのままで動くかと思ったが動かなかった
    IntentFilter属性やBroadcastReceiver属性に変換していく必要があった
    なぜそのままで動かなかったのか不明

  • Javaがインナークラスからアウタークラスのオブジェクトを直接参照していた
    C#ではそんなことできないのでアウタークラスのインスタンスをstaticで定義した
    この修正方法はかなり危ないと思う、主にメモリ管理の面で

Step6:いざ実行

このステップは完全にロボホンの話になるので読み飛ばしていただいても構いません

ロボホン実機にデプロイして実行することができましたが
そのままのコードでは音声発話処理が動作しませんでした
ロボホンにおいて音声発話処理はHVMLというXMLファイルで管理されており
アプリインストール時に自動で本体にコピーされて実行可能な状態となりますが
自動でコピーされませんでしたので手動でコピーする処理を呼び出すようにしました

プロジェクターは無事動作しました
アドレス帳ライブラリについては動作未確認です

結論と総括

  • Xamarin.Androidでロボホンを動かすことは出来ましたがライブラリの完全な動作確認は行っていないので不都合が発生する可能性はあります
  • 公式サポートが受けられない為、ロボホンをC#でやる実用性は無いと考えます
  • ロボホンはGoogleアカウントが使えない仕様であり、Sharp公式のアプリストアで公開する必要があり、Xamarin版のアプリが公開できるかどうかも不明です
  • アプリ本体とは別にMonoSharedRuntime(43.68MB)とXamarin.Android API(29.88MB)が入ってくるので容量も大きくなってきます
  • Binding a Java Library自体について、便利ですがビルドエラーが出ないことは稀のよう(JavaとC#の設計が違うので完全な自動変換なんてできない)なので導入の敷居はそれなりに高いかと考えます

コード一式
https://github.com/ta-yamaoka/RobohonTemplate