Sibainu Relax Room

柴犬と過ごす

Android contentResolverについて

2月11日に儺追神事(はだか祭)標柱建式が行われました。

これは昨日の柴犬です。水仙の花の根元をしきりに気にしていました。この前、テンと遭遇しましたがほかの動物の匂いが気になるのかもしれません。

概要

ファイルの InputStream を作るのにわざわざファイルの Path が必要なのか疑問に感じていました。

Uri が判っているなら Uri から作れないだろうかと思っていました。

Uri から InputStream が作成でき NAS に保存できましたので記録します。

WEBのみでは断片的で覚えにくいのでKotlin の本を新しく購入しました。

contentResolver

Android Developers の WEBの Public methods のなかに openInputStream(Uri uri) というメッソドがありますのでこれを利用してみます。

https://developer.android.com/reference/android/content/ContentResolver

openInputStream(Uri uri) の扱い方は次の同じく Android Developers のWEBのものを参考にしました。

https://developer.android.com/training/data-storage/shared/documents-files?hl=ja

大いに参考になりましたコードは次のものです。

copy

    private fun readTextFromUri(uri: Uri): String {
        val stringBuilder = StringBuilder()
        contentResolver.openInputStream(uri)?.use { inputStream2 ->
            BufferedReader(InputStreamReader(inputStream2)).use { reader2 ->
                var line: String? = reader2.readLine()
                while (line != null) {
                    stringBuilder.append(line)
                    line = reader2.readLine()
                }
            }
        }
        return stringBuilder.toString()
    }

{}ブロックの中で、T を引数で受け参照するときは it にするものとばかり思っていました。

ところが、ラムダ関数の引数にする場合は、識別できれば inputStream2、 reader2 としても AndroidStudio のコードエディターのなかではエラーになりませんでした。

今回は確認しませんが、機会があればほんとうにそうなのか確認したいと思います。

use スコープ関数

上のコードで初めて use なるものを知りましたので調べてみます。

use を JET BRAINS のWEBで調べてみると次のような定義になっています。

inline fun <T : Closeable?, R> T.use(block: (T) -> R): R

リソース(T)に対して指定された [block] 関数を実行し、例外がスローされるかどうかに関係なくリソース(T)を正しく閉じます。

とありますので、Closeable なオブジェクトであれば {} ブロック内に it.close() の記述が必要なさそうです。

JET BRAINS
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/use.html"

InputStream も Closeable なオブジェクトなのでOKなんですね。

onImageSaved()

これまでの以下のようなコードが

                override fun
                        onImageSaved(output: ImageCapture.OutputFileResults) {
                    val msg = "Photo capture succeeded: ${output.savedUri}"
                    Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                    Log.d(TAG, msg)

                    //val projection = null    // 読み込む列の指定
                    // パフォーマンスとしては指定した方がよい
                    val projection = arrayOf(MediaStore.MediaColumns.DATA)
                    val selection = null     // 行の絞り込みの指定
                    val selectionArgs = null // selectionの?を置き換える引数
                    val sortOrder = null     // 並び順

                    //アプリケーションのデータベースに接続して URI を検索します
                    val cursor =
                        applicationContext.contentResolver.query(
                            Uri.parse(
                                output.savedUri.toString()
                            ), projection, selection, selectionArgs, sortOrder
                        )

                    cursor?.apply {
                        var path: String? = null
                        //最初のレコードにカーソルを移します
                        if (moveToFirst()) {
                            //データがあるカラム名は _data です。
                            val index: Int = getColumnIndex(MediaStore.MediaColumns.DATA)
                            path = getString(index)
                        }
                        close()

                        path?.let {
                            //パスが取得できたので File を作成します
                            val file = File(it)
                            //ftpDirectory は "ftp://192.168.□□.□□/" 不要
                            val filelist = FtpAccess().Await(
                                "□□□□",
                                "□□□□□□□□□□",
                                "192.168.□□.□□",
                                "□□□□□□/□□□□□□/",
                                file
                            )
                            findViewById<TextView>(R.id.tvinfo).text = filelist
                        }
                    }
                }

次のとおり簡素に書けます。

copy

                override fun
                        onImageSaved(output: ImageCapture.OutputFileResults) {
                    val msg = "Photo capture succeeded: ${output.savedUri}"
                    Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
                    Log.d(TAG, msg)
 
                    val uri = Uri.parse(output.savedUri.toString())
                    contentResolver.openInputStream(uri)?.use {
                        val filelist = FtpAccessB().Await(
                            "□□□□",
                            "□□□□□□□□□□",
                            "192.168.□□.□□",
                            "□□□□□□/□□□□□□/",
                            name + ".jpg",
                            it)
                        findViewById<TextView>(R.id.tvinfo).text = filelist
                    }
                }

クラス FtpAccessB

引数が増えた分行数が増え簡素にはなりませんでした。

copy

class FtpAccessB {
    // 非同期内でセットした文字列を返す
    fun Await(ftpUsername: String,
              ftpPassword: String,
              ftpServer: String,
              ftpDirectory: String,
              filename: String,
              ips: InputStream): String? {
        Log.d("Coroutine", "executeAwait start")
        var ayncString: String?
        //ここでコルーチンを使います
        runBlocking {
            ayncString = execute(ftpUsername,
                ftpPassword,
                ftpServer,
                ftpDirectory,
                filename,
                ips)
        }
        return ayncString
    }

    suspend fun execute(ftpUsername: String,
                        ftpPassword: String,
                        ftpServer: String,
                        ftpDirectory: String,
                        filename: String,
                        ips: InputStream): String? {
        //ファイルの一覧表を返します
        return withContext(Dispatchers.IO) {
            val infolist = mutableListOf<String>()
            val ftpClient = FTPClient()
            try {
                //デフォルト ポートでリモート ホストに接続され、システムに割り当てられたポートで現在のホストから発信されるソケットを開きます
                ftpClient.connect(ftpServer)

                //指定されたユーザーとパスワードを使用して FTP サーバーにログインします
                ftpClient.login(ftpUsername, ftpPassword)

                //データ転送を行うために接続するデータ ポートを開くようにサーバーに指示されます
                ftpClient.enterLocalPassiveMode()

                //多くの FTP サーバーはデフォルトで BINARY になっています
                ftpClient.setFileType(FTP.BINARY_FILE_TYPE)

                Log.d("ServerFileAccess", ftpClient.status)

                //ディレクトリ移動
                ftpClient.changeWorkingDirectory(ftpDirectory)

                //
                val filenames: Array<String> = ftpClient.listNames()
                for (i in filenames.indices) {
                    Log.d("ServerFileAccess", filenames[i])
                    infolist.add(filenames[i])
                }

                //NASにファイルをアップロード
                ftpClient.storeFile(filename, ips)

            } catch (e: Exception) {
                e.printStackTrace()
                infolist.add(e.printStackTrace().toString())
            } finally {
                Log.d("ServerFileAccess", "finally")
                infolist.add("処理を終了します")
                ftpClient.logout()
                ftpClient.disconnect()
            }
            infolist.joinToString("\n")
        }
    }
}

他のスコープ関数

let

JET BRAINS

inline fun <T, R> T.let(block: (T) -> R): R

T がラムダ関数の引数になっていますので {} ブロック内で T 自信を it で参照します。

apply

JET BRAINS

inline fun <T> T.apply(block: T.() -> Unit): T

T から発生する関数になっていますので {} ブロック内で T 自信を this で参照します。

run

JET BRAINS

inline fun <R> run(block: () -> R): R
inline fun <T, R> T.run(block: T.() -> R): R

T から発生する関数になっていいれば {} ブロック内で T 自信を this で参照します。

also

JET BRAINS

inline fun <T> T.also(block: (T) -> Unit): T

T がラムダ関数の引数になっていますので {} ブロック内で T 自信を it で参照します。

with

JET BRAINS

inline fun <T, R> with(receiver: T, block: T.() -> R): R

T から発生する関数になっていますので {} ブロック内で T 自信を this で参照します。

今回はここまでとします。