2021 Sep. 06.
2021 Aug. 14.
androidでjcifs-ng・コルーチンを使ってSMBサーバーに接続し、ディレクトリ内のファイルを再帰的に取得する実装例
app/libsに配置するライブラリ
bcprov-jdk15to18-1.69.jar
Maven Repository: org.bouncycastle » bcprov-jdk15to18 » 1.69 のFilesのjarをクリックしてダウンロード
jcifs-ng-2.1.6.jar
Maven Repository: eu.agno3.jcifs » jcifs-ng » 2.1.6 のFilesのbundleをクリックしてダウンロード
build.gradle(Module:app)
追加設定
implementation files('libs/jcifs-ng-2.1.6.jar') 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.kotlinx:kotlinx-coroutines-core:1.5.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1' implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.69'
全体
plugins { id 'com.android.application' id 'kotlin-android' } android { compileSdkVersion 30 buildToolsVersion "30.0.3" defaultConfig { applicationId "PACKAGE.PROJECT" 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 "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.6.0' implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.4.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' implementation files('libs/jcifs-ng-2.1.6.jar') 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.kotlinx:kotlinx-coroutines-core:1.5.1' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1' implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.69' }
許可するパーミッション
<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="PACKAGE.PROJECT"> <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.SmbClient"> <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
import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity import jcifs.CIFSContext import jcifs.config.PropertyConfiguration import jcifs.context.BaseContext import jcifs.smb.NtlmPasswordAuthentication import jcifs.smb.SmbFile import kotlinx.coroutines.* import java.util.* import kotlin.coroutines.CoroutineContext class MainActivity : AppCompatActivity(), CoroutineScope { // 認証情報 ////////////////////////////////////////////// // Please replace these values to your data // val user = "USER" val password = "PASSWORD" val domain = "192.168.1.1" val smbroot = "smb://" + domain + "/SMB/DIR/" // smbrootの末尾にはスラッシュ(/)を付ける ////////////////////////////////////////////// val TAG: String = "MySMB" // coroutine準備 private val job = Job() override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job // 終了時のcoroutineのキャンセル設定 override fun onDestroy() { job.cancel() super.onDestroy() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) launch { withContext(Dispatchers.IO) { val smb = connectSMB(user, password, domain, smbroot) Log.d(TAG, "Got SMB: " + smb.path) // get all files recursively in the smb var tmpSmbNormalFiles: MutableList<SmbFile> = mutableListOf<SmbFile>() val allSmbNormalsFiles = getNormalSmbFiles(smb, tmpSmbNormalFiles) for ( eachAllSmbFile in allSmbNormalsFiles) { Log.d(TAG, "eachAllSmbFile" + eachAllSmbFile.path) } } } } /* * SMBサーバーのノーマルファイルを検索する再帰関数 * パラメータ * givenDir 検索するSMBディレクトリパス。 * tmpSmbNormalFiles 空のMutableList<SmbFile>型変数。 * 1つ前の再帰関数実行結果を次の再帰関数に渡すための変数。 * ユーザーは最初にこの関数を起動するので、空のMutableListを与える。 */ private suspend fun getNormalSmbFiles(givenDir: SmbFile, tmpSmbNormalFiles: MutableList<SmbFile>): MutableList<SmbFile> { val childSmbs = givenDir.listFiles() if (childSmbs.size > 0) { for (eachChild in childSmbs) { Log.d(TAG, "eachChildSmb is " + eachChild.path) if (eachChild.isDirectory) { getNormalSmbFiles(eachChild, tmpSmbNormalFiles) } else if (eachChild.isFile) { tmpSmbNormalFiles.add(eachChild) } else { continue } } } return tmpSmbNormalFiles } // SMBサーバーに接続する関数 private suspend fun connectSMB(user: String, password: String, domain: String, smbroot: String): SmbFile { lateinit var smb: SmbFile coroutineScope { smb = async(Dispatchers.IO) { val prop = Properties() prop.setProperty("jcifs.smb.client.minVersion", "SMB202") prop.setProperty("jcifs.smb.client.maxVersion", "SMB300") val bc = BaseContext(PropertyConfiguration(prop)) val creds = NtlmPasswordAuthentication(bc, domain, user, password) val auth: CIFSContext = bc.withCredentials(creds) SmbFile(smbroot, auth) }.await() } return smb } }