【Android】アプリのインストールを検知する
今回は、Androidでアプリのインストールを検知する方法を解説します。
なお、ここに掲載しているソースコードは以下の環境で動作確認しています。
- Android Studio Chipmunk | 2021.2.1
- JDK 11.0.12
- Android Gradle Plugin 7.2.0
- Kotlin 1.6.21
- Gradle 7.4.2
アプリのインストールを検知する方法
Android OSは、アプリがインストールされると android.intent.action.PACKAGE_ADDED
というインテントを発信します。よって、このインテントをBroadcastReceiverにて受信することにより、アプリのインストールを検知することができます。
マニフェストファイルの変更
まず、マニフェストファイル AndroidManifest.xml
に以下の設定を追加します。
<receiver android:name=".PackageChangedReceiver" android:enabled="true" android:exported="false"> <intent-filter> <action android:name="android.intent.action.PACKAGE_ADDED" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="package" /> </intent-filter> </receiver>
BroadcastReceiverの実装
そして、以下のようにBroadcastReceiverを実装します。
public final class PackageChangedReceiver extends BroadcastReceiver { @NonNull private static final String TAG = "PackageChangedReceiver"; @Override public void onReceive(final Context context, final Intent intent) { // intent#getDataStringからインストールされたアプリのパッケージ名を取得できる。 final String packageName = intent.getDataString(); Log.d("PackageChanged", packageName); } }
以上の実装により、アプリのインストールを検知できるようになります。
また、intent#getDataStringを使ってインストールされたアプリのパッケージ名を取得することができるため、期待するアプリがインストールされたのか判断することも可能です。
【Android】WebView の JavaScript からネイティブコードを呼び出す
今回は、WebView の JavaScript からネイティブコードを呼び出す方法を解説します。
なお、ここに掲載しているソースコードは以下の環境で動作確認しています。
- Android Studio Bumblebee | 2021.1.1 Patch 3
- JDK 11.0.11
- Android Gradle Plugin 7.1.3
- Kotlin 1.6.21
- Gradle 7.4.2
JavaScript からネイティブコードを呼び出すまでの流れ
WebView の JavaScript からネイティブコードを呼び出すには、以下を行う必要があります。
- JavaScript とネイティブコードをつなぐインターフェースを作成する
- JavaScript とネイティブコードのインターフェースを WebView に追加する
- WebView の JavaScript を有効にする
今回は、WebView に表示する Web ページにボタンを用意し、そのボタンを押すと、Android ネイティブの Toast を表示するようにします。また、ネイティブコードは JavaScript に対して戻り値を返すようにし、JavaScript はその戻り値を Web ページに表示するようにします。
JavaScript とネイティブコードをつなぐインターフェースを作成する
まず、JavaScript とネイティブコードをつなぐインターフェースを作成します。
class WebAppInterface(private val context: Context) { @JavascriptInterface fun showMessage(message: String): String { Toast.makeText(context, message, Toast.LENGTH_LONG).show() return message } }
インターフェースには、JavaScript から呼び出すネイティブコードをメソッドとして作成します。このメソッドには、@JavascriptInterface を付与し、public にする必要があります。
今回作成するメソッドでは、JavaScript から文字列を受け取るようにし、その文字列を Toast に表示します。また、受け取った文字列をそのまま戻り値として呼び出し元に返します。
WebView の設定
次に、WebView にさきほど作成したインターフェースを追加し、JavaScript を有効にします。
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WebView(this).apply { setContentView(this) loadUrl("file:///android_asset/index.htm") val webAppInterface = WebAppInterface(this@MainActivity) addJavascriptInterface(webAppInterface, "Android") settings.apply { allowFileAccess = true javaScriptEnabled = true } } } }
まず、以下のように WebView にさきほど作成したインターフェースを追加します。
WebView#addJavascriptInterface の第 1 引数にインターフェースのインスタントを渡します。そして、第 2 引数には、JavaScript から呼び出す際の名前を指定します。例えば、この名前に "Android"
を指定した場合、JavaScript から呼び出す際は Android.showMessage()
となります。
WebView(this).apply { ︙ val webAppInterface = WebAppInterface(this@MainActivity) addJavascriptInterface(webAppInterface, "Android") ︙
そして、WebView の JavaScript を有効にします。デフォルトでは JavaScript は無効になっているので、有効にする必要があります。
WebView(this).apply { ︙ settings.apply { ︙ javaScriptEnabled = true } }
以上で、ネイティブの実装は完了です。これで、JavaScript からネイティブコードを呼び出すことができるようになります。
JavaScript からネイティブコードを呼び出す
JavaScript からネイティブコードを呼び出してみます。Web ページの HTML を以下のようにします。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> </head> <body> <script type="text/javascript"> function showMessage(message) { const result = Android.showMessage(message); document.getElementById("text").innerHTML = result; } </script> <input type="button" value="Button" onClick="showMessage('Hello Android!');" /> <p id="text"></p> </body> </html>
ボタンが押されたら、JavaScript とネイティブコードのインターフェースの showMessage メソッドを呼びます。この呼び出しにより、Android ネイティブの Toast が表示されます。また、showMessage メソッドの戻り値を p タグに表示します。
参考
【Android】LiveDataで値を監視する
LiveData
は監視可能なデータホルダークラスです。通常の監視とは異なり、LiveData
は Android のライフサイクルに応じた監視が可能です。つまり、アクティビティ、フラグメント、サービスなどの他のアプリコンポーネントのライフサイクルが考慮されます。
build.gradle
の dependencies
の設定は以下の通り。
// ViewModel implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version") // LiveData implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version")
ここでは、以下の2種類の LiveData
を解説します。
- LiveData
- MutableLiveData
LiveData
データが更新されたとき、監視者にそれを通知します。データを直接更新することはできません。
LiveData#observe
public void observe(LifecycleOwner owner, Observer<? super T> observer)
アクティビティやフラグメントなどの監視者が監視を開始します。引数 owner
には監視者のライフサイクルオーナーを指定します。監視者がアクティビティのときは this
を指定します。監視者がフラグメントのときは viewLifecycleOwner
を指定します。これは、フラグメントのライフサイクルはアクティビティのそれとは異なり、不要な更新通知などを避けるためです。
以下は、フラグメントが LiveData
を監視する場合の observe
の使用方法です。
viewModel.count.observe(viewLifecycleOwner, { binding.countText.text = it.toString() })
LiveData#getValue
public T getValue()
LiveData
に格納されている値を取得します。
MutableLiveData
データが更新されたとき、観察者にそれを通知します。データを直接更新することができます。LiveData
を継承しています。
一般的に、クラスのカプセル化を意識して、MutableLiveDataプロパティを非公開にし、LiveDataプロパティのみを公開します。
class MainViewModel : ViewModel() { private val _count = MutableLiveData(0) val count: LiveData<Int> get() = _count
MutableLiveData#setValue
public void setValue(T value)
MutableLiveData
に値を設定します。メインスレッドから値を設定する場合に使用します。
MutableLiveData#postValue
public void postValue(T value)
MutableLiveData
に値を設定します。ワーカースレッドから値を設定する場合に使用します。
参考
Pythonのf文字列による文字列補完
Pythonで文字列補完をするとき、今までは文字列メソッドformat()を使用していたのですが、Python3.6から追加されたf文字列(フォーマット済み文字列リテラル)を用いることで、より簡潔に記述出来ることがわかったので、ここにその方法を記載します。
サンプルソース
以下にformat()による文字列補完とf文字列による文字列補完を記載します。
w = 'World' msg = 'Hello,{}!'.format(w) print(msg) # Hello,World!
w = 'World' msg = f'Hello,{w}!' print(msg) # Hello,World!
上記の通り、冗長なformat()呼び出しの記述がなくなるため、f文字列による文字列補完のほうがソースコードが簡潔になります。
書式指定
文字列メソッドformat()と同様に、f文字列でも置換フィールドでコロン:のあとに書式指定文字列を指定することで様々な書式を指定出来ます。
s = 'abc' right = f'right : {s:_>8}' center = f'center: {s:_^8}' left = f'left : {s:_<8}' print(right) print(center) print(left) # right : _____abc # center: __abc___ # left : abc_____
i = 1234 s = f'{i:08}' print(s) # 00001234
参考
Pythonのvenvでパッケージ管理する
今回は、venvを用いたPythonのパッケージ管理方法を解説します。
パッケージ管理とは?
Pythonでは、pip
コマンドによりパッケージをインストールすることが出来ます。このときにそのままパッケージをインストールすると、ユーザーディレクトリなどにインストールされ、各プロジェクト共通になります。
しかし、プロジェクト毎にパッケージを隔離したい場合があります。例えば、特定のプロジェクトのパッケージのバージョンを古いバージョンのままにしたいなどです。そのような場合に、パッケージ管理を利用します。
前準備
venv自体はPython3.3から標準搭載であるため、Pythonのバージョンが3.3以上であれば、venvのインストール作業は必要ないです。
まず、プロジェクトディレクトリを以下の構成にします。
[Project Directory] ├src │ ├__init__.py │ └hoge.py └venv
上記のvenvディレクトリは仮想環境ディレクトリなどと呼ばれ、このディレクトリ以下にインストールしたパッケージが保存されていきます。最初、venvディレクトリの中は空にします。ディレクトリ名はvenvでなくてもよいですが、一般的にvenvと命名することが多いので、そのままとします。
なお、srcディレクトリ以下にソースコードを配置します。このディレクトリも制限はないですが、srcディレクトリ以下にソースコードを配置するのが一般的なので、こちらもそれに倣います。
利用方法
仮想環境の作成
カレントディレクトリをプロジェクトディレクトリ直下にした状態で、コマンドプロンプトなどで以下のコマンドを打ちます。
>python -m venv [venv_dir_name]
[venv_dir_name]
には仮想環境ディレクトリのディレクトリ名を入力します。なので、今回はvenvとなります。
仮想環境の作成に成功すると、以下のように仮想環境ディレクトリの配下に新たにディレクトリやファイルが生成されます。
[Project Directory] ├src │ ├__init__.py │ └hoge.py └venv ├Include ├Lib ├Scripts └pyvenv.cfg
Activate
以下のようなコマンドを入力して仮想環境を有効にします。
>.\[venv_dir_name]\Scripts\activate ([venv_dir_name]) >
コマンド入力後、コマンドプロンプト上で仮想環境ディレクトリ名が表示されるようになったと思います。
次に、インストール済みパッケージを確認するために、pip freeze
コマンドを入力します。
([venv_dir_name]) >pip freeze ([venv_dir_name]) >
入力の結果、パッケージのインストール状態がまっさらになっていることがわかります。
パッケージのインストール
pip install
コマンドを入力して、パッケージをインストールします。インストール方法は、venvを使用していないときと同じです。
([venv_dir_name]) >pip install [package name]
次に、再度pip freeze
コマンドを入力します。
([venv_dir_name]) >pip freeze [package name1]==x.x.x [package name2]==x.x.x ・ ・ ・
入力の結果、仮想環境にパッケージがインストールされたことがわかります。
Deactivate
仮想環境を無効にするには、deactivate
コマンドを入力します。
([venv_dir_name]) >deactivate >
入力の結果、コマンドプロンプトから仮想環境ディレクトリ名の表示が消えて、仮想環境を有効にする前の状態に戻ったことがわかります。
仮想環境の破棄と作り直し
仮想環境を1から作り直したい場合など、作成済みの仮想環境を破棄したい場合は、仮想環境ディレクトリ配下のディレクトリとファイルをすべて削除してください。
削除後、再度venv
コマンドを入力することにより、仮想環境を作り直すことが出来ます。
【Android】SavedStateHandle解説
今回は、AndroidのSavedStateHandleを解説します。
SavedStateHandle
クラスは、set()
メソッドおよび get()
メソッドを介して、SavedState
との間でデータの書き込みや取得を行えるようにする Key-Value マップです。また、getLiveData()
を使用して LiveData
オブザーバブルにラップされている値を SavedStateHandle
から取得できます。キーの値が更新されると、LiveData
が新しい値を受け取ります。
build.gradle
の dependencies
では、以下を必要とします。
implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version")
使用例
例えば、以下のように使用します。
class MainViewModel( // ViewModelの第1引数にSavedStateHandleを指定します。 private val handle: SavedStateHandle ) : ViewModel() { companion object { // 値の出し入れに使用するキーを定数宣言します。 private const val KEY_COUNT = "count" } // SavedStateHandleからキーKEY_COUNTにひも付くMutableLiveDataを探して取り出します。 // 存在しない場合は新しく作ります。 // そして、SavedStateHandle内でキーKEY_COUNTに対してひも付けます。 // 初期値0。 private val _count = handle.getLiveData(KEY_COUNT, 0) val count: LiveData<Int> get() = _count fun countUp() { // _count.valueはInt?型ですが、SavedStateHandle#getLiveData呼び出し時に初期値を設定しており、 // nullが入り込むことがありません。よって、!!によって強制的にアンラップしています。 val currentCount = _count.value!! // SavedStateHandle#getLiveDataにて値を取り出している場合、 // SavedStateHandle#setで値の格納と同時に監視者への更新通知が行われます。 // なお、SavedStateHandle#getにて値を取り出している場合には、 // 監視者への更新通知が行われません。 // また、内部的にはMutableLiveData#setValueが呼ばれているため、 // ワーカースレッドから呼び出してはいけません。 handle.set(KEY_COUNT, currentCount + 1) }
また、ViewModel
の取得でby viewModels()
を使用している場合、viewModels()
が ViewModel
に SavedStateHandle
のインスタンスを渡しているため、コードを変更する必要はありません。
class MainFragment : Fragment(R.layout.main_fragment) { private val viewModel: MainViewModel by viewModels()
参考
Pythonでプロパティを実装する
今回は、Pythonでのプロパティの実装方法を解説します。
プロパティとは?
ここで取り扱うプロパティとは、クラス利用者がそのメンバ変数にアクセスするときにgetterやsetterを意識することなくアクセス出来るようにする仕組みです。
通常、クラスを実装するときはメンバ変数を直接外部に公開せず、プライベートにしつつ、別途getterとsetterを用意します。これをカプセル化と呼びます。よって、クラス利用者はgetterやsetterを介してメンバ変数にアクセスします。
class Name1: def __init__(self): self._firstname = '' self._lastname = '' def get_firstname(self): return self._firstname def set_firstname(self, firstname): self._firstname = firstname def get_lastname(self): return self._lastname def set_lastname(self, lastname): self._lastname = lastname def get_fullname(self): return self._firstname + self._lastname name = Name1() name.set_firstname('Fugataro') name.set_lastname('Hogemoto') print(name.get_firstname()) print(name.get_lastname()) print(name.get_fullname()) # Fugataro # Hogemoto # FugataroHogemoto
しかし、プロパティを用いることで、カプセル化を維持しつつ、クラス利用者はgetterやsetterを意識することなくメンバ変数にアクセス出来るようになります。
Pythonでプロパティを実装する方法は2通りあるため、両方解説していきます。
property関数を用いる方法
1つ目はproperty関数を用いる方法です。
まず、今まで通りにgetterとsetterを宣言します。そして、property関数に先ほど宣言したgetterとsetterの関数名を渡します。こうすることにより、クラス利用者はインスタンス名.メンバ変数名
のフォーマットでメンバ変数にアクセス出来るようになりつつ、クラス内部では、先ほどproperty関数に渡したgetterとsetterを経由して、メンバ変数の受け渡しが行われます。
class Name2: def __init__(self): self._firstname = '' self._lastname = '' def get_firstname(self): return self._firstname def set_firstname(self, firstname): self._firstname = firstname def del_firstname(self): del self._firstname firstname = property( get_firstname, set_firstname, del_firstname ) def get_lastname(self): return self._lastname def set_lastname(self, lastname): self._lastname = lastname def del_lastname(self): del self._lastname lastname = property( get_lastname, set_lastname, del_lastname ) def get_fullname(self): return self._firstname + self._lastname fullname = property(get_fullname) name = Name2() name.firstname = 'Fugataro' name.lastname = 'Hogemoto' print(name.firstname) print(name.lastname) print(name.fullname) # Fugataro # Hogemoto # FugataroHogemoto
また、メンバ変数をdel
する関数も登録することが出来ます。以下サンプルソースでは実際にメンバ変数をdel
していますが、del
した後にそのメンバ変数にアクセスすると例外が発生するため、del
が正常に機能していることがわかります。
name = Name2() name.firstname = 'Fugataro' print(name.firstname) del name.firstname print(name.firstname) # Fugataro # AttributeError: 'Name2' object has no attribute '_firstname'
デコレータを用いる方法
2つ目はデコレータを用いる方法です。
デコレータを用いる場合も、getter、setter、deleterを宣言しますが、それら関数に、@property
、@メンバ変数名.setter
、@メンバ変数名.deleter
を付与するだけです。
class Name3: def __init__(self): self._firstname = '' self._lastname = '' @property def firstname(self): return self._firstname @firstname.setter def firstname(self, firstname): self._firstname = firstname @firstname.deleter def firstname(self): del self._firstname @property def lastname(self): return self._lastname @lastname.setter def lastname(self, lastname): self._lastname = lastname @lastname.deleter def lastname(self): del self._lastname @property def fullname(self): return self._firstname + self._lastname name = Name3() name.firstname = 'Fugataro' name.lastname = 'Hogemoto' print(name.firstname) print(name.lastname) print(name.fullname) # Fugataro # Hogemoto # FugataroHogemoto
name = Name3() name.firstname = 'Fugataro' print(name.firstname) del name.firstname print(name.firstname) # Fugataro # AttributeError: 'Name3' object has no attribute '_firstname'