rokkonet

PC・Androidソフトウェア・アプリの開発・使い方に関するメモ

Android開発 ホーム画面を表示する

2021 Jul. 31.

アプリのActivityを消してホーム画面を表示するには、ホーム画面へのインテントを発行する。

出典 Android - androidにてActivityをバックグラウンドにまわすコードは?|teratail

var intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_HOME)
this.startActivity(intent)

android開発 coroutineによる非同期スレッド実行

2021 Jul. 24.

参考ページ
雰囲気で利用しないためのAndroidにおけるKotlin-Coroutineメモ - Qiita
非同期処理のCoroutine(コルーチン)を始めてみよう!【Android・Kotlin】

ライブラリ導入

build.gradle(Module/App)に下記を記述するだけ
jarファイルの配置は不要

dependencies {
   implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
}


概要

メインスレッドクラスにCoroutineScopeをインプリメントする
launch{ }の中に非同期スレッド処理を記述する
launch{ }内では、withContext( ){ }でスレッドを使い分ける
 withContext(Dispatchers.Main) { }:メインスレッド処理
 withContext(Dispatchers.Default) { }:非同期スレッド処理
 withContext(Dispatchers.IO) { }:入出力関係非同期スレッド処理
launch{ }内では、withContext( )の指定がなければ、withContext(Dispatchers.Main)と同じ
async(Dispatchers.XX){ }.await( )では、ブロック内がDispatchers.XXな非同期スレッドとなり、また、その実行終了を待ってメインスレッドに戻る
async(Dispatchers.XX){ }ではブロック最終行の結果が返り値となる。"val VAL=async(Dispatchers.XX){ }.await( )"でVALをメインスレッドに渡すことができる
非同期スレッド中でのスレッドの中断はThread.sleep( )ではなくdelay( )を使う
非同期スレッド処理を関数化する場合は関数定義を "suspend fun" とする

メインクラスに書いておくもの

class MainActivity : AppCompatActivity(), CoroutineScope {

    // coroutineの準備
    private val job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job
    val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
        // 新しいコルーチンの作成時や withContext の呼び出し時に Dispatchers をハードコードしない
        //     https://developer.android.com/kotlin/coroutines/coroutines-best-practices?hl=ja

    // 終了時のcoroutineのキャンセル設定
    override fun onDestroy() {
        job.cancel()
        super.onDestroy()
    }
}


launch{ }の書き方1

withContext(Dispatchers.Default) { } による非同期スレッド

launch {
    withContext(Dispatchers.Default) {
        // 非同期スレッド

    }

    // メインスレッド

}


launch{ }の書き方2

async( ){ }.await( ) による非同期スレッド

launch {
    async(Dispatchers.IO) {
        // Dispatchers.IO、Dispatchers.Defaultを使い分ける
        //     https://qiita.com/offwhite/items/94540e7cc3b330507c9c

        // 非同期スレッド

    }.await()

    withContext(Dispatchers.Main) {
        // メインスレッド

}


launch{ }の書き方3

async( ) { } による非同期スレッド

launch {
    val retAsync = async(Dispatchers.IO){
        // Dispatchers.IO、Dispatchers.Defaultを使い分ける
        //     https://qiita.com/offwhite/items/94540e7cc3b330507c9c

        // 非同期スレッド

    }
    withContext(Dispatchers.Main) {
        // メインスレッド
        //     async( )の結果を入れた変数を関数の引数にして利用する
        FUNCTION(retAsync.await())
}


非同期スレッド内でのHTTP通信例

MainActivity.kt

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import PACKAGE.PROJECT.databinding.ActivityMainBinding
import java.io.BufferedReader
import java.io.InputStream
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import kotlin.coroutines.CoroutineContext

class MainActivity : AppCompatActivity(), CoroutineScope {

    val targetUrl : String = "http://www.google.co.jp"

    // 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)

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

        binding.btnStart.setOnClickListener {

            /*
             * launch{ }の中でメインスレッドと非同期スレッドを使い分ける
             * async( ){ }の中だけが非同期スレッド
             */
            launch {
                withContext(Dispatchers.Main) {
                    /*
                     * メインスレッド1
                     * ここのコードは非同期スレッド実行前に実行される
                     */
                    binding.txtVw1.setText("wait")
                    delay(5000)
                }

                /*
                 * 非同期スレッド
                 * await()により、このブロックの実行後にその下のメインスレッド2が実行される
                 * ブロック内最終行がasync( )の戻り値となる
                 */
                val resultSubThread = async(context = Dispatchers.IO) {
                    val TIMEOUT_MILLIS = 0 // 0は無限
                    val sb = StringBuffer("")
                    var httpConn: HttpURLConnection? = null
                    val br: BufferedReader? = null
                    val `is`: InputStream? = null
                    val isr: InputStreamReader? = null

                    val url = URL(targetUrl)
                    httpConn = url.openConnection() as HttpURLConnection
                    httpConn.connectTimeout = TIMEOUT_MILLIS // 接続にかかる時間
                    httpConn.readTimeout = TIMEOUT_MILLIS // データの読み込みにかかる時間
                    httpConn.requestMethod = "GET" // HTTPメソッド
                    httpConn.useCaches = false // キャッシュ利用
                    httpConn.doOutput = false // リクエストのボディの送信を許可(GETのときはfalse,POSTのときはtrueにする)
                    httpConn.doInput = false // レスポンスのボディの受信を許可
                    httpConn.connect()
                    val responseCode = httpConn.responseCode
                    Log.d("MyApp", responseCode.toString())
                    responseCode
                }.await()

                withContext(Dispatchers.Main) {
                    // メインスレッド2
                    binding.txtVw1.setText(resultSubThread.toString())
                }
            }
        }
    }
}

android開発 ソフトキーボードを消す

2021 Jul. 25.

ボタンがタップされたらソフトキーボードを画面から消す

binding.button1.setOnClickListener {
            val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            imm.hideSoftInputFromWindow(it.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS)
}

android開発 Failed to load class "org.slf4j.impl.StaticLoggerBinder"

2021 Jul. 24.

エラーメッセージ

Failed to load class "org.slf4j.impl.StaticLoggerBinder"

対処

build.gradle(Module)にライブラリを記述

dependencies {
    implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.32'
}

android開発 Didn't find class "org.bouncycastle.asn1.ASN1ObjectIdentifier"

2021 Jul. 24.

エラーメッセージ

Failed resolution of: Lorg/bouncycastle/asn1/ASN1ObjectIdentifier
Didn't find class "org.bouncycastle.asn1.ASN1ObjectIdentifier"

対処

  1. プロジェクトのapp/libsにbcprov-jdk15to18-1.69.jarを配置する
  2. build.gradle(Module)にライブラリ設定を記述する
dependencies {
    implementation group: 'org.bouncycastle', name: 'bcprov-jdk15to18', version: '1.69'
}

android開発 android端末をsmbクライアント(sambaクライアント、cifsクライアント)にする

2021 Jul. 31.
2021 Jul. 25.
2021 Jul. 24.

インストールしたandroid端末

android バージョン 10

コンパイル環境

compileSdkVersion 30
minSdkVersion 24
targetSdkVersion 30

プロジェクトのapp/libsに配置したjarライブラリ

jcifs-ng-2.1.6.jar
bcprov-jdk15to18-1.69.jar

build.gradle(Module)に追記した設定
dependencies {
    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.kotlinx:kotlinx-coroutines-core:1.5.1'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
}
AndroidManifest.xmlにネット利用許可を設定
    <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"/>
activity_main.xml

最新版保管場所 https://bitbucket.org/arsmus/smbclientbasic/src/master/app/src/main/res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <EditText
        android:id="@+id/edTxtDomain"
        android:layout_width="225dp"
        android:layout_height="38dp"
        android:layout_marginTop="20dp"
        android:ems="10"
        android:hint="@string/domain"
        android:inputType="textUri"
        android:textSize="14sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.487"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/edTxtPassword" />

    <EditText
        android:id="@+id/edTxtAccount"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:ems="10"
        android:hint="@string/user"
        android:inputType="textEmailAddress"
        android:textSize="14sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/edTxtPassword"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:ems="10"
        android:hint="@string/password"
        android:inputType="textPassword"
        android:textSize="14sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/edTxtAccount" />

    <EditText
        android:id="@+id/edTxtSmbRoot"
        android:layout_width="288dp"
        android:layout_height="41dp"
        android:layout_marginTop="20dp"
        android:ems="10"
        android:hint="@string/smb_root"
        android:inputType="textUri"
        android:textSize="14sp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.518"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/edTxtDomain" />

    <TextView
        android:id="@+id/txtVw"
        android:layout_width="280dp"
        android:layout_height="302dp"
        android:layout_marginTop="21dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/btnConnect"
        app:layout_constraintVertical_bias="0.0" />

    <Button
        android:id="@+id/btnConnect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="12dp"
        android:text="@string/connect"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.458"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/edTxtSmbRoot" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt

最新版保管場所 https://bitbucket.org/arsmus/smbclientbasic/src/master/app/src/main/java/net/sytes/rokkosan/smbclient/MainActivity.kt

import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.inputmethod.InputMethodManager
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 PACKAGE.PROJECT.databinding.ActivityMainBinding
import java.util.*
import kotlin.coroutines.CoroutineContext


class MainActivity : AppCompatActivity(), CoroutineScope {
    //////////////////////////////////////////////
    // Please replace these values to your data //
    val DOMAIN: String = "192.168.1.1"
    val USER: String = "user"
    val SMBROOT: String = "/samba/server/path"
    //////////////////////////////////////////////

    val TAG: String = "MyApp"
    lateinit var binding: ActivityMainBinding
    lateinit var domain: String
    lateinit var user: String
    lateinit var password: String
    lateinit var smbroot: String

    // 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)

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

        binding.edTxtDomain.setText(DOMAIN)
        binding.edTxtAccount.setText(USER)
        binding.edTxtSmbRoot.setText(SMBROOT)

        binding.btnConnect.setOnClickListener {

            // ソフトキーボードを非表示にする
            val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
            imm.hideSoftInputFromWindow(it.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS)

            binding.txtVw.setText(R.string.wait)

            // 認証情報取得
            if ( binding.edTxtDomain.text.length == 0 ) {
                binding.txtVw.text = "Please set domain"
                return@setOnClickListener
            } else {
                domain = binding.edTxtDomain.text.toString()
            }
            if ( binding.edTxtAccount.text.length == 0 ) {
                binding.txtVw.text = "Please set account"
                return@setOnClickListener
            } else {
                user = binding.edTxtAccount.text.toString()
            }
            if ( binding.edTxtPassword.text.length == 0 ) {
                binding.txtVw.text = "Please set password"
                return@setOnClickListener
            } else {
                password = binding.edTxtPassword.text.toString()
            }
            if ( binding.edTxtSmbRoot.text.length == 0 ) {
                binding.txtVw.text = "Please set root-path of SMB-server"
                return@setOnClickListener
            } else {
                smbroot = "smb://" + domain + binding.edTxtSmbRoot.text.toString()
            }

            // smbアクセス処理はActivityとは別スレッドとする必要があるのでcoroutineを利用する
              launch {
                val deferred = async(Dispatchers.IO) {
                    val prop = Properties()  // java.util.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)
                    val smb = SmbFile(smbroot, auth)
                    var smbPaths: String = ""
                    if (smb.exists()) {
                        var smbFiles = smb.listFiles();
                        for (smbFile in smbFiles) {
                            Log.d(TAG, smbFile.getName())
                            smbPaths += "${smbFile} "
                        }
                    }
                    smbPaths
                }
                withContext(Dispatchers.Main) {
                    binding.txtVw.setText(deferred.await())
                }
            }
        }
    }
}

android開発 java.lang.NoClassDefFoundError: Failed resolution of: Lorg/slf4j/LoggerFactory

2021 Jul. 24.

エラー現象

jcifsライブラリを利用してandroid端末をsmbクライントにするプログラムを作成したところ、
java.lang.NoClassDefFoundError: Failed resolution of: Lorg/slf4j/LoggerFactory
というエラーが出た。

対処

build.gradle(Module)に implementation group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.32' を記述した。

dependencies {
    implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.32'
}


参考ページ Gradleの基礎まとめ - そろそろ悟りを開きたい

その他

slf4j-api-1.7.32.jarとslf4j-android-1.7.32.jarをプロジェクトのlibsに置き、下記をbuild.gradle(Module)に記述してもエラーが消えなかった。

dependencies {
    implementation files('libs/slf4j-api-1.7.32')
    implementation files('libs/slf4j-android-1.7.32')
}