- 投稿日:2020-02-26T23:46:05+09:00
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 5555adb を実行し「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 が自動的に消えてデバッグを開始します。
先程、丸を付けた行で停止しているはずです。以上でデバッグを行うことが出来るようになります。
エミュレータが不要なのは利点と言えるかもしれません。
- 投稿日:2020-02-26T21:03:21+09:00
ライフサイクルのタイミングを出力する 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()) ... }
- 投稿日:2020-02-26T20:49:07+09:00
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の新しい階層に対応できました。
- 投稿日:2020-02-26T15:39:21+09:00
Android Studio 3.6でオフラインモードを設定する方法
Android Studio 3.6の安定版がリリースされましたね。
https://android-developers.googleblog.com/2020/02/android-studio-36.html色々と新機能も追加され、アップデートした方も多いかと思います。
そんなAndroid Studio 3.6ですが、オフラインモードの設定箇所が変わっており、初見だとどこで設定すればいいのかわからなかったのでメモがてら投稿します。
今までのところにない
今までは
Preferences
のGradle
に Offline work のチェックボックスがあり、そこをチェックすることでオフラインモードを有効にしていたかと思います。
しかしながら、3.6ではそのチェックボックスが消えてしまいました。3.5
3.6
サイドバーのGradleにあった
探した結果、公式ページにきちんと載っていました
https://developer.android.com/studio/preview/features#gradle-offline-uiサイドバーの Gradle 右から2番目のアイコンがオフラインモードの設定でした。(ちょっとわかりにくい…?)
このアイコンをクリックすることで設定が切り替わるのですが、有効・無効がわかりにくかったのでスクショを貼っておきます。
オフラインモード有効
オフラインモード無効
- 投稿日:2020-02-26T02:04:01+09:00
Androidでメインスレッドかどうかを判別する
非同期通信など書く際に今どのスレッドで実行されているのか確認したいときは、以下のように書けば判定できる
Log.d("thread(true:Main/false:IO)", "${Thread.currentThread() == context!!.mainLooper.thread}")
- 投稿日:2020-02-26T00:35:22+09:00
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.gradledependencies{ //..(省略) 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.ktclass 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() } } } }ソースコードに書いてある部分もありますが、注意点があります。
メインスレッドでデータベース操作は出来ない
データベースの処理は重いので、メインスレッドで実行してはいけない決まりがあります。もし、メインスレッドで実行してしまうと実行時にエラーが発生します。
なので、非同期処理をするようにしましょう。今回の用に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