アプリインストール後、一度も起動されていないアプリはSTOP状態となります。
システムアプリを除いて、STOP状態のアプリはBroadcastIntentを受け取れない場合があり
ます。
# 概要はhttp://developer.android.com/sdk/android-3.1.htmlで紹介されています。
今回は、アプリのSTOP状態について調査します。
調査は下記のコードで実施しています。
BOOT_BOMPLETEを受け取るだけのレシーバをAndroidManifestで静的に登録しているアプリです。
AndroidManifest.xml
<receiver android:name="MyBroadcastReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> </intent-filter> </receiver>
yuki.broadcast.MyBroadcastReceiver
public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { android.util.Log.e("yuki", "yuki onReceive"); } }
上記で作成したアプリで下記手順を実施し、ブロードキャストインテントを受け取れてい
るかどうかをログ("yuki onReceive")から判断します。
# 手順は上から順番に実施。
1)端末にアプリを新規にインストールしてブロードキャスト送信
adb install apk/BroadcastTest.apk adb shell am broadcast -a android.intent.action.BOOT_COMPLETED結果:反応なし/インストール直後はSTOP状態
2)再起動して再度ブロードキャスト送信
adb shell reboot adb shell am broadcast -a android.intent.action.BOOT_COMPLETED結果:反応なし/STOP状態は再起動しても変わらない
3)アプリ起動後にプロセスkillした後にブロードキャスト送信
adb shell am start -n yuki.broadcast/.BroadcastTestActivity adb shell kill <アプリPID> adb shell am broadcast -a android.intent.action.BOOT_COMPLETED結果:反応あり/一度アプリ起動すれば、今まで通りブロードキャスト受信は可能
4)端末再起動後にブロードキャスト送信
adb shell reboot adb shell am broadcast -a android.intent.action.BOOT_COMPLETED結果:反応あり/一度でもアプリ起動していれば、再起動後でも受信可能
5)アプリを更新した後にブロードキャスト送信
adb install -r apk/BroadcastTest.apk adb shell am broadcast -a android.intent.action.BOOT_COMPLETED結果:反応あり/アプリ更新後も、継続して受信可能
6)アプリをアンインストール→再インストール後にブロードキャスト送信
adb uninstall yuki.broadcast adb install apk/BroadcastTest.apk adb shell am broadcast -a android.intent.action.BOOT_COMPLETED結果:反応なし/再インストール後は再度STOP状態になる
7)FLAG_INCLUDE_STOPPED_PACKAGESを指定してブロードキャスト送信
# パッケージ再インストール
adb uninstall yuki.broadcast adb install apk/BroadcastTest.apk adb shell am broadcast -a android.intent.action.BOOT_COMPLETED --include-stopped-packages結果:反応あり/FLAG_INCLUDE_STOPPED_PACKAGESを付与すればSTOP状態でも受信可能
8)FLAG_INCLUDE_STOPPED_PACKAGESで受信後にFLAG_EXCLUDE_STOPPED_PACKAGES指定で送信
# パッケージ再インストール adb uninstall yuki.broadcast adb install apk/BroadcastTest.apk # FLAG_INCLUDE_STOPPED_PACKAGES付与したブロードキャスト送信 adb shell am broadcast -a android.intent.action.BOOT_COMPLETED --include-stopped-packages # FLAG_EXCLUDE_STOPPED_PACKAGES付与したブロードキャスト送信 adb shell am broadcast -a android.intent.action.BOOT_COMPLETED --exclude-stopped-package結果:反応あり/FLAG_INCLUDE_STOPPED_PACKAGES指定で受信さえすればSTOP状態は解除
される
まとめ:
- 一度でもアプリを起動すれば、再起動後でも受信可能
- アプリの更新でSTOP状態にはならない
- アプリの再インストールはSTOP状態になる
- FLAG_INCLUDE_STOPPED_PACKAGESで一度でも受信すればSTOP状態は解除される
Android4.x以降でもこれは有効です。
「ブロードキャストを受信できない」といった不具合が出た場合、STOP状態となっていな
いか確認したほうがよいでしょう。
●ソースコード解析
本動作・仕様についてシステム側のソースコードを解析してみます。
ブロードキャストインテント送信開始からのコードを追いました。
IntentResolverでIntentを処理するコンポーネントを探索。
com.android.server.IntentResolver.buildResolveList(...)
final boolean excludingStopped = intent.isExcludingStopped(); final int N = src != null ? src.size() : 0; boolean hasNonDefaults = false; int i; for (i=0; i<N; i++) { F filter = src.get(i); int match; if (debug) Slog.v(TAG, "Matching against filter " + filter); if (excludingStopped && isFilterStopped(filter)) { if (debug) { Slog.v(TAG, " Filter's target is stopped; skipping"); } continue; }
excludingStoppedはIntentフラグのFLAG_EXCLUDE_STOPPED_PACKAGESがONである場合True。
isFilterStoppedは対象がSTOP状態である場合True。
両方Trueの場合は、Filter targe(アプリ)がSTOP状態と判断してIntent処理しない。
対象のSTOP状態を調べるメソッドを追ってみます。
com.android.server.pm.PackageManagerService.ActivityIntentResolver.isFilterStopped(...)
@Override protected boolean isFilterStopped(PackageParser.ActivityIntentInfo filter) { PackageParser.Package p = filter.activity.owner; if (p != null) { PackageSetting ps = (PackageSetting)p.mExtras; if (ps != null) { // System apps are never considered stopped for purposes of // filtering, because there may be no way for the user to // actually re-launch them. return ps.stopped && (ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0; } } return false; }
コメントを見るとシステムアプリはSTOP状態でもIntentの受信は可能のようです。
ps.stoppedの設定元を追っていくとcom.android.server.pm.Settings.readStoppedLPw()
の下記にたどり着きます。
String tagName = parser.getName(); if (tagName.equals("pkg")) { String name = parser.getAttributeValue(null, "name"); PackageSetting ps = mPackages.get(name); if (ps != null) { ps.stopped = true; if ("1".equals(parser.getAttributeValue(null, "nl"))) { ps.notLaunched = true; } } else { Slog.w(PackageManagerService.TAG, "No package known for stopped package: " + name); }
なにやらXMLをパースして、pkgタグのname属性に記載されているパッケージのnl属性値が
1であればパッケージSTOP状態としています。
パースしているXMLは/data/system/packages-stopped.xmlのようです。
yuki.broadcastアプリを一度も起動していないと下記のようなXMLの内容になります。
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <stopped-packages> <pkg name="yuki.broadcast" nl="1" /> ... その他色々 </stopped-packages>
一度でも起動するとpkgタグは削除されるので、
ps.stopped = true;
となるルートは通らなくなります。
これらのことから、下記手順を試してみます。
# パッケージ再インストール adb uninstall yuki.broadcast adb install apk/BroadcastTest.apk # packages-stopped.xmlを取得 adb pull /data/system/packages-stopped.xml packages-stopped.xml # ... # packages-stopped.xmlからyuki.broadcastのtagを削除 # ... # 編集したpackages-stopped.xmlで置き換える adb pull packages-stopped.xml /data/system/packages-stopped.xml # ブロードキャスト送信 adb shell am broadcast -a android.intent.action.BOOT_COMPLETED結果:反応あり/STOP状態はpackages-stopped.xmlで管理されている
以上です。