Smile Engineering Blog

ジェイエスピーからTipsや技術特集、プロジェクト物語を発信します

【Android】アプリのインストールを検知する

今回は、Androidでアプリのインストールを検知する方法を解説します。

なお、ここに掲載しているソースコードは以下の環境で動作確認しています。

アプリのインストールを検知する方法

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 からネイティブコードを呼び出す方法を解説します。

なお、ここに掲載しているソースコードは以下の環境で動作確認しています。

JavaScript からネイティブコードを呼び出すまでの流れ

WebView の JavaScript からネイティブコードを呼び出すには、以下を行う必要があります。

  1. JavaScript とネイティブコードをつなぐインターフェースを作成する
  2. JavaScript とネイティブコードのインターフェースを WebView に追加する
  3. 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 タグに表示します。

参考

WebView での JavaScript の使用

【Android】LiveDataで値を監視する

LiveData は監視可能なデータホルダークラスです。通常の監視とは異なり、LiveDataAndroid のライフサイクルに応じた監視が可能です。つまり、アクティビティ、フラグメント、サービスなどの他のアプリコンポーネントのライフサイクルが考慮されます。

build.gradledependencies の設定は以下の通り。

// 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 に値を設定します。ワーカースレッドから値を設定する場合に使用します。

参考

LiveData の概要

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.gradledependencies では、以下を必要とします。

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()ViewModelSavedStateHandleインスタンスを渡しているため、コードを変更する必要はありません。

class MainFragment : Fragment(R.layout.main_fragment) {
    private val viewModel: MainViewModel by viewModels()

参考

ViewModel の保存済み状態のモジュール

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'

参考

class property

カプセル化