2022 Nov. 23.
2021 Sep. 05.
2021 Sep. 04.
インストールしたandroid端末
android バージョン 10
コンパイル環境
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
https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15to18/1.69 のFilesの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"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.sytes.rokkosan.smbclient2"> <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"/> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.SmbClient2"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
MainActivity.kt
// 2021 Sep. 05. // 2021 Aug. 29. 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() { ////////////////////////////////////////////// // Please replace these values to your data // 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 // prepare for connecting SMB server var smbUrl = "smb://" + DOMAIN + SMBROOT if (smbUrl.takeLast(1) != "/") { smbUrl += "/" // smbUrl文字列の末尾はスラッシュ(/)とする必要がある } // get the cache directory in android device androidPath = externalCacheDir!!.absolutePath // get ViewModel for SmbConnection val smbViewModelFactory = SmbViewModelFactory(USER, PASSWORD, DOMAIN, smbUrl, androidPath) smbViewModel = ViewModelProvider(this, smbViewModelFactory).get(SmbViewModel::class.java) // Create observers which updates the UI. val pathObserver = Observer<String> { sPath -> // Update the UI binding.tvSmbPath.text = sPath } val sizeObserver = Observer<Long> { sSize -> // Update the UI binding.tvSmbSize.text = (sSize / 1000L).toString() + "KB" } val downloadedRatioObserver = Observer<Int> { dRatio -> // Update the UI binding.progBarHori.progress = dRatio } val isConnectedObserver = Observer<Boolean> { isConnected -> if (!isConnected) { isSmb = false binding.tvInfo01.setText(R.string.failedConnectSmb) } else { isSmb = true /* * get data in SMB server * 参考ページ https://developer.android.com/topic/libraries/architecture/livedata?hl=ja */ // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. // observe(Owner, ...)のOwnerは、Activityの時はthis、Fragmentの時はviewLifecycleOwnerとする smbViewModel.smbPath.observe(this, pathObserver) smbViewModel.smbSize.observe(this, sizeObserver) // Observerインスタンスの生成はせず、下記のようにまてめてもよい /* smbViewModel.smbPath.observe(this, Observer { sPath -> binding.tvSmbPath.setText(sPath) }) smbViewModel.smbSize.observe(this, Observer { sSize -> binding.tvSmbSize.setText(sSize.toString()) }) */ // Display prograssBar in the screen smbViewModel.downloadedRatio.observe(this, downloadedRatioObserver) binding.progBarHori.visibility = ProgressBar.VISIBLE } } // connect SMB server and download a file to android device binding.tvInfo01.setText(R.string.wait) smbViewModel.downloadSmbFile() // proceed according to smbViewModel.isConnectedSmb 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() { // LiveDataを設定する private val _smbPath: MutableLiveData<String> by lazy { // MutableLiveData<T>は、valでmutableな変数を宣言できる MutableLiveData<String>() } val smbPath: LiveData<String> // 下記により、smbPathの読み込み時は _smbPathが読み出される 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 // SMBサーバーへの接続、ファイルダウンロード fun downloadSmbFile() { viewModelScope.launch(Dispatchers.IO) { lateinit var smb: SmbFile // connect to SMB server try { val prop = Properties() // java.util.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) // LiveDataにSMBサーバーからのデータをセットする // ワーカースレッド空間のデータをLiveDataにセットするにはpostValue()を使う _smbPath.postValue(smb.path) _smbSize.postValue(smb.length()) // download a file from SMB server to android device 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 <T : ViewModel> create(modelClass: Class<T>): T { return SmbViewModel(user, password, domain, smbUrl) as T } }