Windows 11 ストアくそアプリは、Amazonで作ると簡単

筆者は、9VAeきゅうべえという無料のモーショングラフィックスアプリを開発しており、[Windows / Mac / Linux / iOS / Android / Amazon Fire 版を提供](https://9vae.blogspot.com/p/9vae-download.html)しています。スマホ版はアプリストアからインストールできますが、パソコン版は、ZIPによる実行形の提供です。かつて、[C++/WinRT をつかったストアアプリの作成](https://dnjiro.hatenablog.com/entry/2021/08/24/110356)を試みましたが失敗しました。

ところが、2023年、Android用Windowsサブシステムをつかって、9VAeの Amazon Fire版がストアアプリになりました。と思ったら、[あっさり終了(2025年3月5日)](https://developer.amazon.com/ja/apps-and-games/blogs/2024/03/amazon-appstore-on-windows-11-discontinued?cmp=JP_2024_03_ESM_Win11-deprecation&ch=eml&chlast=eml&pub=eso&publast=eso&type=org&typelast=org) orz

- [9VAeきゅうべえ Win11ストア版ダウンロードはこちら](https://apps.microsoft.com/store/detail/9vae%E3%81%8D%E3%82%85%E3%81%86%E3%81%B9%E3%81%88%EF%BC%9A%E3%82%A4%E3%83%A9%E3%82%B9%E3%83%88%E3%81%8B%E3%82%89gifmp4%E5%8B%95%E7%94%BB%E4%BD%9C%E6%88%90/XPDDVNQTKLMJP2?hl=ja-jp&gl=jp&icid=CNavAppsWindowsApps)


<iframe height="512" width="288" src="https://www.youtube.com/embed/veq_IkXQ5ZI" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

最初に登場した、Android用Windowsサブシステムはサンドボックスになっており、アプリとWindowsのやりとりができなかったのですが、詳細設定の「試験的な機能」をONにすると、ユーザードキュメントフォルダへの読み書きが可能になり、Windowsアプリであるかのように動作します。

本記事では、Windows側ドキュメントと、Amazonアプリとのやりとりを解説します。

# DXライブラリ

9VAeきゅうべえAmazon Fire版は、[Android版と共通で、Dxライブラリで開発](https://dnjiro.hatenablog.com/entry/2019/12/11/000255)されています。これを使った理由は、
- C 言語で開発できる
- 今でも国内で開発されており、日本語でメンテされている

DXライブラリは、もともと、Windowsの DirectX をターゲットにしており、Android OS 特有の処理は、Javaで記述する必要があります。
Java と C は、JNI という仕組みでデータのやりとりをします。

- [JNIの使い方が、DXライブラリサイト](https://dxlib.xsrv.jp/lecture/Android/Android_Java_AndroidStudio.html)にあります

# ファイル処理

Javaの中から、以下の方法でインテントを読み出すと、ファイルピッカーが起動します。その中に、Windows というフォルダがあり、Windowsユーザーフォルダが見えます。

ファイルを開く
```java
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);
}
```

ファイルを保存
```java
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);
}

```

ファイルピッカーで指定したファイルは以下のコールバック関数で取得できます
```java
@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は、フルパスで管理しており、ファイル名とフォルダのフルパスを分けて管理していました。それに対し、ファイルピッカーで取得した場合は、

1. ファイル名の先頭に「.」をつける
1. フォルダにはURIを文字列にしたものをいれる

としました。ファイル名の先頭が「.」の場合は、9VAeフォルダに一時ファイルを作成し、それを転送するという方法にしました。以下はその部分のコードです。

```java
	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アプリにしたときの記事が以下にあります

- [9VAeのAmazonストアアプリ開発](https://dnjiro.hatenablog.com/entry/2019/12/11/000255)
- [Windows11 ストアアプリの実機デバッグ](https://qiita.com/danjiro/items/406cf184fda8a9e1aa21)

Amazon Developer のアプリ登録の手順２：ターゲットアプリで、Windowsデバイスにチェックをいれれば、Microsoftストアアプリになります。チェックは、リリースごとに毎回いれる必要があります。

![(new2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67734/20514590-0786-6cec-9db3-7fbf3ccd874f.png)