android開発 ViewModel、LiveData、jcifs-ngを使ったSMBサーバーからのファイルダウンロード、進捗プログレスバー表示 (original) (raw)

2022 Nov. 23.
2021 Sep. 05.
2021 Sep. 04.

インストールしたandroid端末

コンパイル環境

compileSdkVersion 30
minSdkVersion 24
targetSdkVersion 30

android studioのプロジェクトのapp/libsにjcifs-ngライブラリを配置

jcifs-ng-2.1.6.jar

https://mvnrepository.com/artifact/eu.agno3.jcifs/jcifs-ng/2.1.6 のFilesのbundleをクリックしてダウンロード

bcprov-jdk15to18-1.69.jar

build.gradle(Module:app)

次の依存関係設定を記述する

implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0-alpha03"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0-alpha03"
implementation files('libs/jcifs-ng-2.1.6.jar')  <- app/libsに配置したバージョンに合わせる
implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.69'  <- app/libsに配置したバージョンに合わせる
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32'
implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.32'

全体

plugins { id 'com.android.application' id 'kotlin-android' }

android { compileSdkVersion 30 buildToolsVersion "30.0.3"

defaultConfig {
    applicationId "net.sytes.rokkosan.smbclient2"
    minSdkVersion 24
    targetSdkVersion 30
    versionCode 1
    versionName "1.0"

    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

buildTypes {
    release {
        minifyEnabled 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.lifecycle:lifecycle-livedata-ktx:2.4.0-alpha03" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0-alpha03" implementation files('libs/jcifs-ng-2.1.6.jar') implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.69' implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32' implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.32' implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.0' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' }

権限許可

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>

AndroidManifest.xml

xml version="1.0" encoding="utf-8"

<uses-permission androidname="android.permission.INTERNET"/>
<uses-permission androidname="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission androidname="android.permission.ACCESS_WIFI_STATE"/>

<application
    androidallowBackup="true"
    androidicon="@mipmap/ic_launcher"
    androidlabel="@string/app_name"
    androidroundIcon="@mipmap/ic_launcher_round"
    androidsupportsRtl="true"
    androidtheme="@style/Theme.SmbClient2">
    <activity androidname=".MainActivity">
        <intent-filter>
            <action androidname="android.intent.action.MAIN" />

            <category androidname="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

MainActivity.kt

import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.widget.ProgressBar import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import net.sytes.rokkosan.smbclient2.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

val DOMAIN: String = "192.168.1.1"
val SMBROOT: String = "/SMB/SERVER/FILE/"
val USER: String = ""
val PASSWORD: String = ""


lateinit var androidPath: String
lateinit var smbViewModel: SmbViewModel

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

    val binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    var isSmb: Boolean = false
    binding.progBarHori.max = 100

    
    var smbUrl = "smb://" + DOMAIN + SMBROOT
    if (smbUrl.takeLast(1) != "/") {
        smbUrl += "/"
        
    }

    
    androidPath = externalCacheDir!!.absolutePath

    
    val smbViewModelFactory = SmbViewModelFactory(USER, PASSWORD, DOMAIN, smbUrl, androidPath)
    smbViewModel = ViewModelProvider(this, smbViewModelFactory).get(SmbViewModel::class.java)

    
    val pathObserver = Observer<String> { sPath ->
        
        binding.tvSmbPath.text = sPath
    }
    val sizeObserver = Observer<Long> { sSize ->
        
        binding.tvSmbSize.text = (sSize / 1000L).toString() + "KB"
    }
    val downloadedRatioObserver = Observer<Int> { dRatio ->
        
        binding.progBarHori.progress = dRatio
    }
    val isConnectedObserver = Observer<Boolean> { isConnected ->
        if (!isConnected) {
            isSmb = false
            binding.tvInfo01.setText(R.string.failedConnectSmb)
        } else {
            isSmb = true
            
            
            smbViewModel.smbPath.observe(this, pathObserver)
            smbViewModel.smbSize.observe(this, sizeObserver)

            
            smbViewModel.downloadedRatio.observe(this, downloadedRatioObserver)
            binding.progBarHori.visibility = ProgressBar.VISIBLE
        }
    }

    
    binding.tvInfo01.setText(R.string.wait)
    smbViewModel.downloadSmbFile()

    
    smbViewModel.isConnectedSmb.observe(this,isConnectedObserver)
    if (!isSmb) {
        return
    }
}

}

SmbViewModel.kt

import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import jcifs.config.PropertyConfiguration import jcifs.context.BaseContext import jcifs.smb.NtlmPasswordAuthenticator import jcifs.smb.SmbFile import kotlinx.coroutines.* import java.io.File import java.io.FileOutputStream import java.util.*

class SmbViewModel(val user: String, val password: String, val domain: String, val smbUrl: String, val androidPath: String): ViewModel() {

private val _smbPath: MutableLiveData<String> by lazy {
    
    MutableLiveData<String>()
}
val smbPath: LiveData<String>
    
    get() = _smbPath

private val _smbSize: MutableLiveData<Long> by lazy {
    MutableLiveData<Long>()
}
val smbSize: LiveData<Long>
    get() = _smbSize

private val _isConnectedSmb = MutableLiveData<Boolean>()
val isConnectedSmb: LiveData<Boolean>
    get() = _isConnectedSmb

private var _downloadedRatio: MutableLiveData<Int> = MutableLiveData<Int>().also {
        mutableLiveData -> mutableLiveData.value = -1
}
public val downloadedRatio: LiveData<Int> get() = _downloadedRatio


fun downloadSmbFile() {
    viewModelScope.launch(Dispatchers.IO) {
        lateinit var smb: SmbFile

        
        try {
            val prop = Properties()  
            prop.setProperty("jcifs.smb.client.minVersion", "SMB202")
            prop.setProperty("jcifs.smb.client.maxVersion", "SMB300")
            val baseCxt = BaseContext(PropertyConfiguration(prop))
            val auth =
                baseCxt.withCredentials(NtlmPasswordAuthenticator(domain, user, password))
            smb = SmbFile(smbUrl, auth)
            if (!smb.exists()) {
                _isConnectedSmb.postValue(false)
                return@launch
            }
        } catch (e: Exception) {
            _isConnectedSmb.postValue(false)
            return@launch
        }
        _isConnectedSmb.postValue(true)
        
        
        _smbPath.postValue(smb.path)
        _smbSize.postValue(smb.length())

        
        val fileName = smb.name
        val fileSize = smb.length()
        val exterCacheFile = File(androidPath + "/" + fileName)
        val inStream = smb.openInputStream()
        val fileOutStream = FileOutputStream(exterCacheFile)
        val buf = ByteArray(1024)
        var len = 0
        var downloadedSize = 0L
        while (true) {
            len = inStream.read(buf)
            if (len < 0) {
                break
            }
            downloadedSize += len.toLong()
            _downloadedRatio.postValue((downloadedSize * 100L / fileSize).toInt())
            fileOutStream.write(buf)
        }
        fileOutStream.flush();
        fileOutStream.close();
        inStream.close()

        smb.close()
    }
}

override fun onCleared() {
    super.onCleared()
    viewModelScope.cancel()
}

}

SmbViewModelFactory.kt

import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider

class SmbViewModelFactory(val user: String, val password: String, val domain: String, val smbUrl: String): ViewModelProvider.Factory { override fun create(modelClass: Class): T { return SmbViewModel(user, password, domain, smbUrl) as T } }