柴犬は、はだか祭りの準備が進んでいるのか気になるようです。
概要
なかなか進展がなかった cameraX で撮影した画像ファイルの NAS へのアップロードですができるようになりました。
URI の取得とコルーチンがカギになりました。
かなり勉強になりました。多分忘れ必要になるときがありますので記録します。
cameraX のCameraX のスタートガイドがなければURIの取得は無理でした。いろいろ教えていただきました。
2023年10月26日現在
次に紹介する本はちょっと過激な表紙ですが、本の内容はまじめてよく理解できる書き方をしています。先の「Androidアプリ開発」で飛ばしているところを丁寧に説明しているので、これで理解が早まりました。お勧めです。
しかもこれが100円で買え、ボリュームがすごい量です。なので著者に感謝です。
NASにアップロード
左が撮影時のスナップショットです。撮影前の NAS の保存先ホルダーのファイル一覧を取得して表示します。
右が撮影後 SMB をタップして NAS の保存先ホルダーのファイル一覧を取得して表示したものです。撮影したファイル(赤枠)が表示されて保存が確認できました。
撮影したファイル「2024-02-□□□□□□-05-712.jpg」が表示されました。
アップロードできた Synology の NASの状況です。
takePhoto() 関数
takePhoto() 関数の imageCapture.takePicture を修正しました。
imageCapture.takePicture(
outputOptions,
ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun
onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
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 = arrayOf(MediaStore.MediaColumns.DATA)
//アプリケーションのデータベースに接続して URI を検索します
val cursor =
applicationContext.contentResolver.query(
Uri.parse(
output.savedUri.toString()
), projection, null, null, null
)
if (cursor != null) {
var path: String? = null
//最初のレコードにカーソルを移します
if (cursor.moveToFirst()) {
//path はカラムのインデックスが 0 のようです
path = cursor.getString(0)
//スマホのテキストビューにパスを表示します
findViewById<TextView>(R.id.tvinfo).text = path
}
cursor.close()
if (path != null) {
//パスが取得できたので File を作成します
val file = File(path)
//ftpDirectory は "ftp://192.168.□□.□□/" 不要
val filelist = FtpAccess().Await(
"□□□□",
"□□□□□□□□□□",
"192.168.□□.□□",
"□□□□□□/□□□□□□/",
file
)
findViewById<TextView>(R.id.tvinfo).text = filelist
}
}
}
}
)
private fun takePhoto() { // Get a stable reference of the modifiable image capture use case val imageCapture = imageCapture ?: return // Create time stamped name and MediaStore entry. val name = SimpleDateFormat(FILENAME_FORMAT, Locale.US) .format(System.currentTimeMillis()) val contentValues = ContentValues().apply { put(MediaStore.MediaColumns.DISPLAY_NAME, name) put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image") } } // Create output options object which contains file + metadata val outputOptions = ImageCapture.OutputFileOptions .Builder( contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues ) .build() // Set up image capture listener, which is triggered after photo has // been taken imageCapture.takePicture( outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onError(exc: ImageCaptureException) { Log.e(TAG, "Photo capture failed: ${exc.message}", exc) } 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 = arrayOf(MediaStore.MediaColumns.DATA) //アプリケーションのデータベースに接続して URI を検索します val cursor = applicationContext.contentResolver.query( Uri.parse( output.savedUri.toString() ), projection, null, null, null ) if (cursor != null) { var path: String? = null //最初のレコードにカーソルを移します if (cursor.moveToFirst()) { //path はカラムのインデックスが 0 のようです path = cursor.getString(0) //スマホのテキストビューにパスを表示します findViewById<TextView>(R.id.tvinfo).text = path } cursor.close() if (path != null) { //パスが取得できたので File を作成します val file = File(path) //ftpDirectory は "ftp://192.168.□□.□□/" 不要 val filelist = FtpAccess().Await( "□□□□", "□□□□□□□□□□", "192.168.□□.□□", "□□□□□□/□□□□□□/", file ) findViewById<TextView>(R.id.tvinfo).text = filelist } } } } ) }
runBlocking
launch() 関数から作成したコルーチンの中で、接続したフォルダーのファイル一覧を得て、MainActivity のビューテキストに表示しようとしました。
launch() 関数から作成したコルーチンでいろいろ試しましたが、現在の私の力量ではコルーチンの中で作成したデータをメインスレッドに送ることはできませんでした。
ですので、メインスレッドをブロッキングする runBlocking で作成するコルーチンを使うことにします。
SmbAccess()、FtpAccess()関数をやめて、クラスに作り直しました。
クラス SmbAccess
ファイル一覧を表示します。
//fun initStart() から呼び出すようにしています class SmbAccess { // 非同期内でセットした文字列を返す fun Await(user: String, password: String, domain: String, smbroot: String): String? { var ayncString: String? //ここでコルーチンを使います runBlocking { ayncString = execute(user, password, domain, smbroot) } return ayncString } suspend fun execute(user: String, password: String, domain: String, smbroot: String): String?{ //ファイルの一覧表を返します return withContext(Dispatchers.IO){ val infolist = mutableListOf<String>() val prop = Properties() prop.setProperty("jcifs.smb.client.minVersion", "SMB202") prop.setProperty("jcifs.smb.client.maxVersion", "SMB311") val bc = BaseContext(jcifs.config.PropertyConfiguration(prop)) try { val auth = NtlmPasswordAuthenticator(domain, user, password) val cifsCon = bc.withCredentials(auth) val sf = SmbFile(smbroot, cifsCon) try { Log.d("ServerFileAccess", sf.server) Log.d("ServerFileAccess", sf.share) Log.d("ServerFileAccess", sf.name) Log.d("ServerFileAccess", sf.path) if (sf.exists()) { val filenames: Array<String> = sf.list() for (i in filenames.indices) { Log.d("ServerFileAccess", filenames[i]) infolist.add(filenames[i]) } Log.d("ServerFileAccess", "ファイル有") } else { Log.d("ServerFileAccess", "ファイル無") } } catch (ex: Exception) { Log.d("ServerFileAccess", ex.toString()) } finally { if (sf != null) { sf.close(); } Log.d("ServerFileAccess", "finally") } } catch (ex: Exception) { Log.d("ServerFileAccess", ex.toString()) infolist.add("接続できませんでした") } finally { infolist.add("処理を終了します") } //返される文字列です infolist.joinToString("\n") } } }
クラス FtpAccess
File が取得できれば、アップロードは次のような処理でできました。
//NASにファイルをアップロード
val inputStream = FileInputStream(file)
val fileName = file.name
//アップロードします
ftpClient.storeFile(fileName, inputStream)
inputStream.close()
//fun takePhoto() から呼び出すようにしています class FtpAccess { // 非同期内でセットした文字列を返す fun Await(ftpUsername: String, ftpPassword: String, ftpServer: String, ftpDirectory: String, file: File): String? { Log.d("Coroutine", "executeAwait start") var ayncString: String? //ここでコルーチンを使います runBlocking { ayncString = execute(ftpUsername, ftpPassword, ftpServer, ftpDirectory, file) } return ayncString } suspend fun execute(ftpUsername: String, ftpPassword: String, ftpServer: String, ftpDirectory: String, file: File): String? { //ファイルの一覧表を返します return withContext(Dispatchers.IO) { var 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にファイルをアップロード val inputStream = FileInputStream(file) val fileName = file.name ftpClient.storeFile(fileName, inputStream) inputStream.close() } catch (e: Exception) { e.printStackTrace() infolist.add(e.printStackTrace().toString()) } finally { Log.d("ServerFileAccess", "finally") infolist.add("処理を終了します") ftpClient.logout() ftpClient.disconnect() } infolist.joinToString("\n") } } }
initstart() 関数
private fun initstart() { findViewById<Button>(R.id.btLeft).setOnClickListener() { // 非同期処理 //smbroot は "sm://192.168.□□.□□/□□□□□□□□□□□□□□/" val filelist = SmbAccess().Await("□□□□", "□□□□□□□□□□", "192.168.□□.□□", "sm://192.168.□□.□□/□□□□□□/□□□□□□/" ) findViewById<TextView>(R.id.tvinfo).text = filelist } findViewById<Button>(R.id.btRight).setOnClickListener() { findViewById<TextView>(R.id.tvinfo).text = "FTP" // 非同期処理 //ftpDirectory は "ftp://192.168.□□.□□/" 不要 //FtpAccess("□□□□", // "□□□□□□□□□□", // "192.168.□□.□□", // "□□□□□□/□□□□□□/") } } }
フチベニベンケイの様子
黄1、黄2、緑3は先週と比較しますと葉っぱが大きくなりました。赤5も新しい芽はまだ見えていませんが元気な感じです。
緑4はかなり変化がありました。
芽が大きくなった感じがします。
もっと大きな変化は右上の側面からのアップから良くわかります。葉っぱが立ってきたことです。今後が楽しみです。
コルーチンの失敗例
今もコルーチンを十分に理解できていないのですが、はじめのころは次のようなコルーチンを書いていました。
実行するといきなり落ちました。
class FtpAccess {
var ayncString = ""
fun Await(ftpUsername: String,
ftpPassword: String,
ftpServer: String,
ftpDirectory: String,
file: File): String? {
Log.d("Coroutine", "executeAwait start")
ayncString = execute(ftpUsername,
ftpPassword,
ftpServer,
ftpDirectory,
file).toString()
return ayncString
}
fun execute(ftpUsername: String,
ftpPassword: String,
ftpServer: String,
ftpDirectory: String,
file: File): String? {
return runBlocking {
val deferred1 = async {
val infolist = mutableListOf<String>()
val ftpClient = FTPClient() << ここでエラーになっているようです
try {
~~省略~~
} catch (e: Exception) {
e.printStackTrace()
infolist.add(e.printStackTrace().toString())
} finally {
Log.d("ServerFileAccess", "finally")
infolist.add("処理を終了します")
ftpClient.logout()
ftpClient.disconnect()
}
infolist.joinToString("\n")
}
//runBlocking の戻り値 → execute の戻り値
deferred1.await()
}
}
次のように変えて実行してみました。
class FtpAccess {
var ayncString = ""
fun Await(ftpUsername: String,
ftpPassword: String,
ftpServer: String,
ftpDirectory: String,
file: File): String? {
Log.d("Coroutine", "executeAwait start")
ayncString = execute(ftpUsername,
ftpPassword,
ftpServer,
ftpDirectory,
file).toString()
return ayncString
}
fun execute(ftpUsername: String,
ftpPassword: String,
ftpServer: String,
ftpDirectory: String,
file: File): String? {
return runBlocking {
val deferred1 = async {
val infolist = mutableListOf<String>()
try {
val ftpClient = FTPClient() << これのみの記述です
} catch (e: Exception) {
e.printStackTrace()
infolist.add(e.printStackTrace().toString())
} finally {
Log.d("ServerFileAccess", "finally")
infolist.add("処理を終了します")
}
infolist.joinToString("\n")
}
deferred1.await()
}
}
エラーメッセージは次のメッセージでした。
Kotlin.Unit
このコルーチン構成では val ftpClient = FTPClient() できないようです。
今回はこれまでとします。