2012/10/10

Android:packages.xmlとデフォルト起動Activity

●デフォルトActivityの選択

ほとんどの場合、共有アクションに反応できるActivityは複数存在します。
ActivityChooserにより、ユーザはどのActivityで処理するかを選択します。

ICS以前とJB以降とで、そのUIは若干変わりましたが基本は同じです。
ユーザはActivityの選択を"次回以降にも反映する(Always)"か"今回限りのものとする(Just once)"かを決めることができます。
Activity選択画面

ユーザがActivityの選択を次回以降にも反映させようとした場合。
次回以降、同じIntentが投げられると、ActivityChooserは表示されず選択したActivityが起動されます。
これは"デフォルト起動設定"と呼ばれる機能で、都度ユーザが選択動作をする手間を省くものです。

本稿では、デフォルト起動設定の対象となったActivityをデフォルト起動Activityと記載します。


●packages.xml

デフォルト起動Activityの情報は/data/system/packages.xmlに永続化されます。
com.android.server.pm.Settingsはこれをパースし、Activity起動を制御する情報として扱います。

packages.xmlの中を見ていきます。
デフォルト起動Activityの情報はpreferred-activitiesタグ配下に列挙されます。
工場出荷状態だと空タグになっていることがほとんどです。
<preferred-activities />

この状態で、adb shell am start -a android.intent.action.MAINコマンドを投げます。
するとaction.MAINに反応する多くのActivityが一覧表示されます。
ここで[Always]を選択してデフォルト起動Activityを決定します。

10秒程放置してからpackages.xmlをpullするとpreferred-activitiesに変化があります。
<preferred-activities>
  <item name="com.android.systemui/.DreamsDockLauncher" match="100000" set="82">
    <set name="com.android.settings/.inputmethod.InputMethodAndSubtypeEnablerActivity" />
    <set name="com.android.settings/.Settings$AccessibilitySettingsActivity" />
    <set name="com.android.systemui/.DreamsDockLauncher" />
    ...省略...
    <filter>
      <action name="android.intent.action.MAIN" />
      <cat name="android.intent.category.DEFAULT" />
    </filter>
  </item>
</preferred-activities>

なにやらIntentFilterらしき情報とコンポーネントらしき情報が追加されています。
順を追ってそれぞれの内容をみていきます。


●preferred-activities

・<preferred-activities>
デフォルト起動Activity情報のブロックです。
preferred-activitiesは0以上のitem要素を内包します。

・<item>
デフォルト起動Activityの情報です。
ActivityChooserでユーザが[Always]指定した情報がここに格納されます。
itemタグには、1つのIntentと、関連する複数のコンポーネント情報が登録されます。

・<item>@name
name属性はデフォルト起動Activityのコンポーネント名です。
<filter>にマッチするIntentを検知すると、ここで指定されたActivityが起動します。

・<item>@match
<Filter>で定義されるIntentFilterのマッチング種別値です。
この値はIntentFilterクラスの下記定数で定義されます。
  0x0100000 :MATCH_CATEGORY_EMPTY
  0x0200000 :MATCH_CATEGORY_SCHEME
  0x0600000 :MATCH_CATEGORY_TYPE
   ...等々
例えば、
・IntentFilterに<data>を持たない場合はMATCH_CATEGORY_EMPTY
・<data android:scheme>を持つ場合はMATCH_CATEGORY_SCHEME
・<data android:mimeType>を持つ場合はMATCH_CATEGORY_TYPE
といった具合です。

ちなみに、SchemeとTypeの両方を持つ場合はTypeが優先される等、優劣も存在します。

・<item>@set
Activity(<set>)の総数です。

・<set>
<Filter>で定義されるIntent情報に関連するActivity情報です。
Activityが複数ある場合は、複数の<set>が定義されます。

・<set>@name
<Filter>で定義されるIntent情報に関連するActivityのコンポーネント名です。

・<filter>
デフォルト起動するActivityのIntent情報です。
内包する子要素(actionやcategory)はIntentFilterのそれと同等なので省略します。

packages.xmlの中を読めば、
 "Intentに反応できるActivityが複数あった場合にデフォルト起動されるであろうActivity"
の手がかりとなります。


●packages.xmlが更新されるタイミング

デフォルト起動Activityの情報はpackages.xmlを参照して判断されますが、
Intentが発行される度に参照するわけではありません。
packages.xmlをパースした情報はメモリ上にキャッシュし、通常はこちらを参照します。

packages.xmlのpreferred-activities情報が更新される契機をいくつか挙げます。
・Intentに対するActivityのデフォルト起動情報を変更した
・アプリケーション情報画面等から[デフォルト起動情報]をクリアした
・新しいアプリケーションがインストールされた


●packages.xmlの鮮度

メモリ上にキャッシュされている情報とpackages.xmlの情報に差異が生じる時間が存在します。
PackageManagerServiceはメモリ上に展開したpackages.xmlの情報に変更が発生すると、
およそ10秒後にファイルシステム上のpackages.xmlを更新します。
更新タイミングにディレイがあるため、packages.xmlの古い状態が存在します。
# Activityのデフォルト起動設定を変更した直後に電池抜きを行うと
# デフォルト起動設定の変更が再起動後に反映されていないことに気づくでしょう。


●関連クラス

packages.xmlに関連するクラスをいくつか挙げます。

デフォルト起動Activity情報クラス。
  • com.android.server.pm.PreferredActivity
  • com.android.server.PreferredComponent

Intentのマッチング関連
  • android.content.IntentFilter.matchData
どのマッチング種別値になるのかがここで決まります。

packages.xmlのI/O
  • com.android.server.pm.Settings.readLPw
  • com.android.server.pm.Settings.writeLPw
com.android.server.pm.Settings.mSettingsFilenameがpackages.xmlのファイルパスになります。

packages.xmlの更新タイミング
  • com.android.server.pm.PackageManagerService.scheduleWriteSettingsLocked
HandlerへWRITE_SETTINGSメッセージを10000msディレイで送信していることが確認できます。

以上です。