Android アプリ開発
- Android アプリ開発
開発環境構築
(VSCodeは諦めて)Android Studioを導入する。
- ダウンロード
- 下記実行。
# ファイル配置 sudo cp ${ダウンロードしたファイル} /usr/local # 解凍 cd /usr/local sudo zxvf ${ダウンロードしたファイル} # 初回起動 cd /usr/local/android-studio/bin bash studio.sh
- PATHに追加
~/.bashrc
に下記を追記# Android Studio export ANDROID_STUDIO_HOME="/usr/local/android-studio" export PATH="$PATH:$ANDROID_STUDIO_HOME/bin"
日本語化
- Android Studioのバージョンを確認
- 起動
- 左下のOptions Menu -> About
- JetBrains Language Pack for Android Studioから、一致するバージョンの言語パックをダウンロード
- 下記実行
sudo cp ${ダウンロードしたファイル} $ANDROID_STUDIO_HOME/plugins cd $ANDROID_STUDIO_HOME/plugins sudo unzip ${ダウンロードしたファイル} sudo rm ${ダウンロードしたファイル}
- Android Studio -> Plugins -> 歯車アイコン -> Install Plugin from Disk
/usr/local/android-studio/plugins/ja.${ダウンロードしたバージョン}/lib/ja.${ダウンロードしたバージョン}.jar
- Android Studio再起動
- Customize -> Language and Legionで日本語を選択
- Android Studio再起動
参考
起動
studio
adbコマンド
Ubuntuで実機を認識させるための設定
- ターミナルで以下のコマンドを実行して、udevルールを作成します
sudo apt install android-tools-adb
cd /etc/udev/rules.d/
sudo touch 51-android.rules
sudo chmod a+r 51-android.rules
- エディタで51-android.rulesを開き、以下の内容を追加します(VENDORIDは端末によって異なります):
SUBSYSTEM=="usb", ATTR{idVendor}=="VENDORID", MODE="0666", GROUP="plugdev"
(VENDORIDは、lsusb
コマンドで確認できます。例:Googleデバイスは「18d1」)
- udevルールを再読み込みします
sudo udevadm control --reload-rules
sudo udevadm trigger
実機での実行
- 端末のUSBデバッグを有効にします(設定→開発者オプション→USBデバッグ)
- USBケーブルで端末をPCに接続します
- ターミナルで
adb devices
を実行して、デバイスが認識されていることを確認します - Android Studioのデバイス選択ドロップダウンから実機を選択します
- 緑色の再生ボタン(▶)をクリックしてアプリをインストールします
KVM設定(エミュレータ高速化)
# インストール
sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils
sudo adduser $USER kvm
sudo adduser $USER libvirt
# ログアウト/ログインする
# インストールされているか確認
kvm-on
Android端末が接続されていることを確認する
Android端末を接続しているにもかかわらず、うまく認識していないときに参照する。
Android Studioを起動していると、ps aux | grep adb
を実行すると、PIDが度々更新されるのが確認できる。
その場合はAndroid Studioを停止する。
USB
lsusb
adb確認
# デバイスの確認
adb devices
# 停止/開始
adb kill-server && adb start-server
# 実行すると、Android側で切断/接続を検出する。接続モードの問い合わせが行われるので、画面から操作する。
プロジェクトの基本構成
コマンド
gradle init --type java-application
ディレクトリ構成
my-android-app/
├── app/
│ ├── src/
│ │ ├── main/
│ │ │ ├── java/
│ │ │ │ └── パッケージ名/
│ │ │ ├── res/
│ │ │ │ ├── layout/
│ │ │ │ ├── values/
│ │ │ │ └── drawable/
│ │ │ └── AndroidManifest.xml
│ │ └── test/
│ │ └── java/
│ │ └── パッケージ名/
│ └── build.gradle
├── build.gradle
├── settings.gradle
└── gradle.properties
build.gradle
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.9.0'
}
}
allprojects {
repositories {
google()
mavenCentral()
}
tasks.withType(JavaCompile).configureEach {
options.fork = true
options.forkOptions.executable = '/usr/lib/jvm/jdk-17.0.6+10/bin/javac'
}
}
settings.gradle
rootProject.name = 'android_helloworld'
include ':app'
gradle.properties
# Project-wide Gradle settings.
org.gradle.java.home=/usr/lib/jvm/jdk-17.0.6+10
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.nonTransitiveRClass=true
gradle/wrapper/gradle-wrapper.properties
gradleのバージョンが合わない場合はここを修正する。
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
app/build.gradle
/*
* This file was generated by the Gradle 'init' task.
*
* This generated file contains a sample Java application project to get you started.
* For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle
* User Manual available at https://docs.gradle.org/7.6/userguide/building_java_projects.html
*/
plugins {
id 'com.android.application'
}
android {
namespace 'ittimfn.android.helloworld'
compileSdk 35
defaultConfig {
applicationId "ittimfn.android.helloworld"
minSdk 21
targetSdk 35
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_17
targetCompatibility JavaVersion.VERSION_17
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
// JUnit5
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.12.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.12.1'
testImplementation 'org.junit.platform:junit-platform-launcher:1.12.1'
testImplementation 'org.hamcrest:hamcrest:3.0'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}
// JUnit5のテストを実行するための設定
tasks.withType(Test) {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
}
// テストは常に実行されるようにする(ファイルに変更がなくても実行)
outputs.upToDateWhen { false }
}
app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ittimfn.android.helloworld">
<!-- アイコンを追加したい場合は下記を追加。-->
<!-- android:icon="@mipmap/ic_launcher" -->
<!-- android:roundIcon="@mipmap/ic_launcher" -->
<application
android:allowBackup="true"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
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">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
app/src/main/res/values/strings.xml
<resources>
<string name="app_name">Hello World</string>
<string name="hello_world">Hello World!</string>
</resources>
app/src/java/${package}/MainActivity.java
package ittimfn.android.helloworld;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Android StudioなしでコマンドラインからAndroidエミュレータを起動する
前提
- adbインストール済み
- Android Studioコマンドラインツールインストール済み
コマンド
# インストール可能なイメージ
sdkmanager --list
# イメージのインストール
sdkmanager "$image_name"
# 使用可能なイメージ
avdmanager list target
# デバイス表示
avdmanager list device
# エミュレータ作成
# 任意のインスタンス名
name=
# 「インストール可能なイメージ」
package=
# 機種名
device=
avdmanager create avd --name $name --package $package --device $device
# 使用可能なエミュレータを表示
emulator -list-avds
# エミュレータ起動
emulator -avd $エミュレータ名
起動時にSIMの設定をする
tel=09012345678
# ドコモ
mnc=10
emulator \
-avd $name \
-netspeed lte -netdelay none \
-prop gsm.sim.operator.numeric=440$mnc \
-prop gsm.sim.operator.iso-country=jp \
-phone-number $tel
mnc
事業者 | MCC | MNC | numeric 値 |
---|---|---|---|
NTT ドコモ | 440 | 10 | 44010 |
KDDI (au/UQ) | 440 | 50 / 51 | 44050 / 44051 |
ソフトバンク | 440 | 20 | 44020 |
楽天モバイル | 440 | 11 | 44011 |
エミュレータにアプリをインストールする
# 拡張子 apkのファイルがAndroidアプリ。
# エミュレータが起動している状態で実行する。
adb install ./app/build/outputs/apk/debug/app-debug.apk
# app-debug.apkをビルドする
./gradlew assembleDebug
# app-debug.apkをインストールする
./gradlew installDebug
エミュレータからログを取得してホストに送る
- エミュレータメニューの一番下
- Bug report
- Bug report dataをチェック
- エラーになる操作を行う
- Save Reportボタンを押下
エミュレータから電話をかける
本物に対しても使える。
# 発信先
tel_no=123
adb shell am start -a android.intent.action.CALL -d tel:$tel_no
エミュレータに電話をかける
エミュレータにしか使えない。
# 発信元
tel_no=123
adb emu gsm call $tel_no
非通知でかける
# 直接指定するとかけられない。変数に代入する必要がある。
tel_no=#67080053
adb emu gsm call $tel_no
Android端末の開発者モードを有効化する
- 設定 -> デバイス情報 -> ビルド番号を7回タップ
- 設定 -> システム -> 開発者向けオプションを有効化
ログ取得
adb logcat > logfile.txt
# ActivityManagerとMyAppのログのみを取得
adb logcat ActivityManager:I MyApp:D *:S > filtered_log.txt
# おすすめ
adb logcat -c
adb logcat -v long *:E
SQLite + inflater + オリジナルViewのサンプル
Androidの通知の実装
チャネルを作成する
Android 8.0(API レベル 26)以降の機能。Android 8.0以降で、作成されていない場合はエラーになる。Android 8.0より前の場合も作成を試みるとエラーになる。
- 一意のチャネル ID、ユーザーが認識できる名前、重要度を指定して、NotificationChannel オブジェクトを作成します。
- 必要に応じて、システム設定内でユーザーに表示する説明を setDescription() で指定します。
- createNotificationChannel() に通知チャネルを渡して登録します。
import android.os.Build;
import android.util.Log;
import android.app.NotificationManager;
import android.app.NotificationChannel;
public static void createNotificationChannels(String channelId, String channelName, String description, NotificationManager notificationManager, String tag) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
channelId,
channelName,
NotificationManager.IMPORTANCE_HIGH
);
channel.setDescription(description);
notificationManager.createNotificationChannel(channel);
Log.d(tag, "NotificationChannelを作成: " + channelName);
} else {
Log.d(tag, "Android 7以前のため、NotificationChannelは不要");
}
}
// 呼び出す側
import android.content.Context;
import android.app.NotificationManager;
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
Utils.createNotificationChannels(
notificationEnum.getChannelId(),
notificationEnum.getTitle(),
notificationEnum.getDescription(),
notificationManager,
getTag()
);
}