Androidアプリの放送受信機登録(registerReceiver)処理の分析
Androidのブロードキャスト機構は、メッセージの購読/公開機構であることを先に紹介しましたので、このメッセージドリブンモデルを使うには、まずメッセージを購読する必要があります。この記事では、Androidアプリがブロードキャスト受信者をどのように登録し、どこに登録すればよいかを探ります。
書籍「Androidソースコード・シナリオ解析」は、プログラマーの攻防のサイト(
http://0xcc0xcd.com
をクリックし、お入りください。
Androidのブロードキャスト機構では、ActivityManagerServiceがブロードキャストセンターの役割を果たし、システム内のすべてのブロードキャストの登録と公開を担当します。そのため、Androidアプリケーションでブロードキャスト受信機を登録する処理は、ActivityManagerServiceにブロードキャスト受信機を登録する処理となります。Androidアプリケーションは、ContextWrapperクラスのregisterReceiver関数を呼び出して、ContextWrapperクラス自身がBroadcastReceiverをActivityManagerServiceに登録します。 ContextWrapperクラス自身がContextImplクラスでBroadcastレシーバを登録するのです。
Androidアプリケーションフレームワークでは、ActivityクラスとServiceクラスは共にContextWrapperクラスを継承しているので、ActivityやServiceのサブクラスでregisterReceiver関数を呼び出してブロードキャスト受信機を登録できます。activity、Service、ContextWrapper、ContextImplクラスは、前述した Androidでカスタムサービスのプロセス(startService)を新規プロセスで起動する原理 記事で説明したActivityのクラス図
この記事では、前回の記事に引き続き、シナリオの分析を例にしています AndroidにおけるBroadcast機構の簡単な紹介と学習計画 そこで、この記事を読んでから続きを読んでいただければと思います。また、AndroidアプリケーションはActivityManagerServiceにブロードキャストコネクタを登録するため、Binderのプロセス間通信機構が関わってきますので、AndroidシステムのBinderについて理解していただければと思います。 そこで、Androidシステムのプロセス間通信機構について理解していただくために Androidプロセス間通信(IPC)機構Binderの概要紹介と学習計画 記事の内容
本題に入り、その AndroidにおけるBroadcast機構の簡単な紹介と学習プラン 記事で紹介した例では、放送受信機の登録はMainActivityから開始されるので、登録処理のシーケンス図を見てみましょう。
このシーケンス図を分析する前に、MainActivityがregisterReceiver関数を呼び出して放送受信機を登録している様子を見てみましょう。
public class MainActivity extends Activity implements OnClickListener {
......
@Override
public void onResume() {
super.onResume();
IntentFilter counterActionFilter = new IntentFilter(CounterService.BROADCAST_COUNTER_ACTION);
registerReceiver(counterActionReceiver, counterActionFilter);
}
......
}
MainActivityは、親クラスContextWrapperのregisterReceiver関数を通して、BroadcastReceiverインスタンス counterActionReceiverをonResume関数で登録し、 IntentFilterインスタンス counterActionFilterを通して、購読したい放送がCounterService型であるとActivityManagerServiceに伝えています。 BROADCAST_COUNTER_ACTION] 型であることを伝え、 ActivityManagerServiceが [BROADCAST_COUNTER_ACTION] 型を受信すると、CounterActionReceiverインスタンスの onReceive関数に配信するようにします。
次に、登録の各ステップの分析に入ります。
ステップ1.contextWrapper.registerReceiver
この関数は、 frameworks/base/core/java/android/content/ContextWrapper.java ファイルにある
public class ContextWrapper extends Context {
Context mBase;
......
@Override
public Intent registerReceiver(
BroadcastReceiver receiver, IntentFilter filter) {
return mBase.registerReceiver(receiver, filter);
}
......
}
ここでメンバ変数mBaseはContextImplのインスタンスです。
Androidアプリ起動処理ソースコード解析
この記事>~<。
ステップ2. ContextImpl.registerReceiver
この関数は、frameworks/base/core/java/android/app/ContextImpl.java ファイルに実装されています。
class ContextImpl extends Context {
......
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
return registerReceiver(receiver, filter, null, null);
}
@Override
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
String broadcastPermission, Handler scheduler) {
return registerReceiverInternal(receiver, filter, broadcastPermission,
scheduler, getOuterContext());
}
private Intent registerReceiverInternal(BroadcastReceiver receiver,
IntentFilter filter, String broadcastPermission,
Handler scheduler, Context context) {
IIntentReceiver rd = null;
if (receiver ! = null) { if (mPackageInfo !
if (mPackageInfo ! = null && context ! = null) {
if (scheduler == null) {
scheduler = mMainThread.getHandler();
}
rd = mPackageInfo.getReceiverDispatcher(
receiver, context, scheduler,
mMainThread.getInstrumentation(), true);
} else {
......
}
}
try {
return ActivityManagerNative.getDefault().registerReceiver(
mMainThread.getApplicationThread(),
rd, filter, broadcastPermission);
} catch (RemoteException e) {
return null;
}
}
......
}
2つの関数を経て、ようやくContextImpl.registerReceiverInternal関数に行き着きます。メンバ変数mPackageInfoはLoadedApkのインスタンスで、ブロードキャストの受信処理を担当しており、後のsendingBroadcastsの記事で詳しく説明する予定です。パラメータ broadcastPermission と scheduler は null、パラメータ context は上記関数から関数 getOuterContext を呼び出して取得します。ここでは MainActivity を指していますが、これは MainActivity が Context クラスを継承しているため、ここでは Context 型で参照するようにしています。
mPackageInfo != null と context != null という条件になっているので、mPackageInfo ! = null、scheduler == nullという条件も成り立つので、mMainThread.getHandlerを呼び出してHandlerを取得し、後でActivityManagerServiceが送信するブロードキャストを配信するために使用する。ここで、メンバ変数mMainThreadはActivityThreadのインスタンスであり、先ほどの Androidアプリケーションの起動処理に関するソースコード解析 この記事でも解説しています。ActivityThread.getHandler 関数の実装を見て、戻って ContextImpl.registerReceiverInternal 関数の解析の続きをしましょう。
ステップ3. ActivityThread.getHandler(アクティビティスレッドゲットハンドラ
この関数は、frameworks/base/core/java/android/app/ActivityThread.java ファイルにある
public final class ActivityThread {
......
final H mH = new H();
private final class H extends Handler {
......
public void handleMessage(Message msg) {
......
switch (msg.what) {
......
}
......
}
......
}
......
final Handler getHandler() {
return mH;
}
......
}
このHandlerを設置することで、アプリケーションが処理するメッセージを配信することができるようになります。
ContextImpl.registerReceiverInternal 関数の前のステップに戻ると、 mPackageInfo.getReceiverDispatcher 関数を通して IIntentReceiver インターフェースオブジェクト rd を取得し、次にこれを Binder オブジェクトとして渡します これは ActivityManagerService に渡され、この Binder オブジェクトを介して MainActivity に対応するブロードキャストを受信すると通知しています。
また、mPackageInfo.getReceiverDispatcher関数の実装を最初に見て、その後戻ってContextImpl.registerReceiverInternal関数の解析を続けています。
ステップ4.LoadedApk.getReceiverDispatcher(ロードアパックゲットレシーバーディスパッチャー
この関数は、frameworks/base/core/java/android/app/LoadedApk.java ファイルに実装されています。
final class LoadedApk {
......
public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r,
Context context, Handler handler,
Instrumentation instrumentation, boolean registered) {
synchronized (mReceivers) {
LoadedApk.ReceiverDispatcher rd = null;
HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null;
if (registered) {
map = mReceivers.get(context);
if (map ! = null) {
rd = map.get(r);
}
}
if (rd == null) {
rd = new ReceiverDispatcher(r, context, handler,
instrumentation, registered);
if (registered) {
if (map == null) {
map = new HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>();
mReceivers.put(context, map);
}
map.put(r, rd);
}
} else {
rd.validate(context, handler);
}
return rd.getIIntentReceiver();
}
}
......
static final class ReceiverDispatcher {
final static class InnerReceiver extends IIntentReceiver.Stub {
final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher;
......
InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) {
mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd);
......
}
......
}
......
final IIntentReceiver.Stub mIIntentReceiver;
final Handler mActivityThread;
......
ReceiverDispatcher(BroadcastReceiver receiver, Context context,
Handler activityThread, Instrumentation instrumentation,
boolean registered) {
......
mIIntentReceiver = new InnerReceiver(this, !registered);
mActivityThread = activityThread;
......
}
......
IIntentReceiver getIIntentReceiver() {
return mIIntentReceiver;
}
}
......
}
LoadedApk.getReceiverDispatcher関数では、まず、パラメータrに対応するReceiverDispatcherが既に存在するかどうかを確認します。存在する場合はそのまま返され、存在しない場合は新しいReceiverDispatcherが作成され、rの値をKeyとしてHashMapに保持し、このHashMapをLoadedApkのメンバー変数mReceiversにContextで保存し、ここではMainActivityをKey値として、ActivityとBroadcastReceiverさえ与えられていれば、対応するBroadcastReceiverがすでにLoadedApk内に存在するかどうかチェックできるようになっています。というように、ActivityとBroadcastReceiverが与えられれば、対応するReceiverDispatcherがLoadedApk内にすでに存在するかどうかを確認することができます。
BroadcastReceiverDispatcherを新規に作成すると、コンストラクタでInnerReceiverのインスタンスを作成します。これはIIntentReceiverインターフェースを実装したBinderオブジェクトで、ReceiverDispatcher.getIIntentReceiver関数でアクセスでき、取得した後はActivityManagerServiceに渡されて放送を受信することになります。また、ReceiverDispatcherクラスのコンストラクタでは、受信したHandle型パラメータactivityThreadを保存し、後でブロードキャストを配信する際に利用できるようにしています。
さて、ContextImpl.registerReceiverInternal関数に戻り、IIntentReceiver型のBinderオブジェクトを取得したら、いよいよActivityManagerServiceに登録する。
ステップ5. ActivityManagerProxy.registerReceiver
この関数は、frameworks/base/core/java/android/app/ActivityManagerNative.java ファイルに実装されています。
class ActivityManagerProxy implements IActivityManager
{
......
public Intent registerReceiver(IApplicationThread caller,
IIntentReceiver receiver,
IntentFilter filter, String perm) throws RemoteException
{
Parcel data = Parcel.object();
Parcel reply = Parcel.object();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller ! = null ? caller.asBinder() : null);
data.writeStrongBinder(receiver ! = null ? receiver.asBinder() : null);
filter.writeToParcel(data, 0);
data.writeString(perm);
mRemote.transact(REGISTER_RECEIVER_TRANSACTION, data, reply, 0);
reply.readException();
Intent intent = null;
int haveIntent = reply.readInt();
if (haveIntent ! = 0) {
intent = Intent.CREATOR.createFromParcel(reply);
}
reply.recycle();
data.recycle();
return intent;
}
......
}
この関数はBinderドライバを経由してActivityManagerServiceのregisterReceiver関数に渡ります。
ステップ6.activityManagerService.registerReceiver
この関数は、frameworks/base/services/java/com/android/server/am/ActivityManagerService.java ファイル内の
public final class ActivityManagerService extends ActivityManagerNative
Monitor, BatteryStatsImpl.
......
public Intent registerReceiver(IApplicationThread caller,
IIntentReceiver receiver, IntentFilter filter, String permission) {
synchronized(this) {
ProcessRecord callerApp = null;
if (caller ! = null) {
callerApp = getRecordForAppLocked(caller);
if (callerApp == null) {
......
}
}
List allSticky = null;
// Look for any matching sticky broadcasts...
Iterator actions = filter.actionsIterator();
if (actions ! actionsIterator(); if (actions != null) {
while (actions.hasNext()) {
String action = (String)actions.next();
allSticky = getStickiesLocked(action, filter, allSticky);
}
} else {
......
}
// The first sticky in the list is returned directly back to
// the client.
Intent sticky = allSticky ! = null ? (Intent)allSticky.get(0) : null;
......
if (receiver == null) {
return sticky;
}
ReceiverList rl
= (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
if (rl == null) {
rl = new ReceiverList(this, callerApp,
Binder.getCallingPid(),
Binder.getCallingUid(), receiver);
if (rl.app ! = null) {
rl.app.receivers.add(rl);
} else {
......
}
mRegisteredReceivers.put(receiver.asBinder(), rl);
}
BroadcastFilter bf = new BroadcastFilter(filter, rl, permission);
rl.add(bf);
......
mReceiverResolver.addFilter(bf);
// Enqueue broadcasts for all existing stickies that match
// this filter.
if (allSticky ! = null) {
......
}
return sticky;
}
}
......
}
この関数は、registerReceiver関数を呼び出したアプリケーションプロセスのレコードブロックを取得することから始まります。
ProcessRecord callerApp = null;
if (caller ! = null) {
callerApp = getRecordForAppLocked(caller);
if (callerApp == null) {
......
}
}
ここで得られるのは、前の記事
AndroidにおけるBroadcast機構の簡単な紹介と学習計画
これは前回紹介したBroadcastアプリケーションのプロセスログブロックで、MainActivityが起動されるところです。
List allSticky = null;
// Look for any matching sticky broadcasts...
Iterator actions = filter.actionsIterator();
actionsIterator(); if (actions ! actionsIterator(); if (actions != null) {
while (actions.hasNext()) {
String action = (String)actions.next();
allSticky = getStickiesLocked(action, filter, allSticky);
}
} else {
......
}
// The first sticky in the list is returned directly back to
// the client.
Intent sticky = allSticky ! = null ? (Intent)allSticky.get(0) : null;
broadcast_counter_actionです。ここではまず、getStickiesLocked関数を使って、対応するスティッキーインテントリストが存在するかどうかを調べます。スティッキーインテントとは?あるActionタイプのブロードキャストを送信するためにsendStickyBroadcast関数を最後に呼び出すと、システムはこのブロードキャストを表すIntentを保存し、後で同じActionタイプのブロードキャスト受信者を登録するためにregisterReceiverを呼ぶと、この最後に送信したブロードキャストを取得することができます。このため、Sticky Intentと呼ばれる。この最後のブロードキャストは処理されますが、ActivityManagerServiceに残っているため、対応するActionタイプを登録した次の受信者はまだそれを継承することができます。
ここで、sendStickyBroadcast を使って CounterService.BROADCAST_COUNTER_ACTION タイプのブロードキャストを送信しないと仮定すると、ここでは allSticky と sticky を null として取得することになります。
続けて、ここで渡された受信機はnullではないので、続行します。
ReceiverList rl
= (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
if (rl == null) {
rl = new ReceiverList(this, callerApp,
Binder.getCallingPid(),
Binder.getCallingUid(), receiver);
if (rl.app ! = null) {
rl.app.receivers.add(rl);
} else {
......
}
mRegisteredReceivers.put(receiver.asBinder(), rl);
}
ここで実際にReceiverListリストに保存されているブロードキャストレシーバーは、ホストプロセスのリストはrl.app、ここではMainActivityが配置されているプロセス、アプリケーションプロセスを表すプロセスレコードブロックでは、ActivityManagerServiceで、このプロセスによって登録されたブロードキャストレシーバーを保存するために使用される、受信者のリストを持っています。そして、このReceiverListは、ActivityManagerServiceのメンバ変数mRegisteredReceiversに、Key値をreceiverとして格納され、ブロードキャスト受信時に該当するブロードキャスト受信機を素早く探すために使用されます。
さらに下を見ると
BroadcastFilter bf = new BroadcastFilter(filter, rl, permission);
rl.add(bf);
......
mReceiverResolver.addFilter(bf);
上記は放送受信機を保存するだけで、まだフィルターと関連付けられていないので、ここではBroadcastFilterを作成して放送受信機リストrlとフィルターを関連付け、ActivityManagerServiceのメンバー変数mReceiverResolverに保存しています。
これで放送受信機の登録は終わりですが、比較的簡単ですが些細なことです。主な内容は、ActivityManagerServiceにブロードキャストレシーバの受信機と受信するブロードキャストフィルタの種類を保存して、後で該当するブロードキャストを受信して処理できるようにすることです。次回は、この処理を詳しく解析していきますので、ご期待ください。
ラオ・ルオの新浪微博。
http://weibo.com/shengyangluo
とフォローを歓迎します
関連
-
Androidでの録音とMP3へのローカルトランスコード
-
Android ViewPager のエラーです。NULLオブジェクトの参照で仮想メソッドxxxを呼び出そうとした
-
selectionに主な型が含まれていないエラー
-
Android 問題集 No.11:トランスポートエンドポイントが接続されていない
-
root化されているのですが、adb shellの後、suを入力するとpermission deniedと表示されます。
-
アンドロイドプログレスバー自定义
-
Android統計チャート MPAndroidChart
-
自作のシンプルなアンドロイド用メモ帳アプリ
-
Android TextViewは、あるテキストのカラー・フォント・サイズを設定する
-
ARMアセンブリ共通命令 NULL演算 NOP命令
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
BindView 問題 NULLオブジェクト参照で仮想メソッド 'void android ...' を呼び出そうとする
-
アプリの実行エラー。デフォルトのアクティビティが見つかりません
-
Android Studioの解決策:xxxは囲むクラスではありませんエラー
-
Android Studioで「Error:SSL peer shut down incorrectly」というエラーが表示される。
-
Androidアプリ】【形状利用概要
-
Android Studioの設定 Gradleの概要
-
Androidで色を取得するいくつかの方法
-
Android開発日記】SwipeRefreshLayoutにプルアップ読み込み機能を追加しました
-
android.view.inflateexception バイナリ xml ファイル行例外の解決方法
-
Android AVDで "このターゲットにはシステムイメージがインストールされていません "と表示される