
柴犬は、はだか祭りの準備が進んでいるのか気になるようです。
概要
なかなか進展がなかった 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() できないようです。
今回はこれまでとします。