サービスの概要#
サービスの特徴
サービスは AMS によって管理されるオブジェクトです。
サービスはアクティビティと一緒に起動することなく、単独で起動できます(ただし、そのサービスに属するプロセス、つまり ActivityThread
を起動する必要があります)。
AMS は ActivityThread にサービスを起動するよう通知します。
一般的にアプリがサービスを起動する方法は 2 つあります。
Context#startService
を使用して指定されたサービスを起動する
Context#bindService
を使用してサービスをバインドする
サービスのバインド#
public class TestService extends AppCompatActivity {
// 匿名クラス
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
}
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
// ダウンキャスト(もちろんBinderが提供するメソッドを使用することもできます)
MyService.MyBindClass bindClass = (MyService.MyBindClass) iBinder;
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
}
ContextImpl - bindService が AMS にレシーバーを登録する呼び出し#
Context の実装クラスは ContextImpl
です(理由は Activity#attach 関数を見ればわかりますが、ここでは詳しく説明しません)。したがって、bindService
を使用する際の実装クラスは ContextImpl です。
IServiceConnection
オブジェクトを取得:LoadApk#getServiceDispatcher
を通じて IServiceConnection
オブジェクトを取得します。IServiceConnection はアプリ側の IBinder サーバーオブジェクトであり、将来的に AMS がコールバックを行います(次の小節で説明します)。
Binder プロキシを取得して AMS にアクセス:ActivityManager#getService
を通じて Binder プロキシクラスを取得し、プロキシクラスを介して AMS#bindIsolatedService
メソッドを呼び出します。
// ContextImpl.java
final @NonNull LoadedApk mPackageInfo;
@Override
public boolean bindService(Intent service, ServiceConnection conn, int flags) {
...
return bindServiceCommon(service, conn, flags, null, mMainThread.getHandler(), null,
getUser());
}
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
String instanceName, Handler handler, Executor executor, UserHandle user) {
IServiceConnection sd;
// 判空
if (conn == null) {
throw new IllegalArgumentException("connection is null");
}
if (handler != null && executor != null) {
throw new IllegalArgumentException("Handler and Executor both supplied");
}
// mPackageInfoの型はLoadedApk
if (mPackageInfo != null) {
// @ 1. 主にLoadedApk#getServiceDispatcherを通じてIServiceConnectionをラップします
if (executor != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), executor, flags);
} else {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, flags);
}
} else {
throw new RuntimeException("Not supported in system context");
}
validateServiceIntent(service);
try {
... 省略部分
// @ 2. AMS#bindIsolatedServiceメソッドを分析します
int res = ActivityManager.getService().bindIsolatedService(
mMainThread.getApplicationThread(), getActivityToken(), service,
service.resolveTypeIfNeeded(getContentResolver()),
sd, flags, instanceName, getOpPackageName(), user.getIdentifier());
// バインド結果
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
}
return res != 0;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
IServiceConnection の作成 - Binder コールバック - サービス接続#
LoadedApk#getServiceDispatcherCommon メソッドを分析します:このメソッドは主に IServiceConnection インターフェースを作成します。このインターフェースは後で AMS がコールバックを行います。
キャッシュをチェック:まず、ローカルキャッシュにそのサービスの Binder コールバックがあるかどうかを確認します(Context がキーとして使用されます)。もしあれば、直接返します。なければ、ServiceDispatcher オブジェクトを作成します。
もしなければ、LoadedApk.ServiceDispatcher オブジェクトを作成します。
getIServiceConnection を通じて Binder サーバーコールバックインターフェースを取得します。
// LoadedApk.java
public final class LoadedApk {
// ServiceDispatcherのキャッシュ
private final ArrayMap<Context, ArrayMap<ServiceConnection,
LoadedApk.ServiceDispatcher>> mServices = new ArrayMap<>();
public final IServiceConnection getServiceDispatcher(ServiceConnection c,
Context context, Handler handler, int flags) {
// @ getServiceDispatcherCommonを追跡
return getServiceDispatcherCommon(c, context, handler, null, flags);
}
private IServiceConnection getServiceDispatcherCommon(ServiceConnection c,
Context context, Handler handler, Executor executor, int flags) {
synchronized (mServices) {
LoadedApk.ServiceDispatcher sd = null;
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher> map =
mServices.get(context);
// 1. キャッシュを判断
if (map != null) {
...
sd = map.get(c); // キャッシュあり
}
if (sd == null) {
// @ 2. キャッシュがない場合、直接作成します(返されたインターフェースに基づいて作成します)
if (executor != null) {
sd = new ServiceDispatcher(c, context, executor, flags);
} else {
sd = new ServiceDispatcher(c, context, handler, flags);
}
...
if (map == null) {
map = new ArrayMap<>();
mServices.put(context, map);
}
map.put(c, sd);
} else {
sd.validate(context, handler, executor);
}
// 3. LoadedApk.ServiceDispatcher#getIServiceConnectionを分析します
return sd.getIServiceConnection();
}
}
}
ServiceDispatcher クラス:LoadedApk の静的内部クラスで、AMS にコールバックを行う Binder は InnerConnection クラス です。AMS がコールバックを行うと、connected
メソッドが呼び出されます。
IServiceConnection.Stub を作成して AMS にコールバックを行います。
AMS のコールバックは、RunConnection
オブジェクトを作成し、Handler
または Executor
を呼び出します。
アプリ側の IServiceConnection サービスの実装、クラス関係図
// LoadedApk.java
public final class LoadedApk {
static final class ServiceDispatcher {
private final ServiceDispatcher.InnerConnection mIServiceConnection;
private static class InnerConnection extends IServiceConnection.Stub {
// 弱参照
final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher;
InnerConnection(LoadedApk.ServiceDispatcher sd) {
mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd);
}
// AMSが呼び出すときにconnectedメソッドを使用します
public void connected(ComponentName name, IBinder service, boolean dead)
throws RemoteException {
LoadedApk.ServiceDispatcher sd = mDispatcher.get();
if (sd != null) {
// @ connectedを分析します
sd.connected(name, service, dead);
}
}
}
// コンストラクタ
ServiceDispatcher(ServiceConnection conn,
Context context, Handler activityThread, int flags) {
// InnerConnectionオブジェクトを作成します
mIServiceConnection = new InnerConnection(this);
... 省略部分
}
IServiceConnection getIServiceConnection() {
// 実装クラスはIServiceConnection.Stubを実装します
return mIServiceConnection;
}
public void connected(ComponentName name, IBinder service, boolean dead) {
// Executorが設定されている場合はExecutorを通じて通知します
if (mActivityExecutor != null) {
// Executorを通じて
mActivityExecutor.execute(new RunConnection(name, service, 0, dead));
} else if (mActivityThread != null) {
// @ Handlerを通じて(ActivityThread#Hクラス)
mActivityThread.post(new RunConnection(name, service, 0, dead));
} else {
doConnected(name, service, dead);
}
}
}
}
AMS を通じた Binder コールバック - ServiceConnection#
RunConnection クラス:Runnable で、現在の状況では Handler を使用しています。この Runnable はActivityThread#Hクラス(これも Handler です)に渡され、コールバックを行います。
// LoadedApk#ServiceDispatcher#RunConnection.java
private final class RunConnection implements Runnable {
// 構築
RunConnection(ComponentName name, IBinder service, int command, boolean dead) {
mName = name;
mService = service;
mCommand = command;
mDead = dead;
}
public void run() {
// 現在渡されているmCommand = 0
if (mCommand == 0) {
// @ doConnectedメソッドを分析します
doConnected(mName, mService, mDead);
} else if (mCommand == 1) {
doDeath(mName, mService);
}
}
final ComponentName mName;
final IBinder mService;
final int mCommand;
final boolean mDead;
}
ServiceDispatcher#doConnected メソッド:アプリに渡された ServiceConnection インターフェースにコールバックを行い、以下のような複数の状況があります。
同名のサーバーキャッシュ | 目標 IBinder の状態 | 説明 | 処理行動 |
---|---|---|---|
Y | 比較が同じ | 既に接続済み | 直接返し、特に処理しない |
Y | 比較が異なる | 既に接続済みだが異なる IBinder プロキシを取得 | 1. まず切断 onServiceDisconnected 、2. 再接続 onServiceConnected |
N | IBinder が空でない | 初回接続 | 接続呼び出し onServiceConnected |
// LoadedApk#ServiceDispatcher.java
private final ServiceConnection mConnection;
public void doConnected(ComponentName name, IBinder service, boolean dead) {
ServiceDispatcher.ConnectionInfo old;
ServiceDispatcher.ConnectionInfo info;
synchronized (this) {
...
old = mActiveConnections.get(name);
// 1. すでにバインドされているかつ同じBinderであるかを判断します
if (old != null && old.binder == service) {
// すでにこの接続があります。まあ、いいでしょう!
return;
}
if (service != null) {
// 新しいサービスをオンラインにします
info = new ConnectionInfo();
info.binder = service;
info.deathMonitor = new DeathMonitor(name, service);
try {
service.linkToDeath(info.deathMonitor, 0);
mActiveConnections.put(name, info);
} catch (RemoteException e) {
// このサービスは取得する前に死んでいました...何もしないでおきます。
mActiveConnections.remove(name);
return;
}
} else {
// サービスが切断された場合、キャッシュから削除します
mActiveConnections.remove(name);
}
if (old != null) {
old.binder.unlinkToDeath(old.deathMonitor, 0);
}
}
// 2. もし古い接続であれば、onServiceDisconnectedを呼び出して切断しますが、異なるIBinderです
if (old != null) {
mConnection.onServiceDisconnected(name);
}
if (dead) {
mConnection.onBindingDied(name);
} else {
// 新しい有効なサービスがある場合、それは現在接続されています。
if (service != null) {
// 3. onServiceConnectedを呼び出します(ユーザーインターフェースに戻ります)
mConnection.onServiceConnected(name, service);
} else {
mConnection.onNullBinding(name);
}
}
}
AMS - サービスの起動、バインドプロセス#
クライアントアプリはContextImpl#bindServiceを通じてAMS#bindIsolatedServiceメソッドをトリガーし、アプリとサービスをバインドします。
サービス Intent には FileDescriptors が必要です。
ActiveServices#bindServiceLocked メソッドを呼び出します。
// ActivityManagerService.java
// 存活しているサービスのリスト
final ActiveServices mServices;
public int bindIsolatedService(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, IServiceConnection connection, int flags, String instanceName,
String callingPackage, int userId) throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// 1. intentをチェックします
// intentにはFileDescriptorsが必要です
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
// クライアントパッケージをチェックします
if (callingPackage == null) {
throw new IllegalArgumentException("callingPackage cannot be null");
}
... 省略部分
// 関数名にLockが含まれている理由は、呼び出しスレッドをブロックするためです
synchronized(this) {
// @ 2. ActiveServices#bindServiceLockedを呼び出します
return mServices.bindServiceLocked(caller, token, service,
resolvedType, connection, flags, instanceName, callingPackage, userId);
}
}
ActiveServices#bindServiceLocked メソッド
呼び出し元プロセスが存在するかどうかを確認します(すでに kill されている可能性があります)。存在しない場合は例外をスローします。
retrieveServiceLocked メソッドを通じて:PKMS から対応するサービス情報を取得します。
アプリ側で設定された BIND_AUTO_CREATE
フラグが設定されている場合、そのフラグを通じて bringUpServiceLocked
メソッドを使用してサービスを自動的に作成します。
requestServiceBindingLocked メソッドを通じて:サービスをバインドするよう要求します。この関数には初回バインドと再バインドの機能が含まれています(違いは最後のパラメータにあります)。
// ActiveServices.java
int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
String resolvedType, final IServiceConnection connection, int flags,
String instanceName, String callingPackage, final int userId)
throws TransactionTooLargeException {
// 呼び出し元のPid、Uid
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
// 1. AMSを通じて呼び出し元プロセスを探します
final ProcessRecord callerApp = mAm.getRecordForAppLOSP(caller);
if (callerApp == null) {
// エラーをスローします
throw new SecurityException(
"Unable to find app for caller " + caller
+ " (pid=" + callingPid
+ ") when binding service " + service);
}
...
// 2. PKMS内のサービスを探します
ServiceLookupResult res =
retrieveServiceLocked(service, instanceName, resolvedType, callingPackage,
callingPid, callingUid, userId, true,
callerFg, isBindExternal, allowInstant);
// resがnullの場合、サービスが存在しないことを示します
if (res == null) {
return 0;
}
// その中のServiceRecordを判断します
if (res.record == null) {
return -1;
}
ServiceRecord s = res.record;
...
try {
...
// サービスを自動的に起動します
if ((flags&Context.BIND_AUTO_CREATE) != 0) {
s.lastActivity = SystemClock.uptimeMillis();
needOomAdj = true;
// サービスを自動的に起動します
if (bringUpServiceLocked(s, service.getFlags(), callerFg, false,
permissionsReviewRequired, packageFrozen, true) != null) {
mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE);
return 0;
}
}
...
AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp);
// サービス接続記録
ConnectionRecord c = new ConnectionRecord(b, activity,
connection, flags, clientLabel, clientIntent,
callerApp.uid, callerApp.processName, callingPackage);
... OomAdjの数値を調整します
// サービスプロセスが起動しているかどうかを判断します(appはProcessRecordです)
if (s.app != null && b.intent.received) {
try {
// ユーザーが渡したインターフェースを通じてコールバックを行います
// 前の小節で分析したように(connはIServiceConnectです)、最終的にはonServiceConnectedを呼び出します
c.conn.connected(s.name, b.intent.binder, false);
} /* 省略 catch */
// 現在のアプリプロセスがサービスプロセスにバインドされた最初のものである場合
// && doRebindメソッドが呼び出された場合
if (b.intent.apps.size() == 1 && b.intent.doRebind) {
// 最後のパラメータは再バインドするかどうかを示します
// @ requestServiceBindingLockedメソッドを分析します
requestServiceBindingLocked(s, b.intent, callerFg, true);
}
} else if (!b.intent.requested) { // アプリ側でバインドされていない
// 最後のパラメータは再バインドするかどうかを示します
requestServiceBindingLocked(s, b.intent, callerFg, false);
}
...
} finally {
Binder.restoreCallingIdentity(origId);
}
return 1;
}
ActiveServices#requestServiceBindingLocked メソッド:渡された ServiceRecord に対して判断、操作を行います。
サービスレコードが指定されたプロセスが起動しているかどうかを確認します(ProcessRecord と IApplicationThread が存在するかどうかを確認します)。つまり、サービスが起動しているかどうかを確認します。
現在、サービスのターゲットプロセスがすでに起動していると仮定すると、そのプロセス内で実装されたIApplicationThread#requestServiceBindingLockedを通じてターゲットプロセスに戻ります。
// ActiveServices.java
private final boolean requestServiceBindingLocked(ServiceRecord r,
IntentBindRecord i,
boolean execInFg,
boolean rebind) throws TransactionTooLargeException {
// ProcessRecordとそのService#IApplicationThreadが存在するかどうかを確認します
if (r.app == null || r.app.getThread() == null) {
// サービスが現在実行されていない場合、バインドできません。
return false;
}
... デバッグメッセージ
// クライアントがバインドしていないか、サービスを再バインドする必要がある場合
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
// IApplicationThread(アプリ側の実装)にscheduleBindServiceメソッドを通知します
// scheduleBindServiceメソッドを分析します
r.app.getThread().scheduleBindService(r, i.intent.getIntent(), rebind,
r.app.mState.getReportedProcState());
if (!rebind) {
i.requested = true;
}
i.hasBound = true;
i.doRebind = false;
} /* 省略 catch */
}
return true;
}
アプリプロセス - Service#onBind
の呼び出し#
ActivityThread - scheduleBindService がサービスのあるプロセスでバインドを行います。
BindServiceData オブジェクトを作成し、ServiceRecord
と Intent
情報を保存します。
サーバープロセスは Binder 通信通知を受け取ると、Handler を通じてメインスレッドに BIND_SERVICE 情報を送信します。
メインスレッドが BIND_SERVICE
メッセージを受け取ると、handleBindService メソッドを呼び出します。初回バインドの場合、1. サービスの onBind メソッドを呼び出し、次に 2. AMS#publishService メソッドを呼び出します。
// ActivityThread.java
private class ApplicationThread extends IApplicationThread.Stub {
... 省略他のメソッド
public final void scheduleBindService(IBinder token, Intent intent,
boolean rebind, int processState) {
...
// 1. BindServiceDataオブジェクトを作成し、ServiceRecordとIntentを保存します
BindServiceData s = new BindServiceData();
// tokenはServiceRecordです
s.token = token;
s.intent = intent;
s.rebind = rebind;
... デバッグメッセージ
// 2. メインスレッドにBIND_SERVICE情報を送信します
sendMessage(H.BIND_SERVICE, s);
}
}
class H extends Handler {
public static final int BIND_SERVICE = 121;
public void handleMessage(Message msg) {
... 省略他のcase
case BIND_SERVICE:
... 省略トレース
handleBindService((BindServiceData)msg.obj);
break;
}
}
private void handleBindService(BindServiceData data) {
// tokenはServiceRecordです
CreateServiceData createData = mServicesData.get(data.token);
// キャッシュを確認します
// mServicesはhandleCreateService時に追加されます
Service s = mServices.get(data.token);
// 取得できない場合、サービスはまだ起動していないことを示します
if (s != null) {
try {
...
try {
if (!data.rebind) {
// 3. 初回バインド時にonBind、AMS#publishServiceメソッドを呼び出します
IBinder binder = s.onBind(data.intent);
// publishServiceはサーバーが起動したことを示します
ActivityManager.getService().publishService(
data.token, data.intent, binder);
} else {
// 再バインドの場合はonRebindを呼び出します
s.onRebind(data.intent);
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
}
try {
// AMSにサービスが実行中であることを通知します
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
} /* 省略 catch */
} /* 省略 catch */
}
}
サービスの公開 - すべてのユーザーに通知#
サービスプロセスが初めて起動する場合、AMS#publishService メソッドが呼び出されます。
// ActivityManagerService.java
// tokenはBindServiceDataです
public void publishService(IBinder token, Intent intent, IBinder service) {
// 漏洩する可能性のあるファイルディスクリプタを拒否します
if (intent != null && intent.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
synchronized(this) {
if (!(token instanceof ServiceRecord)) {
throw new IllegalArgumentException("Invalid service token");
}
// publishServiceLockedメソッドを分析します
mServices.publishServiceLocked((ServiceRecord)token, intent, service);
}
}
**ActiveServices)**publishServiceLocked メソッドは、ServiceRecord を通じてそのサービスに接続されているすべてのアプリ側に通知を行い、逐次通知を行います。
// ActiveServices.java
// tokenはBindServiceDataです
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
final long origId = Binder.clearCallingIdentity();
try {
... デバッグメッセージ
if (r != null) {
...
// そのサービスのバインド情報を取得します
IntentBindRecord b = r.bindings.get(filter);
if (b != null && !b.received) {
b.binder = service;
b.requested = true;
b.received = true;
// ServiceRecordを通じて、そのサービスに接続されているすべての記録を取得します
ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
for (int conni = connections.size() - 1; conni >= 0; conni--) {
ArrayList<ConnectionRecord> clist = connections.valueAt(conni);
// 各接続記録をループします
for (int i=0; i<clist.size(); i++) {
// 現在の接続記録
ConnectionRecord c = clist.get(i);
...
try {
// connはIServiceConnectです
// IServiceConnect#connectedメソッドを分析します
c.conn.connected(r.name, service, false);
} /* 省略 catch */
}
}
}
...
}
} finally {
Binder.restoreCallingIdentity(origId);
}
}
サービスの起動#
public class TestService extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = new Intent(this, MyService.class);
startService(intent);
}
}
ContextImpl startService が AMS を呼び出す#
ActivityManager をプロキシクラスとして使用して AMS の startService メソッドにアクセスします。ここで、startService はコールバックを行いませんが、ComponentName を返すことで起動が成功したかどうかを判断できます。
// ContextImpl.java
@Override
public ComponentName startService(Intent service) {
...
return startServiceCommon(service, false, mUser);
}
private ComponentName startServiceCommon(Intent service, boolean requireForeground,
UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess(this);
// プロキシクラスを通じてAMS#startServiceを呼び出します
ComponentName cn = ActivityManager.getService().startService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()), requireForeground,
getOpPackageName(), getAttributionTag(), user.getIdentifier());
if (cn != null) {
// サービスを起動する権限がありません
if (cn.getPackageName().equals("!")) {
throw new SecurityException(
"Not allowed to start service " + service
+ " without permission " + cn.getClassName());
} else if (cn.getPackageName().equals("!!")) {
// サービスを起動できません
throw new SecurityException(
"Unable to start service " + service
+ ": " + cn.getClassName());
} else if (cn.getPackageName().equals("?")) {
// サービスを起動することは許可されていません
throw ServiceStartNotAllowedException.newInstance(requireForeground,
"Not allowed to start service " + service + ": " + cn.getClassName());
}
}
... 省略部分
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
AMS - サービスの起動プロセス#
ActivityManagerService#startService
Intent が FileDescriptors を含んでいるかどうかを確認します。
ActiveServices#startServiceLocked メソッドを呼び出します。
// ActivityManagerService.java
final ActiveServices mServices;
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, boolean requireForeground, String callingPackage,
String callingFeatureId, int userId)
throws TransactionTooLargeException {
// Intentをチェックします
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
// パッケージをチェックします
if (callingPackage == null) {
throw new IllegalArgumentException("callingPackage cannot be null");
}
... デバッグメッセージ
synchronized(this) {
// 呼び出し元のpidとuidを取得します
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
ComponentName res;
try {
// mServicesの型はActiveServicesです
// startServiceLockedメソッドを分析します
res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid,
requireForeground, callingPackage, callingFeatureId, userId);
} finally {
Binder.restoreCallingIdentity(origId);
}
return res;
}
}
- 現在のデバイスに指定されたサービスが存在するかどうかを確認します。retrieveServiceLocked メソッドを確認します。
- 指定された ServiceRecord を見つけます。
// ActiveServices.java
// callerは呼び出し元
ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
int callingPid, int callingUid, boolean fgRequired,
String callingPackage, @Nullable String callingFeatureId, final int userId,
boolean allowBackgroundActivityStarts, @Nullable IBinder backgroundActivityStartsToken)
throws TransactionTooLargeException {
... 省略部分
// 1. 現在のデバイスに指定されたサービスが存在するかどうかを確認します。retrieveServiceLockedメソッドを確認します
ServiceLookupResult res =
retrieveServiceLocked(service, null, resolvedType, callingPackage,
callingPid, callingUid, userId, true, callerFg, false, false);
// サービスが見つからない場合
if (res == null) {
return null;
}
...
ServiceRecord r = res.record;
... 省略部分
// 2. 見つかったServiceRecordはサービスを記述するために使用されます
return startServiceInnerLocked(r, service, callingUid, callingPid, fgRequired, callerFg,
allowBackgroundActivityStarts, backgroundActivityStartsToken);
}
ServiceRecord はサービスを記述するために使用され、ActivityRecord がアクティビティを記述するのと似ています。
アプリ側の記述 | AMS 側の記述 |
---|---|
アクティビティ | ActivityRecord |
サービス | ServiceRecord |
ActiveServices - PKMS を通じてサービスを探す#
AMS がサービスを探す方法は ServiceLoopupResult
で、目的は ServiceRecord を見つけることです。ServiceRecord を見つけるためには主に PKMS を通じて行います。
// ActiveServices.java
private ServiceLookupResult retrieveServiceLocked(Intent service,
String instanceName, String resolvedType, String callingPackage,
int callingPid, int callingUid, int userId,
boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
boolean allowInstant) {
// 各サービスはServiceRecordに変換されます
ServiceRecord r = null;
ServiceMap smap = getServiceMapLocked(userId);
// ComponentNameを組み立てます
final ComponentName comp;
if (instanceName == null) { // 現在渡されているinstanceNameはnullです
comp = service.getComponent();
} else {
// Componentを通じてComponentNameを組み立てます
final ComponentName realComp = service.getComponent();
if (realComp == null) {
throw new IllegalArgumentException("Can't use custom instance name '" + instanceName
+ "' without explicit component in Intent");
}
comp = new ComponentName(realComp.getPackageName(),
realComp.getClassName() + ":" + instanceName);
}
// ComponentNameの結果を判断します
if (comp != null) {
r = smap.mServicesByInstanceName.get(comp);
}
... 省略部分
if (r == null) {
try {
...
// PackageManagerを通じて対応するサービスを見つけます
ResolveInfo rInfo = mAm.getPackageManagerInternal().resolveService(service,
resolvedType, flags, userId, callingUid);
ServiceInfo sInfo = rInfo != null ? rInfo.serviceInfo : null;
if (sInfo == null) {
// PKMSが見つからない場合は直接nullを返します
Slog.w(TAG_SERVICE, "Unable to start service " + service + " U=" + userId +
": not found");
return null;
}
... 省略部分
} catch (RemoteException ex) {
// 同じプロセス内なので、発生することはありません
}
}
if (r != null) {
... 省略部分
return new ServiceLookupResult(r, null);
}
return null;
}
// キーはcallingUser(int)
final SparseArray<ServiceMap> mServiceMap = new SparseArray<>();
private ServiceMap getServiceMapLocked(int callingUser) {
// キャッシュを取得しようとします
ServiceMap smap = mServiceMap.get(callingUser);
if (smap == null) {
// 新しいService記録オブジェクトを再作成します
smap = new ServiceMap(mAm.mHandler.getLooper(), callingUser);
mServiceMap.put(callingUser, smap);
}
return smap;
}
final class ServiceMap extends Handler {
final int mUserId;
// 呼び出されたサービスはすべて記録されます
final ArrayMap<ComponentName, ServiceRecord> mServicesByInstanceName = new ArrayMap<>();
... 省略部分
}
mServiceMap はユーザーをキーとして使用し、対応する ServiceMap を保存します。内部には ComponentName と ServiceRecord が保存されており、ユーザーが同じサービスを呼び出した場合、キャッシュを取得できます。
サービスの起動エントリ - startServiceInnerLocked#
// ActiveServices.java
final ActivityManagerService mAm;
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
// これもFunctionにLockedキーワードが含まれている理由です
synchronized (mAm.mProcessStats.mLock) {
final ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
}
}
r.callStart = false;
final int uid = r.appInfo.uid;
final String packageName = r.name.getPackageName();
final String serviceName = r.name.getClassName();
... 省略部分
// bringUpServiceLockedを通じてサービスを起動します
String error = bringUpServiceLocked(r, service.getFlags(), callerFg,
false /* whileRestarting */,
false /* permissionsReviewRequired */,
false /* packageFrozen */,
true /* enqueueOomAdj */);
...
return r.name;
}
関数名 | 機能 |
---|---|
bringUpServiceLocked | プロセスを判断し、起動する必要がある場合は AMS を通じて呼び出します |
bringDownServiceLocked | そのサービスに接続されているすべての接続を取得し、サービスを停止します |
bringUpServiceLocked はプロセスの状態を判断し、サービスが存在するプロセスが起動していない場合は AMS を通じてターゲットプロセスを起動します |
// ActiveServices.java
private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
boolean enqueueOomAdj)
throws TransactionTooLargeException {
... 省略判空、Logメッセージ
// サービスが隔離されているかどうか
final boolean isolated = (r.serviceInfo.flags&ServiceInfo.FLAG_ISOLATED_PROCESS) != 0;
// サービスが実行されるプロセスを取得します
final String procName = r.processName;
HostingRecord hostingRecord = new HostingRecord("service", r.instanceName);
ProcessRecord app;
if (!isolated) {
// 1. AMSを通じてProcessRecordオブジェクトを取得しようとします
app = mAm.getProcessRecordLocked(procName, r.appInfo.uid);
if (app != null) { // ProcessRecordが存在する場合
final IApplicationThread thread = app.getThread();
final int pid = app.getPid();
final UidRecord uidRecord = app.getUidRecord();
if (thread != null) {
try {
app.addPackage(r.appInfo.packageName, r.appInfo.longVersionCode,
mAm.mProcessStats);
// 2. realStartServiceLockedでサービスを実際に起動します
realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg,
enqueueOomAdj);
return null;
} /* 省略 catch*/
}
}
} else {
... 省略部分
}
if (app == null && !permissionsReviewRequired && !packageFrozen) {
// ターゲットプロセスが存在しない場合はAMS#startProcessLockedを通じて起動します
if ((app = mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,
hostingRecord, ZYGOTE_POLICY_FLAG_EMPTY, false, isolated)) == null) {
... 起動失敗
// サービスを停止します
bringDownServiceLocked(r, enqueueOomAdj);
return msg;
}
if (isolated) {
r.isolatedProc = app;
}
}
... 省略部分
}
実際にサービスを作成する - realStartServiceLocked#
ProcessRecord 内のスレッド(つまり ActivityThread 内の ApplicationThread オブジェクト)を通じて scheduleCreateService メソッドを呼び出します。
// ActiveServices.java
private void realStartServiceLocked(ServiceRecord r, ProcessRecord app,
IApplicationThread thread, int pid, UidRecord uidRecord, boolean execInFg,
boolean enqueueOomAdj) throws RemoteException {
// ターゲットプロセスを設定します
r.setProcess(app, thread, pid, uidRecord);
... 省略部分
// ターゲットプロセス内のすべてのサービスを取得します
final ProcessServiceRecord psr = app.mServices;
final boolean newService = psr.startService(r);
...
// AMSを通じてOOM_ADJの数値を調整します
mAm.enqueueOomAdjTargetLocked(app);
mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE);
boolean created = false;
try {
...
final int uid = r.appInfo.uid;
final String packageName = r.name.getPackageName();
final String serviceName = r.name.getClassName();
...
// threadはアプリ側のIApplicationThreadです
thread.scheduleCreateService(r,
r.serviceInfo,
mAm.compatibilityInfoForPackage(r.serviceInfo.applicationInfo),
app.mState.getReportedProcState());
...
created = true;
} /* 省略 catch、finally */
... 省略部分
}
ActivityThread - アプリ側が scheduleCreateService を処理し、メインハンドラを通じて CREATE_SERVICE をメインスレッドに送信し、その後 handleCreateService メソッドを呼び出します。
// ActivityThread.java
private class ApplicationThread extends IApplicationThread.Stub {
public final void scheduleCreateService(IBinder token,
ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
updateProcessState(processState, false);
// 必要なデータをCreateServiceDataクラスにカプセル化します
CreateServiceData s = new CreateServiceData();
s.token = token; // TokenはServiceRecordクラスです
s.info = info; // サービスのクラス名などの情報を含みます
s.compatInfo = compatInfo;
// Handlerを通じてCREATE_SERVICE情報を送信します
sendMessage(H.CREATE_SERVICE, s);
}
}
class H extends Handler {
public static final int CREATE_SERVICE = 114;
public void handleMessage(Message msg) {
switch (msg.what) {
case CREATE_SERVICE:
...
handleCreateService((CreateServiceData)msg.obj);
break;
}
}
}
@UnsupportedAppUsage
private void handleCreateService(CreateServiceData data) {
Service service = null;
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
try {
Application app = packageInfo.makeApplication(false, mInstrumentation);
...
// LoadedApkを通じてサービスオブジェクトを作成します
// getAppFactory()=> AppComponentFactoryオブジェクトを作成します
service = packageInfo.getAppFactory()
.instantiateService(cl, data.info.name, data.intent);
// 対応するContextImplオブジェクトを作成します
ContextImpl context = ContextImpl.getImpl(service
.createServiceBaseContext(this, packageInfo));
// service#attachメソッドを呼び出します
service.attach(context, this, data.info.name, data.token, app,
ActivityManager.getService());
// onCreateメソッドを呼び出します
service.onCreate();
try {
// AMSにサービスが正常に作成されたことを通知します
// data.tokenはServiceRecordです
ActivityManager.getService().serviceDoneExecuting(
data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} /* 省略 catch */
}
LoadedApkを通じてサービスオブジェクトを作成します。
// LoadedApk.java
private AppComponentFactory mAppComponentFactory;
public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,
CompatibilityInfo compatInfo, ClassLoader baseLoader,
boolean securityViolation, boolean includeCode, boolean registerPackage) {
mActivityThread = activityThread;
// デフォルトのApplicationInfo
mApplicationInfo = new ApplicationInfo();
...
// createAppFactoryメソッドを通じてファクトリを作成します
mAppComponentFactory = createAppFactory(mApplicationInfo, mBaseClassLoader);
}
public AppComponentFactory getAppFactory() {
return mAppComponentFactory;
}
private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
// カスタムファクトリがある場合は反射を通じて作成します
if (mIncludeCode && appInfo.appComponentFactory != null && cl != null) {
try {
return (AppComponentFactory)
cl.loadClass(appInfo.appComponentFactory).newInstance();
} /* 省略部分 */
}
// デフォルトのファクトリ
return AppComponentFactory.DEFAULT;
}
デフォルトファクトリ AppComponentFactory:ClassLoader#loadClass
を通じて指定されたクラスを読み込み、反射を通じて指定されたクラスを作成します。
// AppComponentFactory.java
public class AppComponentFactory {
... 省略部分
public @NonNull Service instantiateService(@NonNull ClassLoader cl,
@NonNull String className, @Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
// ClassLoader#loadClassを通じて指定されたクラスを読み込み、そのクラスを反射を通じて作成します
return (Service) cl.loadClass(className).newInstance();
}
// デフォルトのファクトリ
public static final AppComponentFactory DEFAULT = new AppComponentFactory();
}