タブレット用プログラムの書き止め

android OS & iPadOS の記録。

【kotlin】内部ストレージのファイル入出力。File.appendText()、File.readLines()

2024-08-26 20:37:06 | Android studio 日記

Preferenceで文字列リストを保存している。
理由は分からないがアプリが強制終了。再現性は無し。本当に原因が分からない。

そして、再起動後にPreferenceは消えて無くなった。
なので、手動でリスト内容を内部ストレージに保存する項目を追加。

内部ストレージに入出力する方法はたくさんあるが設定が良くないのか多くがエラー(笑)
単純な方法でテキストの書き込み読み出しを実行。

    private fun write() {
        // 文字列配列から書き込み。改行を付け加える
       
        val lists = ArrayList< String >(出力データ)
        var file = requireContext().filesDir // アプリ固有内部ストレージ

        file = File(file,"appData/list.txt") // filesDir + "appData/list.txt" 出力場所、出力ファイル名
        if (file.exists())
            file.delete() // 存在してたら削除
        else {
            val f = file.parentFile
            if (f!!.exists()==false)
                f.mkdir() // appDataディレクトリが無ければ作る
        }

        try {
            file.createNewFile() // 新規作成。list.txtファイルが存在していて実行するとエラー
            for (i in lists.indices) {
                file.appendText(lists[i] + "\r\n") // 改行を付けて追加書き込み。\は半角バックスラッシュ
            }
        } catch ( e:Exception ) {
            // エラー全部受け取り
        }
    }

    private fun read() {
        var file = requireContext().filesDir
        file = File(file,"appData/list.txt")
        if (file.exists()==false)
            return

        val lists: List< String > = file.readLines() // 一行分を1要素にして配列を作ってくれる
        if (lists.size<=0)
            return

        // 作った文字列配列を処理する
    }


アプリのバグ出し修正、デザイン変更等々、気長に考える。
アプリの内部ストレージのファイルアクセスは権限はいらないみたい。

ファイルの書き込み読み出しは問題なさそう。

 


【kotlin】グルグル ProgressBarの組み込み

2024-06-23 00:33:43 | Android studio 日記

結果的に凄く楽です。

しかし、処理後のデータを表示に利用する時は、場合により同じコルーチンの中で使う事。
外に出して問題が無ければ良いが、操作は同じなのに画面表示の状態が変わってしまう時は疑ってみる。

async { }.await() に騙されてはいけない。
UIスレッドで処理後データが予想と異なることが良くある。タイミングの問題?

原因が分からず、対処療法で何日も悩み、コルーチン内に置くことで操作と結果が毎回予想と同じになりました。


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        _progressBar = requireActivity().findViewById(R.id.progressbar)

    }

    fun proc() {

        ProgressBar.VISIBLE.also { _progressBar!!.visibility = it }
        CoroutineScope(Dispatchers.Default).launch {
            async { /* 長い処理 */ }.await()

            withContext(Dispatchers.Main){
                /* scrollToPosition()などを使う場合はコルーチンの中にまとめる */
                _recyclerView!!.setAdapter( adapter() )
                if (sw) _recyclerView!!.scrollToPosition(position()) // adapter()、position()は割愛
                
                ProgressBar.GONE.also { _progressBar!!.visibility = it }
            }
        }
    }


【追記】

async { }.await() は、コルーチンの中では戻り値が帰るまでワーカースレッドの他を待たせる。
UIスレッドは動き続けていて長い処理が終わっていなくてもデータへのアクセスができてしまう。

今回はデータを使う部分をコルーチンの中に入れて思い通りの動作にしたという事。
状況によってはコールバックを使わないと望む動作にならないかな。それは面倒くさいな。

他のViewの反応をAllWaitFlag で強制帰還で飛ばしてしまおう。

 

【追記の追記】

処理が短時間で終わる時にグルグルがフラッシュしてしまう。
気になる場合は、postDelayed() を利用する。

 

        var flag:Boolean = true
        Handler(Looper.getMainLooper()).postDelayed({
            if (flag) ProgressBar.VISIBLE.also { _progressBar!!.visibility = it }
        },100)
        CoroutineScope(Dispatchers.Default).launch {
            async { /* 処理 */ }.await() // ワーカースレッドで実行

            withContext(Dispatchers.Main) { // UIスレッドに移って実行
                // UIスレッドで処理後の内容を設定する

                flag = false
                ProgressBar.GONE.also { _progressBar!!.visibility = it }
            }
        }

時間経過後にグルグルを表示する設定。ここでは 0.1秒の設定。

処理が終わっていればフラグを倒してグルグルを非表示にする。
時間経過後に実行される場合、もうフラグが倒れているので何もしない。

処理が長引いた場合は、時間経過後にグルグル表示する。
ワーカースレッドの処理が終わり、後処理も済めばグルグルを非表示にする。

設定時間経過より短い場合はグルグルが表示されない。
処理が長い場合はグルグルが表示される。


コードの条件により上手く動作しない事もある。
その場合は遅延表示を諦める

 


【kotlin】 Coroutinesについて

2024-05-22 22:20:09 | Android studio 日記

個人見解です。

同期タイプと非同期タイプがある。

runBlocking {} は同期タイプ。
UI(メイン)スレッドを止めてワーカースレッドの処理を待つ。
処理後にUIスレッドに戻り処理を続ける。

launch {} は非同期タイプ。
UIスレッドは動き続ける。ワーカースレッドは処理後に指定の場所に保存して通知を送る。
UIスレッドは通知が来たらデータを取り出し処理をする。

ワーカースレッドは、シングル、マルチが指定できる。
低性能CPUは少ないスレッド数でないとUIスレッドに悪影響がでる。

 

    private val parentJob = Job()

    fun test() { 

          /*
            * 呼ばれた分が同時に処理を始める。
            * 性能の低い機種はUI処理に滞りが出るので注意。
            */
          CoroutineScope.launch( parentJob ) {
                asyncTask()
          }


          /*
            * プールに積んで非同期でシングル処理を行う。
            */
          CoroutineScope(
                Executors.newSingleThreadExecutor().asCoroutineDispatcher()
          ).launch( parentJob ) {
                asyncTask()
          }


          /*
            * プールに積んで非同期で指定数の処理を並行する。ここでは4スレッド指定。
            */
           CoroutineScope(
                Executors.newFixedThreadPool(4).asCoroutineDispatcher()
           ).launch(parentJob) {
                asyncTask()
           }

    }

    suspend fun asyncTask() { 

        /* 省略 * /
    }

    override fun onDestroy() {
          parentJob.cancel // 実行中、待機中の非同期処理を止める。
          super.onDestroy()
    }

kotlinの非同期処理はシンプルで応用も簡単。
宣言が、Javaより厳格だけど慣れれば分かりやすい。
市販のガイドがJavaだった。それが不幸の始まりだったかも(笑)

 


【kotlin】 非同期処理(RecyclerViewにサムネイル設定が大量の場合)

2024-04-25 15:41:21 | Android studio 日記

RecyclerView.Adapterに画像のフルパスリストを渡し、サムネイル画像を生成する。
その画像を設定すると処理時間が長すぎてサムネイル画像のスクロールがスムーズに動かない。

画像作成と設定、更新を非同期処理で行う。
システムが管理しているサムネイル画像を利用する選択もあるが自前で用意する事にする。

サンプルなどから作成したコードは、随時開始してしまいCPUの能力を削りすぎる。
また、メモリもスレッド数分消費してしまう。
画像数が少なければ問題ないが状況により書き換え更新のストップやユーザーアクションに反応しなくなる。
非同期処理の意味がなくなるのでコルーチンにシングルスレッド処理を設定する。

コンパクトで処理が速いのですが古いデバイスでは低能力、低メモリ量なので大量処理には向かないため。


アダプター修正内容。

class MyFragment : Fragment() {
    /* 省略 * /

    private val parentJob = Job()

    inner class MyAdapter() : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
        /* 省略 * /

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            holder.imageView.setImageBitmap(loadImage(position))
            /* 省略 * /
        }

        private fun loadImage(position: Int): Bitmap? {
            val imagePath: String = pathList.get(position)
            /* 省略 * /

            if (!cf.exists()) { /* キャッシュファイルが無い時 * /

                CoroutineScope(
                    newSingleThreadExecutor().asCoroutineDispatcher()
                ).launch( parentJob ) {
                    asyncTask( imagePath, _recyclerView!!, position)
                }

                return BitmapFactory.decodeResource(
                    requireContext().getResources(),
                    R.drawable.img_file
                )
            }
            return BitmapFactory.decodeFile(cf.absolutePath) /* キャッシュファイルがあれば、Bitmapを返す * /
        }
        /* 省略 * /
    }

 

    suspend fun asyncTask(path:String, rv: RecyclerView, vID: Int) { 

        /* 省略 * /
        
        val ret = MyMakeThumbnail().toThumbnailFile(souFile, cacheFile, _saveSize)

        when ( ret ) {
            0 -> if (vID < _rv.adapter!!.itemCount) {
                _rv.post(Runnable {
                    _rv.adapter!!.notifyItemChanged(vID)
                })
            }

             /* 省略 * /
        }
    }
}


非同期を開始する場所に書く。

CoroutineScope(  newSingleThreadExecutor().asCoroutineDispatcher() ).launch(parentJob) {
    asyncTask(path, _recyclerView!!, position)
}

シングルスレッド処理を開始させる。
前の処理が続いている場合は待機され順番に1つずつ処理が行われる。


コルーチンがよく分からず取り合えず動く内容という形でした。

とりわけ、runBlockingが未理解で付けたら動く。くらいに使ってました(笑)
恐ろしい。


下記サイトを参考に何となくコルーチン組みました。

【Kotlin/JVM】CoroutineDispatcher を作る
       https#//qiita.com/sdkei/items/a056a1275b05b11ddcd4


CoroutinesのlaunchとasyncとChannelとFlowの使い分け
       https#//qiita.com/naoi/items/9892db4cec2e9c0f6114

 


【kotlin】 Toastをカスタマイズする

2024-04-03 13:26:40 | Android studio 日記

Toast の文字の大きさを大きくしたい。
という思いから調べたところ、純正からはそのような記事は見つからなかった。

説明されている複数のサイトでカスタマイズする方法が分かりました。
ですが、自分のプログラムに組み込むには問題があり例外が発生してしまう。

複数の方法を組み合わせて自分のものに使えるように調整した。

【参考】
 ※【Android Studio】Toast に画像・テキストを追加するカスタマイズ方法
  https#//codeforfun.jp/android-studio-how-to-customize-toast/

 ※ [Android] Toast をカスタマイズする
  https#//akira-watson.com/android/toast-custom.html

 

【注意】
ViewModel でViewの参照の値を保存することはメモリリークの原因になります。
メソッド内で参照を作成した場合はメソッドが終了すると参照をシステムが切ってくれます。多分^^;

 

アプリケーションの Context が求められれば、ViewModelのメソッド内でViewが作成できる。
そのViewをToastに設定して表示する。

Viewの構成は、custom_toast.xmlに記載しておく。

※ toast.setView(view) は、非推奨になりました。最新のOSでは、いずれ使用できなくなります。

 

【MainViewModel.kt】

class MainViewModel(app:Application): AndroidViewModel(app) {

    fun toastMake(text:String) {
        val view: View = LayoutInflater.from(getContext())
            .inflate(
                R.layout.custom_toast,
                null
            )

        view.findViewById(R.id.toast_message).text = text

        val toast: Toast =
            Toast.makeText(
                view.context,
                "",
                Toast.LENGTH_LONG
            )
        toast.setGravity(
            Gravity.CENTER,
            0,
            200
        )
        toast.setView(view)  // 非推奨
        toast.show()
    }
}

 

【custom_toast.xml】 res/layout に作成。


< RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/relative_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    < TextView
        android:id="@+id/toast_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:background="@drawable/message_frame"
        android:textColor="@color/background_color"
        android:textStyle="bold"
        android:textSize="24sp"
        />
< /RelativeLayout>


android:background="@drawable/message_frame"
枠の設計を別にしています。
色や形状をカスタマイズ。説明は省略。