概要
Activityなしのアプリを作ったときに、BroadcastReceiverが上手く動いてくれなかったので色々と調べてみた結果をまとめてみます。
背景
Bluetoothテザリングってすごい便利なんだけど、本体のBluetoothをOFFにするとテザリング設定が自動でOFFになってしまい、いちいち設定画面にいかなきゃいけないのが面倒だと思ったのが発端です。
既存のアプリを使うことも検討したけど、なんか怖かった(!)ので自分で作ってみました。
後ろで勝手に動くだけだし、UIなんていらない、BluetoothをONにしたときに自分で勝手に動いてくれるアプリを作りたかったので、Serviceを使ってアプリを作ることを検討しました。
Bluetoothの状態が変更されたときというのは、BroadcastReceiverを使用すればなんとか出来そうでしたし、Bluetoothのテザリングをオンにする方法も、調べてみるとなんとか出来そうな感じでした。(無理やりな方法ですが)
アプリの流れとしてはこんな感じ。
- アプリをインストールする
- BluetoothのON/OFFを切り替える
- BroadcastReceiverが切り替えのイベント時に動く
- Serviceが起動して、BluetoothのテザリングをONにする
とっても簡単な流れですね。
作り始めた当初は、こんなのすぐ出来るだろって思っていましたよ。ええ。とある問題にハマるまでは。
どうハマったのか
Bluetoothを受け取るようのReceiverクラスを作成し、テザリングをONにするためのServiceクラスも作って、よしこれをManifestファイルに登録すれば終わりだ。
そう思ってこんな感じのマニフェストファイルを書きました。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.litmon.app.autobluetoothtetheringchanger">
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<service android:name=".BluetoothService" />
<receiver android:name=".BluetoothReceiver">
<intent-filter>
<!-- Catch Event to BLUETOOTH_STATE_CHANGED -->
<action android:name="android.bluetooth.adapter.action.STATE_CHANGED" />
</intent-filter>
</receiver>
</application>
</manifest>
Bluetoothの状態を取得したり変更したりするので、パーミッションの設定をし、自作のServiceとReceiverを登録。
Receiverでは、Bluetoothのオンオフを取得するためのアクションを設定。
カンペキです。カンペキな流れです。
さぁ、これでいよいよ最高のテザリングライフが送れる!
そう思った私を、誰が咎められるでしょうか。
===
そして、アプリは動きませんでした。
登録したはずのReceiverに、処理が届かない。
なぜだ。
(ここにハマって3ヶ月位このアプリの開発を放置……)
動かなかった原因
アプリの需要的には自分の欲が満たせれば良かったので、この問題にハマってもういいやを繰り返していたらいつの間にか3ヶ月くらい経ってたのですが、さすがにそろそろと思い本腰を入れて原因を探ってみると、こんな記事を見つけました。
ここによると、Android3.1以降の端末では、BroadcastReceiverをAndroidのシステムに登録するためには、Activityが最低一回は起動した状態でないとダメらしい。
詳しくは、 Android 3.1 APIs | AndroidDevelopers の
Launch controls on stopped applications
という項目に書かれています。(以下、本文中の引用はこちらのサイトから引用しています)
簡単に説明すると、アプリが止まっている状態というのが、Androidでは2つの意味で定義されています。
- FLAG_INCLUDE_STOPPED_PACKAGES
- FLAG_EXCLUDE_STOPPED_PACKAGES
上のやつは、アプリを起動していないときって意味です。これは問題ではありません。
注意するのが2つ目のやつ。
これは、以下の説明を読むと理解できました。
Applications are in a stopped state when they are first installed but are not yet launched and when they are manually stopped by the user (in Manage Applications).
簡単に和訳すると、
「アプリを初めてインストールして、かつ一回も起動していない時にFLAG_EXCLUDE_STOPPED_PACKAGESがつくよ。あ、あとアプリマネージャから手動でアプリを止めた時もね。」
ということ。
要は、起動が確認できてないアプリには、起動してないってことを表すフラグが立った状態になるわけですね。
そして、以下の文章がとどめでした。(一部、分かりやすいように改変しています)
FLAG_EXCLUDE_STOPPED_PACKAGES does this to prevent broadcasts from background services from inadvertently or unnecessarily launching components of stopped applications.
「FLAG_EXCLUDE_STOPPED_PACKAGESがついてるアプリは、バックグラウンドのサービスとかではbroadcastを受け取れないようにしているよ。ごめんね。」
……ほう。
結論
ということで、結論として
「ActivityなしのアプリでIntentを受け取ることが出来るか」
という今回の議題に関しては、
「No!」
という結論が出てしまいました。
なんとも残念な結果に終わってしまいましたが、まぁAndroidのシステム上定められているのならばしょうがないでしょう。
ということで、同じような悩みを持った人は、
「あぁ、仕様だから仕方ない」
ということで自分を慰めてあげましょう。
ちなみに
Bluetoothのテザリング自動ONアプリですが、結局Activityを初回のみ起動することでなんとか運用しています。(初回のみなら大して手間でもないし)
もしかしてまさかこんな人はいないと思いますが、「Bluetoothのテザリングをコードからオンにする方法が知りたい!」って人がもしいたら、以下のリポジトリに全コード置いてあるので、勝手に使ってやってください。
具体的には、BluetoothPanUtil#setTetheringOn
というメソッドを頑張って呼べば動きます。
BluetoothService
クラスを見ればどうやって使ってるかもなんとなく分かると思うので、ご参考までに。