Sibainu Relax Room

愛犬の柴犬とともに過ごす部屋

Android CameraX SMBにてNAS接続1

今日の朝の散歩です。桜の木の枝の細いところまで雪が付着して幻想的な景色でした。これに見とれている(不思議に思っている)柴犬です。

概要

ファイルのアップロードは、ネットワークの状況や、アップロードするファイルの数やサイズによって、 どのくらい時間がわかりません。

なので、ファイルのアップロード処理は同期処理で行うより、非同期で行うほうがいいようです。

非同期処理というのは、処理を開始したら、その場で処理の終了を待たずに、なんらかの方法で後で処理が終了したことの通知を受けるような仕組みのことです。

Android の場合、非同期でコードを書かないと動かないこともありますので、コルーチンを使います。

依存関係を整理します。

依存Module

SMB関係

モジュールは jcifs-ng-2.1.10.jar で SMB2 と SMB3 をサポートします。

jcifs-ng-2.1.10.jar
ダウンロード先
      https://repo1.maven.org/maven2/eu/agno3/jcifs/jcifs-ng/2.1.10/

コルーチン関係

コルーチンで必要なモジュールは何が必要か分かりません。とにかく次のものを追加しました。

dependencies {

    // コルーチン
    val kotlin_version = "1.3.9"
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlin_version}")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${kotlin_version}")
    val lifecycle_version = "2.7.0"
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${lifecycle_version}")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:${lifecycle_version}")
}

build.gradle.kts (Module :app)

dependencies の部分のみ抜き出しました。

copy

dependencies {

    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    // FTP Apatche
    implementation(files("libs/commons-net-3.10.0.jar"))

    // SMB
    implementation(files("libs/jcifs-ng-2.1.10.jar"))

    // コルーチン
    val kotlin_version = "1.3.9"
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlin_version}")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${kotlin_version}")
    val lifecycle_version = "2.7.0"
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${lifecycle_version}")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:${lifecycle_version}")

    // camerax1
    val camerax_version = "1.1.0-beta01"
    implementation("androidx.camera:camera-core:${camerax_version}")
    implementation("androidx.camera:camera-camera2:${camerax_version}")
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    implementation("androidx.camera:camera-video:${camerax_version}")

    implementation("androidx.camera:camera-view:${camerax_version}")
    implementation("androidx.camera:camera-extensions:${camerax_version}")
}

コルーチン(Coroutine)の作成

Coroutineの作成にはビルダーを使用します。現在ビルダーは3種類あるようで、まとめると次の表のようになりました。

ビルダー 概要 使う場面
launch関数 現在のスレッドをブロックせずに新しいコルーチンを起動し、コルーチンへの参照をJobとして返します。実行中のコルーチンは、進行がキャンセルされるとキャンセルされます。 mainスレッドの実行を止めることできません。新しいスレッドを開始するのと似ています。
async関数 結果をDeferredとして返します。実行中のコルーチンは、進行がキャンセルされるとキャンセルされます。 mainスレッドの実行を止めることできません。launch関数とにていますが、値を返します。awaitという中断メソッドがあり完了すると値を返します。
runBlocking関数 結果をDeferredとして返します。実行中のコルーチンは、進行がキャンセルされるとキャンセルされます。 コルーチンが完了するまで呼び出し出し元のスレッドのブロッキングが必要な場合もあります。そうしないと、プログラムが早く終了しては困る場合に使用します。

今回は launch() 関数の使用

簡単なプロジェクト sibainuapp を作ってテストすることにします。

copy

package siba.inu.android.sibainuapp

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Button
import kotlinx.coroutines.launch
import java.util.Properties
import jcifs.context.BaseContext
import jcifs.smb.NtlmPasswordAuthenticator

class MainActivity : AppCompatActivity() {
    // コルーチン関連

    private val sibainujob: kotlinx.coroutines.Job = kotlinx.coroutines.Job()

    private val sibainuscope = kotlinx.coroutines.CoroutineScope(kotlinx.coroutines.Dispatchers.Default + sibainujob)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        initstart()
    }

    private fun initstart() {
        findViewById<Button>(R.id.button).setOnClickListener() {
            // 非同期処理
            sibainuscope.launch() {
                NasAccess("user",
                          "password",
                          "domain",
                          "smbroot")
            }
        }
    }

    //ジョブとそのすべての子を含むこのスコープをキャンセル
    override fun onDestroy() {
        sibainujob.cancel()
        super.onDestroy()
    }

    //smbrootは "smb://ドメイン名/共有フォルダー名/"という感じで最後に「/」を付けます。
    private fun NasAccess(user: String,
                          password: String,
                          domain: String,
                          smbroot: String) {
        var prop = Properties()
        prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
        prop.setProperty("jcifs.smb.client.maxVersion", "SMB311")
        var bc = BaseContext(jcifs.config.PropertyConfiguration(prop))
        var creds = NtlmPasswordAuthenticator(domain, user, password)
        var auth = bc.withCredentials(creds)

        try {
            var sf = jcifs.smb.SmbFile(smbroot, auth)
            if (sf.exists()) {
                // 処理1
            } else {
                // 処理2
            }
        }catch (ex: Exception) {
            // 処理3
        } finally {
            // 処理4
        }
    }
}

テストしてみたところエラーとなりました。ここでかなりの時間を要することとなりました。

jcifs-ng 必須モジュール

jcifs-ng を動かすためには次の2つのモジュールが必須のようです。

Bouncy Castle Provider

https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on

SLF4J API Module

https://mvnrepository.com/artifact/org.slf4j/slf4j-api

この2つのモジュールを依存関係に入れずにテストしたところ、次のコードでエラーになりました。

原因が全く分からず、いろいろWEBのHPをあたりながら2日ほどバージョンを変えるなど試行錯誤しました。

var bc = BaseContext(jcifs.config.PropertyConfiguration(prop))

今考えれば JAVA の Properties を扱うんですから何らかの手当てが必要だったんだ思っている次第です。

現在のbuild.gradle.kts (Module :app)の全体

copy

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "siba.inu.android.cameraxapp3"
    compileSdk = 34

    defaultConfig {
        applicationId = "siba.inu.android.cameraxapp3"
        minSdk = 26
        targetSdk = 33
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = "1.8"
    }

    buildFeatures {
        viewBinding = true
    }

}

dependencies {

    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    // FTP Apatche
    implementation(files("libs/commons-net-3.10.0.jar"))
    // SMB
    implementation(files("libs/bcprov-jdk15on-1.70.jar"))
    implementation(files("libs/slf4j-api-2.0.11.jar"))
    implementation(files("libs/jcifs-ng-2.1.10.jar"))
    // コルーチン
    val kotlin_version = "1.3.9"
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlin_version}")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:${kotlin_version}")
    val lifecycle_version = "2.7.0"
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${lifecycle_version}")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:${lifecycle_version}")
    // camerax1
    val camerax_version = "1.1.0-beta01"
    implementation("androidx.camera:camera-core:${camerax_version}")
    implementation("androidx.camera:camera-camera2:${camerax_version}")
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    implementation("androidx.camera:camera-video:${camerax_version}")

    implementation("androidx.camera:camera-view:${camerax_version}")
    implementation("androidx.camera:camera-extensions:${camerax_version}")
}

今回は、2つのモジュールのことが分かるまでかなり手間取りました。

この件は、これで終わりとします。