筆者は、9VAeきゅうべえという無料のモーショングラフィックスアプリを開発しており、Windows / Mac / Linux / iOS / Android / Amazon Fire 版を提供しています。スマホ版はアプリストアからインストールできますが、パソコン版は、ZIPによる実行形の提供です。かつて、C++/WinRT をつかったストアアプリの作成を試みましたが失敗しました。
ところが、2023年、Android用Windowsサブシステムをつかって、9VAeの Amazon Fire版がストアアプリになりました。と思ったら、あっさり終了(2025年3月5日) orz
最初に登場した、Android用Windowsサブシステムはサンドボックスになっており、アプリとWindowsのやりとりができなかったのですが、詳細設定の「試験的な機能」をONにすると、ユーザードキュメントフォルダへの読み書きが可能になり、Windowsアプリであるかのように動作します。
本記事では、Windows側ドキュメントと、Amazonアプリとのやりとりを解説します。
DXライブラリ
9VAeきゅうべえAmazon Fire版は、Android版と共通で、Dxライブラリで開発されています。これを使った理由は、
- C 言語で開発できる
- 今でも国内で開発されており、日本語でメンテされている
DXライブラリは、もともと、Windowsの DirectX をターゲットにしており、Android OS 特有の処理は、Javaで記述する必要があります。
Java と C は、JNI という仕組みでデータのやりとりをします。
- JNIの使い方が、DXライブラリサイトにあります
ファイル処理
Javaの中から、以下の方法でインテントを読み出すと、ファイルピッカーが起動します。その中に、Windows というフォルダがあり、Windowsユーザーフォルダが見えます。
ファイルを開く
private static final int PICK_X_FILE = 2;
private void openFile(Uri pickerInitialUri, String fType) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType(fType);
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);
Cret_x_FileUri=null;// 入力されたかどうかのフラグ
startActivityForResult(intent, PICK_X_FILE);
}
ファイルを保存
private static final int CRET_X_FILE = 3;
private void createFile(Uri pickerInitialUri, String fType) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType(fType);
intent.putExtra(Intent.EXTRA_TITLE, LblORGFILE);
Cret_x_FileUri=null;// 入力されたかどうかのフラグ
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);
startActivityForResult(intent, CRET_X_FILE);
}
ファイルピッカーで指定したファイルは以下のコールバック関数で取得できます
@Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
if ((requestCode == PICK_X_FILE || requestCode == CRET_X_FILE) && resultCode == Activity.RESULT_OK) {
Uri uri = null;String path;
if (resultData != null) {
uri = resultData.getData();
path=uri.getPath();
if(requestCode == CRET_X_FILE) Cret_x_FileUri = uri;//保存後に転送用
Context context = this.getApplicationContext();
String scheme = uri.getScheme();
String name = null; path = null;
if ("file".equals(scheme)) {
name = new File(uri.getPath()).getName();
} else if("content".equals(scheme)) {
String[] projection ={
MediaStore.MediaColumns.DISPLAY_NAME};
Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
name = cursor.getString(0);
}
cursor.close();
}
}
InputString = sdPath9VAe()+"/"+name;
if( name.endsWith(".svg")||name.endsWith(".SVG")) {
InputString = sdPath9VAe() + "/." + name;
}
try { //getNewFile:同名ファイルがあると(n)をつける
String newName = getNewFile(InputString);
if (newName!=null) InputString=newName;
if (requestCode == PICK_X_FILE) {
if (newName != null) {
InputStream strm = context.getContentResolver().openInputStream(uri);
DataInputStream daIn = new DataInputStream(strm);
DataOutputStream daOut = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream(InputString)));
byte[] b = new byte[4096]; int rByte = 0;// Read Data
while (-1 != (rByte = daIn.read(b))) { daOut.write(b, 0, rByte);}
daIn.close(); daOut.close();
}
}
PathString = InputString;
if(InputString.contains("/.")) {
PathUriString = uri.toString();
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
ファイル情報は、URI形式で取得できます。
String形式にして、JNIでC側に転送し、情報を記憶してます。
URI形式のデータは直接読み書きできないので、いったん一時ファイルに保存し、それを、JavaでURIに読み書きします。
一時ファイルは、必ず作成できないといけないので、ファイル名の先頭に「.」をつけることにし、その名前なら、使ったあと必ず消すようにしました。
初期の9VAeは、フルパスで管理しており、ファイル名とフォルダのフルパスを分けて管理していました。それに対し、ファイルピッカーで取得した場合は、
- ファイル名の先頭に「.」をつける
- フォルダにはURIを文字列にしたものをいれる
としました。ファイル名の先頭が「.」の場合は、9VAeフォルダに一時ファイルを作成し、それを転送するという方法にしました。以下はその部分のコードです。
if(PathUriString!=null) {
Cret_x_FileUri = Uri.parse(PathUriString);
}
if(Cret_x_FileUri!=null){ //Uriへデータを転送し作成したデータを削除する
try { Context context = this.getApplicationContext();
String frmName = sdPath9VAe()+"/"+msg;
InputStream smIn = new FileInputStream(frmName);
DataInputStream dIn = new DataInputStream(smIn);
OutputStream smOut = getContentResolver().openOutputStream(Cret_x_FileUri);
DataOutputStream dOut = new DataOutputStream(smOut);
byte[] b = new byte[4096]; int rByte = 0;// Read Data
while (-1 != (rByte = dIn.read(b))) { dOut.write(b, 0, rByte);}
dIn.close(); dOut.close();
if(msg.charAt(0)=='.') {
File dir = new File(frmName);
dir.delete(); //元の削除
}
Cret_x_FileUri=null;
}catch (Exception e) {
e.printStackTrace();Cret_x_FileUri=null;
}
}
Amazonストアアプリの作り方
9VAeをAmazonアプリにしたときの記事が以下にあります
Amazon Developer のアプリ登録の手順2:ターゲットアプリで、Windowsデバイスにチェックをいれれば、Microsoftストアアプリになります。チェックは、リリースごとに毎回いれる必要があります。