- 投稿日:2020-01-24T21:57:16+09:00
初心者の初心者による初心者のためのAndroidアプリ開発
はじめに
こんにちは。
現在、高専の4年生をしており、最近Androidアプリ開発について勉強しています。きっかけとしては単純なもので、自身の研究内容にAndroidアプリの開発の知識が必要だったというだけです。プログラミング自体に興味を持ってからの日々が浅いので、正直見るもの見るもの新鮮です。この記事を見られているということは、タイトルにも書かれているようにAndroidアプリ開発の初心者、もしくは、これから学んでいくという方がほとんどだと思います。そのため、『これからAndroidアプリ開発について学んでいきたいと思ったけどプログラミング全然知らないけど大丈夫かな?』って方をメインに、つまずきやすいと感じた部分を初心者なりに精一杯わかりやすく解説して学んでいけたらと思っています。
イベントとイベントハンドラ、リスナ
今回はイベントとイベントハンドラとリスナについて解説していきます。正直、イベントとリスナはまだしも、イベントハンドラなんていう単語聞いたことないと思います。もちろん私も聞いたことなかったです。意味自体は非常に簡単で一度聞いてしまえば難しく考える必要なものではないです。
イベントとは、ボタンのタップ、アイコンをドラッグなど使用者が画面に対して何かの操作を実行することです。いわゆるスマートフォンの基本操作です。そのイベントに対して実行する処理のことをイベントハンドラと呼びます。こちらは、画面の遷移やアプリケーションの起動などといった動作のことです。そして、このイベントの検出を行っているものをリスナと呼びます。
この考え方自体はAndroidに限らず、iOSやデスクトップアプリなどといった使用者の操作に応じて特定の処理を実行するアプリケーションでの共通の考え方でもあります。Androidでリスナを設定する手順
ここでは実際に簡易的なアプリケーションの作成をしていきます。あらかじめ下記のコードを記述しておくことをお勧めします。このとき、『override』にコンパイルエラーが発生していても問題ありません。
java/com.websarva.wings.android.ibennto/MainActivity.kt
1.各イベントに対応したリスなクラスを作成する
2.リスナクラス内の所定のメソッド(つまりイベントハンドラ)に処理を記述する
3.リスナクラスのインスタンスを生産してリスナ設定メソッドを引数(媒介)として渡すリスナクラスの作成する
MainActivityのonCreate()メソッド以降に次のコードを記述します。ここでは、privateインナークラスにListenerクラスを追記することで、『ボタンのタップ』という操作に対してリスナクラスへ移ります。
java/com.websarva.wings.android.ibennto/MainActivity.kt
リスナクラスには専用のインターフェースを実装します。タップという操作に対してのリスナクラスを作成するには『View.OnClickListener』インターフェースを実装します。インターフェースを実装した時点でそのインターフェースに定義されているメソッドを記述する必要があります。今回のコードのView.OnClickListenerインターフェースではonClick()メソッドのことです。つまり、これがイベントハンドラです。
リスナクラス内の所定のイベントハンドラに処理を記述する
次に、イベントハンドラであるonClick()メソッドに処理を記述していきます。EditTextから入力された文字を取得し、TextViewに表示させる動作です。
java/com.websarva.wings.android.ibennto/MainActivity.kt
リスナクラスのインスタンスを生産してリスナ設定メソッドを引数として渡す
最後にリスナを設定します。onCreate()メソッド内に以下のコードを挿入します。
java/com.websarva.wings.android.ibennto/MainActivity.kt
完成です!
最後に
今回作成したものは単純なアプリケーションですが、今後の必要となる要素が詰まっているので繰り返し学習していきたいですね。
本記事は、『基礎&応用力をしっかり育成! Androidアプリ開発の教科書 Kotlin対応 なんちゃって開発者にならないための実践ハンズオン』を参考にしております。
- 投稿日:2020-01-24T21:15:01+09:00
Android 10で、端末からIMEIなどのSIM情報を取得する方法
はじめに
Android 10から、端末からIMEIなどのSIM情報を取得するためには、「READ_PRIVILEGED_PHONE_STATE」という権限をAndroidManifest.xmlに追加する必要が出てきました。
Android 10 以降で IMEI とシリアル番号の両方を含む再設定不可能なデバイス ID にアクセスするには、アプリに READ_PRIVILEGED_PHONE_STATE 特権が必要です。
注意: Google Play ストアからインストールされたサードパーティ製アプリでは特権を宣言できません。
Privacy changes in Android 10 | Android デベロッパー | Android Developersより引用追加するにあたって、以下のように
tools:ignore="ProtectedPermissions"
を追加することで、Android Studioから警告されることなく追加できます。(tools:ignore="ProtectedPermissions"
を追加しなくても一応ビルドはできます)AndroidManifest.xml<uses-permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE" tools:ignore="ProtectedPermissions" />しかし、サードパーティ製アプリ(Google Playに公開されているアプリ)では宣言していても、特権を利用することはできません。該当メソッド呼び出し時にSecurityExceptionがスローされます。
上記の宣言に加えて、主に以下の対応が必要になります。
- プリインアプリ化する
- 「READ_PRIVILEGED_PHONE_STATE」をホワイトリストに追加する
プリインアプリ化する
「READ_PRIVILEGED_PHONE_STATE」を有効にするには、まずアプリをプリインアプリ化する必要があります。
プリインアプリとは、主に/system/app
や/system/priv-app
配下にあるアプリのことです。出荷時にあらかじめインストールされているアプリのことですね。
(端末によっては/product/priv-app
配下にもあることもあるようです。)
その中でも、/system/priv-app
配下にある、「システム権限を必要とする特権アプリ」である必要があります。「READ_PRIVILEGED_PHONE_STATE」をホワイトリストに追加する
「READ_PRIVILEGED_PHONE_STATE」をAndroidManifest.xmlに追加し、プリインアプリ化したのち、さらに以下のような感じで権限をホワイトリストに追加する必要があります。
ファイル名に関しては、厳密にこの命名じゃないとダメというのはないらしいです。/etc/permissions/privapp-permissions-OEM_NAME.xml<permissions> <privapp-permissions package="アプリのパッケージ名"> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> </privapp-permissions> </permissions>この辺りはおそらく手動で追加するものではなく、端末によっても格納先が異なると思われるので、あくまで参考程度にしてください。当然ながら、追加するには端末をroot化しないとできません。
注意
- プリインアプリがショートカットアプリ(起動するとGoogle Playのアプリページに飛ばすだけのもの)の場合、ショートカットアプリに対しても「READ_PRIVILEGED_PHONE_STATE」を宣言しないと無効になってしまうので注意!
- プリインアプリ更新時、APKファイルの格納先が移動されるようですが、どうやら仕様のようです。
- アプリ更新時にAndroidManifestがまるまる上書きされるものかと思っていたのですが、まるまる上書きはされず、以前に宣言した内容は残るらしいです。AndroidManifestの仕組みについて勉強しておかないとまずいと実感しました。
- 複数のマニフェスト ファイルをマージする | Android デベロッパー | Android Developersの内容を理解すると良いのかもしれないと感じました。(まだちゃんと読めてませんが)
コマンドメモ
この対応を行うにあたって使用したコマンドを下記にまとめます。
対象のアプリ(APKファイル)が端末内のどこにあるのかを調べる
adb shell pm list packages -f | grep -i "アプリのパッケージ名"プリインアプリの上書き(root権限必須)
adb root adb remount adb push "APKファイルのパス" /system/app/ adb shell chmod 644 /system/app/"APKファイル名" adb reboot端末のシャットダウン(再起動を繰り返すようになってしまった場合に使用)
adb shell reboot -pホワイトリストを端末内にプッシュ
adb push "ホワイトリストのパス" /etc/permissions/参考URL
- 投稿日:2020-01-24T18:08:13+09:00
Androidで特定部分だけ角丸にする
shape
のcorner
にtopLeftRadius
みたいなパラメータがあるので、角丸にしたい部分だけ指定してあげれば角丸になる。以下は上だけ角丸にした場合。<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/white" /> <corners android:topLeftRadius="4dp" android:topRightRadius="4dp" /> </shape>全体角丸は
radius
で指定したほうがわかりやすい<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/white" /> <corners android:radius="4dp" /> </shape>
- 投稿日:2020-01-24T16:57:51+09:00
[Android] コールバック
作るもの
以下をコールバックを使って実装する。
・ボタンを押すと非同期処理を実行する
・非同期処理が終わったら画面に「終了」を表示する
用意するクラス
・AsyncTaskCallback.java コールバックインターフェース
・MainActivity.java コールバックインターフェースを実装する
・HttpAsync.java 非同期処理クラス使い方
AsyncTaskCallback.java
public interface AsyncTaskCallbacks { public void onTaskFinished(); //終了 public void onTaskCancelled(); //キャンセル }MainActivity.java
public class MainActivity extends AppCompatActivity implements AsyncTaskCallbacks { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setAsyncButton(); } private void setAsyncButton(){ Button button = findViewById(R.id.button); button.setOnClickListener((View v)->{ HttpAsync httpAsync = new HttpAsync(this); httpAsync.execute(); //非同期処理呼び出し }); } @Override public void onTaskFinished() { TextView textView = findViewById(R.id.text); textView.setText("終了"); } @Override public void onTaskCancelled() { TextView textView = findViewById(R.id.text); textView.setText("キャンセル"); } }HttpAsync.java
public class HttpAsync extends AsyncTask<Integer, Void, Integer> { private AsyncTaskCallbacks callbacks_ = null; //コールバック登録用コンストラクタ public HttpAsync(AsyncTaskCallbacks _callBacks){ this.callbacks_ = _callBacks; //コールバック登録 } @Override protected Integer doInBackground(Integer... params) { //何かの処理 return null; } @Override protected void onPostExecute(Integer result) { callbacks_.onTaskFinished(); //非同期処理が終了したらonTaskFinished()を呼ぶ } @Override protected void onCancelled() { callbacks_.onTaskCancelled(); //非同期処理がキャンセルされたらonTaskCancelled()を呼ぶ } }感想
参考URLのサイトがかなりわかりやすくて簡単に実装できた。
参考URL
- 投稿日:2020-01-24T16:45:18+09:00
libGDXでandroidアプリのチューニングをした話
はじめに
もう9年前。iOSアプリが日本ランキングの1位になった。
iOSソリティアV
さりげなく自慢調子に乗ってandroidアプリも作った。nativeで作った。
nativeで当時android2.xなので、SurfaceViewを使った。
androidソリティアVアニメがいまいちだったけど、ちゃんとできた。
フレームレートがちょっと低かった。android/iOS共に現在10周年記念中。
数年前、チューニングしたいな、と思った。
iOSとソース一本化
したいけど、一本化するにはやることが多すぎる・・・。うーん、GLSerfaceViewとかか。めんどくさそうだぞ。しかもよくなるか保証がない。
そんなとき、こんな記事が
Android StudioでlibGDX入門ああlibGDX・・・web化しようとして、前にやったなあ。GWTがいまいちで途中で止めたんだった。
一部の画面のみlibGDXとか、できるのか。そんなわけで一部の機能のみlibGDX、問題ない機能はそのままnative。な構成のandroidアプリとしてチューニングしてみることになったのであった。
結果
良かった点
- ちゃんとチューニングになった。開発も結構サクサクいけた。
- 前に捨てたlibGDXコードもちゃんと再利用できた。
- Javaソース追えるので、いろいろ助かった。
- android nativeのレイアウトをlibGDX画面の上に出したりもできた。ほんとに最低限のみ、libGDX化で行けた。楽だった。
- 使い慣れたテクスチャパッカーも使える。
悪かった点
- 日本語の突っ込んだ情報が無い・・・
最後に
結構良かった。androidアプリとして、間違った選択ではなかった。
- 投稿日:2020-01-24T12:13:05+09:00
Androidアプリでソフトウェアキーボードの入力を1文字ずつ検出する
概要
Androidで1文字ずつ文字の検出をしようと思ってonKey~をつかってもできませんでした.
ちょっと調べたところ,ソフトウェアキーボードのキーボードイベントを拾ってくれないらしいです.
https://developer.android.com/training/keyboard-input/commandsので,1文字ずつ文字の検出する機能を実装しました.
EditTextの場合,TextChangedListenerを使えば一応できますが,入力ではなくてEditTextのテキストが変わったら反応するため処理がめんどくさいので使いません.
ソースコード
https://github.com/yokoro13/Get-Software-KeyBoard-Input
実装
今回はEditTextの場合で実装していきます.
この実装方法だと(たぶん)ほかのViewでもつかえます.WebViewでは使えました.やることは簡単で,もとになるViewを継承して
onCreateInputConnection
とdispatchKeyEvent
をオーバーライトするだけです.
dispatchKeyEvent
でKeyEvent
を拾えるので,そこで入力時にしたいことを記述します.MyEditText.ktpackage com.usr.myedittexttest import android.content.Context import android.util.AttributeSet import android.util.Log import android.view.KeyEvent import android.view.inputmethod.BaseInputConnection import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection import android.widget.EditText class MyEditText : EditText { constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection { return BaseInputConnection(this, false) } override fun dispatchKeyEvent(event: KeyEvent): Boolean { val dispatchFirst = super.dispatchKeyEvent(event) if (event.action == KeyEvent.ACTION_UP){ val charCode = event.unicodeChar // 入力時の処理 Log.d("MyEditText", "onKey keyCode=$charCode") Log.d("MyEditText", "onKey input=${charCode.toChar()}") } return dispatchFirst } }新しいViewはもとのViewと同じように使用できます.
MainActivitypackage com.usr.myedittexttest import androidx.appcompat.app.AppCompatActivity import android.os.Bundle class MainActivity : AppCompatActivity() { lateinit var myEditText: MyEditText override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) myEditText = findViewById(R.id.my_edit) } }
activity_main.xml
は使うViewを作成したものに変更します.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"> <com.usr.myedittexttest.MyEditText android:id="@+id/my_edit" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>入力を通知するListenerを作成する場合
入力したことを通知するListenerを使ったほうが便利なのでListenerを作成します.
まずListenerを作成
InputListenerpackage com.usr.myedittexttest import java.util.* interface InputListener: EventListener { fun onKey(text: Char) }次に
MyEditText
を少し改造MyEditTextpackage com.usr.myedittexttest import android.content.Context import android.util.AttributeSet import android.util.Log import android.view.KeyEvent import android.view.inputmethod.BaseInputConnection import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputConnection import android.widget.EditText class MyEditText : EditText { private var listener: InputListener? = null constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) override fun onCreateInputConnection(outAttrs: EditorInfo): InputConnection { return BaseInputConnection(this, false) } override fun dispatchKeyEvent(event: KeyEvent): Boolean { val dispatchFirst = super.dispatchKeyEvent(event) if (event.action == KeyEvent.ACTION_UP){ val charCode = event.unicodeChar // 入力時の処理 Log.d("MyEditText", "onKey keyCode=$charCode") Log.d("MyEditText", "onKey input=${charCode.toChar()}") // 入力を通知 listener?.onKey(charCode.toChar()) } return dispatchFirst } fun setListener(listener: InputListener){ this.listener = listener } }
MainActivity
では入力の通知を受け取った時の処理を記述します.
そのためにInputListenerを実装します.
通知を受け取るとonKey
が呼ばれるので,そこに処理を記述してください.MainActivity.ktpackage com.usr.myedittexttest import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log class MainActivity : AppCompatActivity(), InputListener{ lateinit var myEditText: MyEditText override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) myEditText = findViewById(R.id.my_edit) myEditText.setListener(this) } override fun onKey(text: Char) { Log.d("MainActivity", "text=$text") // TODO something } }終わりに
勉強し始めたばかりですので,より良い方法があるかと思います.よろしければお教えくださいますと幸いです.