3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クソアプリAdvent Calendar 2023

Day 4

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

Last updated at Posted at 2023-12-12

筆者は、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 という仕組みでデータのやりとりをします。

ファイル処理

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は、フルパスで管理しており、ファイル名とフォルダのフルパスを分けて管理していました。それに対し、ファイルピッカーで取得した場合は、

  1. ファイル名の先頭に「.」をつける
  2. フォルダには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ストアアプリになります。チェックは、リリースごとに毎回いれる必要があります。

(new2).png

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?