20200226のAndroidに関する記事は6件です。

Android でも Android アプリを開発したい(デバッグ編)

はじめに

Android でも Android アプリを開発したいの続きです。
デバッグを行うためのツール、adb のインストールから実際に Android Studio を使ってデバッガを起動するところまでを記載します。

注意点

PCが一台必要です。Android 単体では実施できません。
今回は Windows を使った手順を紹介します。

Android はWifiでルータ等に繋がっている必要があります。

Ubuntu の画面表示には XSDL を使用している前提で記載しています。

SDK Platform Tools のダウンロード

Windows で以下のサイトを開いて、SDK Platform Tools をダウンロードして解凍して下さい。

https://developer.android.com/studio/releases/platform-tools.html

解凍すると platform-tools フォルダに adb.exe ファイルがあるはずです。
後で adb.exe を使用します。

USBドライバのインストール

Windows に USB ドライバをインストールします。
後で Windows と Android 端末を USB ケーブルで接続しますが、
Windows から Android 端末を認識するために USB ドライバを使用します。
Android 端末を開発している会社ごとにドライバーが異なるため手順を書ききれません。
丸投げになりますが、以下のサイトを参考にインストールしてください。

https://developer.android.com/studio/run/oem-usb

USB デバッグの有効化をオンにする

Android 端末で開発者向けオプションをオンにしてから、USB デバッグの有効化をオンにします。
有効化するとデバッグモードで動作し、プログラムの途中で停止したりできるようになります。
Android 端末ごとに手順が違うので、書ききれないため以下のサイトを参考にして下さい。

https://developer.android.com/studio/debug/dev-options

Ubuntu に adb のインストール

Android 端末で UserLAnd または Termux の Ubuntu を立ち上げて LXTerminal 等から以下のコマンドを実行して adb をインストールして下さい。

# adb を Ubuntu にインストール(Termux の場合は sudo は不要です。UserLAnd の場合は sudo が必要です。)
sudo apt install -y adb

# adb をコピーして Android Studio から使えるようにする
cp /usr/bin/adb ~/Android/Sdk/platform-tools/

Android 端末をWifi経由でデバッグできるようにする

USB ケーブルで Windows と Android を接続して下さい。
ダイアログが出るのでOKボタンを押して下さい。

Windows マークを右クリックして「ファイル名を指定して実行」を選択してください。
名前 の入力欄に cmd と入力し Enter キーを押してコマンドプロンプトを立ち上げてください。

以下のように adb コマンドのある platform-tools ディレクトリまで cd コマンドで移動し、adb を使用して Android 端末にWifi経由で接続できるようにしてください。

# D:\platform-tools_r29.0.6-windows\ に platform-tools がある場合。ご自分の指定した解凍先に合わせて適宜読み替えて下さい。
cd D:\platform-tools_r29.0.6-windows\platform-tools

# Android 端末をWifi経由で接続できるように設定する
adb tcpip 5555

adb を実行し「restarting in TCP mode port: 5555」と出たら USB ケーブルを外して下さい。
ここで行った設定は Android を再起動するまで有効になります。

デバッガの実行

Ubuntu のデスクトップ環境を立ち上げ、それから Android Studio を立ち上げます。
(もし既に Android Studio を立ち上げてあった場合には一旦、終了してから立ち上げ直して下さい。)
メニューの下のコンボボックスの中に Android 端末名が出ていたら成功です。デバッグが可能な状態になっています。

実際にデバッガを試してみましょう。

まず適当にプロジェクトを生成したら、MainActivity.java を開いて下さい。
onCreate メソッドが見えるはずです。
メソッド内の1行目、super.onCreate~ と書かれている行、の左側の行番号の書かれている箇所の右側をクリックして下さい。丸が付いたでしょうか。この丸の位置で実行中のプログラムを一時停止しします。

続いてメニュー下の Android 端末名の4つ程右側のボタンに虫のような形をしたものがあります。その虫ボタンを押して下さい。
しばらく待つと、アプリが立ち上がり、Waiting For Debugger と出ます。
この時に Force Close は押さないで下さい。

Android の通知領域を開いて XServer XSDL is running をクリックします。(右側の Stop は触れないように気をつけて下さい)
Android Studio に戻り、しばらく待っていると Waiting For Debugger が自動的に消えてデバッグを開始します。
先程、丸を付けた行で停止しているはずです。

以上でデバッグを行うことが出来るようになります。
エミュレータが不要なのは利点と言えるかもしれません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ライフサイクルのタイミングを出力する LifecycleLogger

class LifecycleLogger: LifecycleObserver {

  @SuppressLint("LogNotTimber")
  @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
  fun l(owner: LifecycleOwner, event: Lifecycle.Event) {
    Log.d("###", "${event.name} → ${owner.lifecycle.currentState} $owner")
  }

}

Activity や Fragment でこう呼ぶ。

   init {
     lifecycle.addObserver(LifecycleLogger())
     ...
   }

? LifecycleLogger とか。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidStudio3.6に更新したら、mapping.txtの配置場所が変わった

本日、AndroidStudio3.6のstable版がリリースされました。
https://android-developers.googleblog.com/2020/02/android-studio-36.html

早速アップデートして、作っているアプリのgradleバージョンも上げてビルドしてみたところ、mapping.txtに関して詰まったポイントがあったので記載します。

変更点

mapping.txtの配置場所が(地味に)変わりました。
flavor/buildType→flavorBuildTypeになっています。(flavorとbuildTypeに階層が無くなった)

変更
# 以前
app/build/outputs/mapping/flavor/buildType/mapping.txt
# AndroidStudio3.6
app/build/outputs/mapping/flavorBuildType/mapping.txt

# 例
app/build/outputs/mapping/flavor/debug/mapping.txt
↓
app/build/outputs/mapping/flavorDebug/mapping.txt

問題と解決法

問題

CIにflavorとbuildTypeを渡してビルドしていたのですが、成果物としてmapping.txtを保存する処理で、エラーになりました。
具体的にはmapping.txtを成果物フォルダにコピーしようとしており、下記のようなスクリプトを書いていました。
(CIではビルド時にFlavorとBuildType(debug/releaseなど)を指定しています)

成果物コピー
cp app/build/outputs/mapping/${Flavor}/${BuildType}/mapping.txt Outputs/

新しいディレクトリに対応できていないので、エラーになります。

エラー
cp: cannot stat ‘app/build/outputs/mapping/flavor/debug/mapping.txt’: No such file or directory

解決法

buildTypeはdebug/releaseという形で先頭が小文字だったのでどうしようかと思っていたのですが、bashに先頭を大文字にできる機能がありました。

cp app/build/outputs/mapping/${Flavor}${BuildType^}/mapping.txt Outputs/

参照時に^をつけると先頭だけ大文字にしてくれるようです。
(Bashのバージョンによるかもしれません)
これで無事mapping.txtの新しい階層に対応できました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Android Studio 3.6でオフラインモードを設定する方法

Android Studio 3.6の安定版がリリースされましたね。
https://android-developers.googleblog.com/2020/02/android-studio-36.html

色々と新機能も追加され、アップデートした方も多いかと思います。

そんなAndroid Studio 3.6ですが、オフラインモードの設定箇所が変わっており、初見だとどこで設定すればいいのかわからなかったのでメモがてら投稿します。

今までのところにない

今までは PreferencesGradleOffline work のチェックボックスがあり、そこをチェックすることでオフラインモードを有効にしていたかと思います。
しかしながら、3.6ではそのチェックボックスが消えてしまいました。

3.5

スクリーンショット_2020-02-26_15_13_46-2.png

3.6

サイドバーのGradleにあった

探した結果、公式ページにきちんと載っていました :innocent:
https://developer.android.com/studio/preview/features#gradle-offline-ui

サイドバーの Gradle 右から2番目のアイコンがオフラインモードの設定でした。(ちょっとわかりにくい…?)

スクリーンショット_2020-02-26_15_23_56.png

このアイコンをクリックすることで設定が切り替わるのですが、有効・無効がわかりにくかったのでスクショを貼っておきます。

オフラインモード有効

スクリーンショット 2020-02-26 15.31.45.png

オフラインモード無効

スクリーンショット 2020-02-26 15.31.56.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Androidでメインスレッドかどうかを判別する

非同期通信など書く際に今どのスレッドで実行されているのか確認したいときは、以下のように書けば判定できる

Log.d("thread(true:Main/false:IO)", "${Thread.currentThread() == context!!.mainLooper.thread}")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AndroidのRoomでデータベースを操る(Kotlin)

はじめに

この記事では、軽くRoomに触れつつ実際の使い方を書いています。
ゆるめの理解でとりあえず使いたい!という人向けです。

Roomって何?

Androidで内部データベースを扱いたい場合は、SQLiteを一般的には使います。
ですが、このSQLiteをそのまま使うことは現在非推奨になっています。理由としてはこちらの公式ドキュメントによると『クエリはコンパイル時に検証されない』『大量のボイラープレートコードが必要になる』等とのことです。
Roomは、このSQLiteをスマートに使うための物です。
Roomは3つの要素から構成されます。

Entity

データベースに入れるアイテムの事です。
基本的にはkotlinのdata classを使用します。

Dao(データアクセスオブジェクト)

データベースに、Entityに関するクエリを与えることが出来るインターフェースです。
ここによく使うクエリを登録します。

データベース

読んで字のごとく、データベースです。
この中に使用するEntityとDaoを登録します。

使い方

Roomを使う場合、androidxのRoom依存関係をbuild.gradle(app)に記述します。(projectのGradle Scriptの所にあります)
また、Roomを使うのにkaptという物も必要になるので、プログラムの先頭にapply plugin: "kotlin-kapt"と記述します。
次に、以下をdependenciesの中に記述します。

build.gradle
dependencies{

//..(省略)

    def room_version = "2.2.4"

    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"

// optional - Kotlin Extensions and Coroutines support for Room
    implementation "androidx.room:room-ktx:$room_version"

// optional - RxJava support for Room
    implementation "androidx.room:room-rxjava2:$room_version"

// optional - Guava support for Room, including Optional and ListenableFuture
    implementation "androidx.room:room-guava:$room_version"
}

上記のdef room_version = "2.2.4"の部分は、こちらに最新のバージョンがありますので、その時必要に応じて更新して下さい。わざわざ公式ドキュメントを見なくてもIDEなら警告と思うので多分気が付きます。(日本語版だと古いバージョンのままのときがあります。罠かな?)

次に、先程の三要素を作っていきます。

Entity

HumanEntity.kt
@Entity
data class HumanEntity constructor(
    @PrimaryKey(autoGenerate = true) val id: Int,
    val name: String,
    val age: Int
)

見ての通りで、これは人間のEntityです。idがPrimariyKeyで、他の複数のデータと重複しないものです。また、autoGenerate = trueとすると、idをこちらから設定しなくても適当に割り振ってくれます。今回はこれを出し入れします。

Dao

UserDao.kt
@Dao
interface UserDao{
    //データベース上の全てのHumanEntityを取得
    @Query("SELECT * FROM HumanEntity")
    //データベース上にあるhumanNameさんを全て取得
    fun getHumanAll(): List<HumanEntity>
    @Query("SELECT * FROM HumanEntity WHERE name == :humanName")
    fun searchName(humanName: String): List<HumanEntity>
    //humansを全て取得、primaryKey重複の際には入れ替え
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertHuman(vararg humans: HumanEntity)
    //humansを全て消去
    @Delete
    fun deleteAllHuman(vararg humans: HumanEntity)
}

このように、@Queryを使ってSQLiteのクエリを作ったり、@Insert@Deleteを使って挿入したり削除したりのクエリを作成することが出来ます。
もしクエリが間違っていれば、ここでエラーを吐いてくれます。
その他にも使えるものはあるので、こちらを参照して下さい。

データベース

UserDataBase
@Database(entities = [HumanEntity::class],version = 1)
abstract class UserDataBase: RoomDatabase(){
    abstract fun userDao(): UserDao
}

他のEntityを追加したい場合は、[]の中に加えてあげます。
Daoを追加したい場合も、abstruct fun 〇〇Dao(): 〇〇Daoと同じように追加します。

それでは、これらを使うプログラムを実際に記述していきます。
機能としては
1. データベース内の全ての人間を表示
3. データベースに人間を登録する
4. データベース内の名前が〇〇である人を全て削除
を作ります。
今回は、TableLayoutとTableRowを使用します。
また、分かりやすくするため、xml内の設定を直打ちしています。(実際にはちゃんとresourceを使ったほうが良いです)
以下コードです。少し長いです。

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TableLayout
        android:background="#AAFFFF"
        android:id="@+id/humanTable"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="0.8">
        <TableRow
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/headRow">
            <TextView
                android:layout_height="wrap_content"
                android:layout_width="200dp"
                android:text="名前"/>
            <TextView
                android:layout_height="wrap_content"
                android:layout_width="200dp"
                android:text="年齢"/>
        </TableRow>
    </TableLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:orientation="vertical"
        android:layout_weight="0.2">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_marginBottom="20dp">
            <EditText
                android:id="@+id/addNameInput"
                android:layout_width="150dp"
                android:layout_height="match_parent"
                android:inputType="text"
                android:hint="名前"/>
            <EditText
                android:id="@+id/addAgeInput"
                android:layout_width="150dp"
                android:layout_height="match_parent"
                android:inputType="number"
                android:hint="年齢"/>
            <Button
                android:id="@+id/addButton"
                android:layout_width="100dp"
                android:layout_height="match_parent"
                android:text="追加"/>
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">
            <EditText
                android:id="@+id/removeNameInput"
                android:layout_width="300dp"
                android:layout_height="match_parent"
                android:inputType="text"
                android:hint="名前"/>
            <Button
                android:id="@+id/removeButton"
                android:layout_width="100dp"
                android:layout_height="match_parent"
                android:text="削除"/>
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

これは、app/res/layout にlayout resources fileとして作って下さい。

human_row.xml
<?xml version="1.0" encoding="utf-8"?>
<TableRow
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/nameTextView"
        android:layout_height="wrap_content"
        android:layout_width="200dp"
        android:text="名前"/>
    <TextView
        android:id="@+id/ageTextView"
        android:layout_height="wrap_content"
        android:layout_width="200dp"
        android:text="年齢"/>

</TableRow>
MainActivity.kt
class MainActivity : AppCompatActivity() {

    private lateinit var db: UserDataBase
    private lateinit var dao: UserDao
    private val mainHandler = Handler(Looper.getMainLooper())
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //database という名前のデータベースを使用
        db = Room.databaseBuilder(
            applicationContext,
            UserDataBase::class.java,"database"
        ).build()
        dao = db.userDao()
        setAllHumanToTable()

        addButton.setOnClickListener(ButtonListener())
        removeButton.setOnClickListener(ButtonListener())
    }
    //データベース内にあるデータを全て表示します
    private fun setAllHumanToTable(){
        //データベース関連の動作は重いため、メインスレッドで動かしてはいけない決まりがあります
        AsyncTask.execute{
            val humanList = dao.getHumanAll()
            for(human in humanList){
                //全てのhumanをrowに追加
                val row = View.inflate(this@MainActivity,R.layout.human_row,null)
                row.nameTextView.text = human.name
                row.ageTextView.text = human.age.toString()

                //逆に、メインスレッド以外でUIの操作は出来ません
                //なので、このようにしてメインスレッドにViewの追加をお願いしています
                mainHandler.post{
                    humanTable.addView(row)
                }
            }
        }
    }
    private fun resetTable(){
        //humanTableの子を全て参照して
        //先頭以外のrowを全て削除しています
        humanTable.children.forEach {
            if(it is TableRow && it.id != R.id.headRow){
                mainHandler.post{
                    humanTable.removeView(it)
                }
            }
        }
        setAllHumanToTable()
    }
    private fun addHumanFunction(){
        AsyncTask.execute{
            val name = addNameInput.text.toString()
            val age = addAgeInput.text.toString().toIntOrNull() ?: -1
            if(name != "" && age >= 0){
                dao.insertHuman(HumanEntity(id = 0,name = name,age = age))
            }
        }
    }
    private fun removeHumanFunction(){
        AsyncTask.execute{
            val name = removeNameInput.text.toString()
            val humanList = dao.searchName(name)
            for(human in humanList){
                dao.deleteAllHuman(human)
            }
        }
    }
    inner class ButtonListener:  View.OnClickListener{
        override fun onClick(v: View) {
            AsyncTask.execute {
                when(v.id){
                    R.id.addButton -> addHumanFunction()
                    R.id.removeButton -> removeHumanFunction()
                }
                resetTable()
            }
        }
    }
}

見た目はこんな感じになります。
Screenshot from 2020-02-26 00-26-18.png

ソースコードに書いてある部分もありますが、注意点があります。

メインスレッドでデータベース操作は出来ない

データベースの処理は重いので、メインスレッドで実行してはいけない決まりがあります。もし、メインスレッドで実行してしまうと実行時にエラーが発生します。
なので、非同期処理をするようにしましょう。今回の用にAsyncTask.excute()を使うのはあくまで一例です。

DataBaseオブジェクトを何個も作らない

先程のこちらにも書いてありますが、データベースオブジェクトは重いので、シングルトン設計パターンに従うようにして下さい。

おまけ

今回のコード等はこちらにあります。

参考

https://qiita.com/yukiyamadajp/items/73bffb6a3697cb62f9e1
https://qiita.com/b_a_a_d_o/items/45bda89f49bf163144af
https://developer.android.com/training/data-storage/room?hl=ja
https://developer.android.com/training/data-storage/room/accessing-data?hl=ja

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む