うにのトゲは刺さると痛い(´・ω・`)-39

それほど必要でもなかったのだが、Unity上でMediaStoreとContentResolverを使って画像を取得する方法を調べて2~3日くらいを潰す…

未だにまともに画像表示出来ていないが50%くらい目的を果たせたので取り敢えず、その苦闘の記録を残すとする(ヽ'ω`)

正直な所、Javaでプラグインを書いた方が早いような気がする(ノ∀`)

※尚、以下のコードは色んなところから切り貼りしたり、適当に書いている為に、変数名等は統一されていないw


AndroidJavaClassとかってよくわからない(´・ω・`)

まず第一にAndroidJavaClassやAndroidJavaObjectや、そのメソッドの呼び出しであるCallやCallStaticがよくわからずに躓く…__○_
どうもReflectionみたいな感じでJavaのオブジェクトを扱ってるのかなぁ?って感じか。

習うよりも慣れろというか、コード実行の成功という或る種の成功体験、つまりは自己の環境に問題がなく、そのコードの内容が完全ではなくてもある程度のレベルで実行出来るという正当性を確認してからじゃないと、中々コードの内容の理解が進まない俺氏なので、色々とぐぐって試した。

他にも見たサイトやコードはあったような気もするけれど、比較的楽に動作を確認できたのが下の4つ。

アラートダイアログの表示
【Unity】Androidのネイティブの機能を使う

インテントでブラウザを開く
UnityC# で完結できるAndroidプラグインを簡単に作っちゃうぞ!☆ミ

インテントでギャラリーを開く? その他色々 5番目のコードだったかな?
在unity中如何高效的使用内置android方法

これは何か書き換えたんだったか間違えたんかわからないけど、JsonUtility.ToJsonが"{}"しか吐かなかったので、普通に文字列化した日時を書き込むように変更して試した。実機デバッグでファイルが書き込まれること、catで内容を確認した。
UnityでAndroidの内部ストレージにファイルを保存する


動作するコードを確認した結果、なんとなくはわかってきたような気がする。

AndroidJavaClassは完全限定名を渡してnewするものみたい。
どういう仕組かようわからんけど、newしてるけど扱いとしてインスタンスじゃなくてクラスなのかな?
メソッド呼び出しもCallStaticというStaticメソッドだし…

AndroidJavaObjectは普通のインスタンスとしてCallでメソッド呼び出しする。
最初のStringがメソッド名でそれ以降が可変長引数のobject配列らしい。
ここの処理もどうしてるのかよくわからない。

多分、基本的に下の2行は常にやる感じ?
UnityPlayerオブジェクトと現在のアクティビティ(つまりはコンテキスト)を取得してる模様。

AndroidJavaClass up = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentActivity = up.GetStatic<AndroidJavaObject>("currentActivity");

関係ないが、完全限定名以外に正規名なんてのもあるのか(´・ω・`)
完全限定名と正規名とバイナリ名


ContentResolverやらMediaStoreで苦しむ

むかーし、Androidのページの翻訳もどきをちょこっとしてた時にContentProviderやContentResolverという言葉を目にしてたが、がっつりとそのクラスを使ったことがなかったので、色々と苦労した…(ヽ'ω`)

ContentResolver.query()等の抽出操作についての理解とそれをC#側から行う為の試行錯誤の為にえらく時間を要した…

最初に参考にしてたページ群で使用されていた画像系のcolumn名がAndroid10では使えなかったようで、それに気がつくまで時間がかかった…
何がいけないのかがわからなかったから(ノ∀`)

たまたま見かけた音楽系のタグを使用して、音楽タイトルの取得に成功した。
尚、このページの内容はよくわかっていないw
ロシア語は全くわからんw

参考?:
Плагин: сканирование устройства на наличие аудиофайлов

Unity完結での音楽タイトルの取得
    public void GetMusicMedia()
    {
        try
        {
            AndroidJavaClass up = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject currentActivity = up.GetStatic<AndroidJavaObject>("currentActivity");
            AndroidJavaObject contentMusicUri = new AndroidJavaClass("android.provider.MediaStore$Audio$Media").GetStatic<AndroidJavaObject>("EXTERNAL_CONTENT_URI"); 
            AndroidJavaObject contentResolver = currentActivity.Call<AndroidJavaObject>("getContentResolver");
            AndroidJavaObject cursor = contentResolver.Call<AndroidJavaObject>("query", contentMusicUri, null, null, null);

            int titleColumn = cursor.Call<int>("getColumnIndex", "android.provider.MediaStore.Audio.Media.TITLE");

            while (cursor != null && cursor.Call<bool>("moveToNext"))
            {
                string m = cursor.Call<string>("getString", new object[] { titleColumn });
                Debug.Log("m:" + m);
            }

            if (cursor != null)
            {
                cursor.Call("close");
            }
        }
        catch (System.Exception ex)
        {
            Debug.LogError(ex.Message);
        }
    }
ImageのタイトルやIDの取得
public void GetPicMedia()
    {
        try
        {
            AndroidJavaClass up = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject currentActivity = up.GetStatic<AndroidJavaObject>("currentActivity");
            AndroidJavaObject contentUri = new AndroidJavaClass("android.provider.MediaStore$Images$Media").GetStatic<AndroidJavaObject>("EXTERNAL_CONTENT_URI");
            AndroidJavaObject contentResolver = currentActivity.Call<AndroidJavaObject>("getContentResolver");

            AndroidJavaObject cursor = contentResolver.Call<AndroidJavaObject>("query", contentUri, null, null, null,null);


            int titleIndex = cursor.Call<int>("getColumnIndex", "android.provider.MediaStore.MediaColumns.Title");
            int IDIndex = cursor.Call<int>("getColumnIndex", "android.provider.MediaStore.MediaColumns._ID");
            while (cursor != null && cursor.Call<bool>("moveToNext"))
            {
              
                if (titleIndex != -1)
                {
                    Debug.Log("title:" + cursor.Call<string>("getString", new object[] { titleIndex }));
                }

                if (IDIndex != -1)
                {
                    Debug.Log("ID:" + cursor.Call<string>("getString", new object[] { IDIndex }));
                }

            }

            if (cursor != null)
            {
                cursor.Call("close");
            }
        }
        catch (System.Exception ex)
        {
            Debug.LogError(ex.Message);
        }
    }

EXTERNAL_CONTENT_URIの指定

ちなみにEXTERNAL_CONTENT_URIの指定でもはまった。
これはStack Overflowのページだったかどっかに書いてあったが、何処で見たかわからない(ノ∀`)
複数のページの情報を総合したのかもしれない。

AndroidJavaClass("android.provider.MediaStore$Audio$Media").GetStatic<AndroidJavaObject>("EXTERNAL_CONTENT_URI"); 

コラム名の取得

これはどんなcolumn名があるのか知りたくて調べたんだっけかな?
確かImageのDATAとかがDeprecatedで…

全cloumn名の取得
public void GetColumnsName()
    {
        try
        {
            AndroidJavaClass up = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject currentActivity = up.GetStatic<AndroidJavaObject>("currentActivity");
            AndroidJavaObject contentImageUri = new AndroidJavaClass("android.provider.MediaStore$Images$Media").GetStatic<AndroidJavaObject>("EXTERNAL_CONTENT_URI"); 
            AndroidJavaObject contentResolver = currentActivity.Call<AndroidJavaObject>("getContentResolver");
            AndroidJavaObject cursor = contentResolver.Call<AndroidJavaObject>("query", contentImageUri, null, null, null, null);

            string[] colNames = cursor.Call<string[]>("getColumnNames");

            foreach(string colName in colNames)
            {
                Debug.Log("colName: " + colName);
            }

            if (cursor != null)
            {
                cursor.Call("close");
            }
        }
        catch (System.Exception ex)
        {
            Debug.LogError(ex.Message);
        }
    }

参考:
Android開発: ”ContentResolver” とは何者か?


enumの取得

AndroidJavaClass BitmapConfig = new AndroidJavaClass("android.graphics.Bitmap$Config");
AndroidJavaObject RGB_F16 = BitmapConfig.GetStatic<AndroidJavaObject>("RGBA_F16");

参考:
How to access enum in Unity AndroidJavaClass


上手く行かなかったもの

ContentResolverへ渡すprojectionを作る方法を探して試してみたが、なんか駄目だった(´・ω・`)
やり方を変えたら上手く行くかもしれないが、

まあ、全部取得しちゃってもよくね( ゜σ・゚)ホジホジ?

という高度な政治的判断により、掘り下げることをやめた(ノ∀`)

配列引数作成?
private AndroidJavaObject javaArrayFromCS(string[] values)
    {
        AndroidJavaClass arrayClass = new AndroidJavaClass("java.lang.reflect.Array");
        AndroidJavaObject arrayObject = arrayClass.CallStatic<AndroidJavaObject>("newInstance", new AndroidJavaClass("java.lang.String"), values.Length);
        for (int i = 0; i < values.Length; ++i)
        {
            arrayClass.CallStatic("set", arrayObject, i, new AndroidJavaObject("java.lang.String", values[i]));
        }

        return arrayObject;
    }

参考:
AndroidJavaObject.Call array passing error (Unity for Android)
Setting fields of an android java object from unity C#, including array


Bitmap => byte配列

immutableだかなんかとエラーが出たのでcopyをしたが、これが本当に正しい対処かどうかわからない。

AndroidJavaClass BitmapConfig = new AndroidJavaClass("android.graphics.Bitmap$Config");
AndroidJavaObject RGB_F16 = BitmapConfig.GetStatic<AndroidJavaObject>("RGBA_F16");
bitmap = bitmap.Call<AndroidJavaObject>("copy", RGB_F16,true);

Bitmapをbyte配列にする

これで一応Byte配列になるような感じなのだが、

AndroidJavaClass ByteBuffer = new AndroidJavaClass("java.nio.ByteBuffer");
AndroidJavaObject byteBuffer = ByteBuffer.CallStatic<AndroidJavaObject>("allocate", bitmap.Call<int>("getByteCount"));
bitmap.Call("copyPixelsToBuffer", byteBuffer);
byte[] bmparr = byteBuffer.Call<byte[]>("array");

その後、以下のようにしてTextureを作成して、

Texture2D texture = new Texture2D(width, height);
texture.LoadImage(bmparr);
texture.Apply();

TextureをSprite.CreateでスライトとしてImageに突っ込んで上手くいかなかった(ヽ'ω`)

なんか反転して、色が赤だけで他の部分は透過して描画されていたような…
もう少し頑張って駄目だったら、素直にJavaでプラグインを作るか…

つーかそもそも必須な機能じゃないんだよな、今調べてることってw

参考:
AndroidでのBitmap/JPEG/byte配列の相互変換


いちいち[デバッグ]メニューをクリックしてから[Unityデバッガーのアタッチ]をクリックするのがめどい(´・ω・`)

普通に設定で表示出来るのかなと思ったが、ちょっと一手間必要だった。
なんで「コマンドの追加」で選択する時の表示名が"Unityデバッガーのアタッチ"ではなくて"cmdldUnityAttach"なのだろうか。
わかりにくい(´・ω・`)

まあでもツールバー上に追加出来たので良しとする。

参考:
VisualStudio2019でUnityデバッガーのアタッチをツールバーに置く


調べ物に疲れてしまったのと日銭を稼ぎに行かないといけないので、しばらく更新は途絶えるかもしれない(ノ∀`)