Android 4대 구성 요소의 서비스 작동 원리
As we all know, 서비스는 두 가지 작업 상태로 나뉘는데 하나는 시작 상태이고 주로 백엔드 계산을 실행하는 데 사용되며 다른 하나는 귀속 상태이며 주로 다른 구성 요소와 서비스 상호작용에 사용된다.주의해야 할 것은 서비스의 이 두 가지 상태가 공존한다는 것이다.서비스 시작 방법도 두 가지로 나뉜다
1.Context.startService
Intent intentService = new Intent(this, MyService.class);
startService(intentService);
2.Context.bindService
Intent intentService = new Intent(this, MyService.class);
bindService(intentService,mServiceConnection,BIND_AUTO_CREATE);
1.startService 상세 정보
먼저 Activity와 Context의 관계를 살펴보겠습니다.
public class Activity extends ContextThemeWrapper
public class ContextThemeWrapper extends ContextWrapper
public class ContextWrapper extends Context
public abstract class Context
ContetWrapper에서 start Service 발견 public class ContextWrapper extends Context {
Context mBase;
...
public ComponentName startService(Intent service) {
return mBase.startService(service);
}
} 在前面的章节中可以了解到,Activity创建的时候回调用attach方法,将一个ContextImpl对象关联起来,这个对象就是mBase,接下来分析ContextImpl的startService方法
public ComponentName startService(Intent service) {
warnIfCallingFromSystemProcess();
return startServiceCommon(service, mUser);
}
private ComponentName startServiceCommon(Intent service, UserHandle user) {
try {
validateServiceIntent(service);
service.prepareToLeaveProcess();
ComponentName cn = ActivityManagerNative.getDefault().startService(
mMainThread.getApplicationThread(), service,
service.resolveTypeIfNeeded(getContentResolver()), 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());
}
}
return cn;
} catch (RemoteException e) {
return null;
}
}
ActivityManagerNative.getDefault () 이것은 무엇입니까?
앞의 장에서 알 수 있듯이 이것은 IPC 호출로Activity Manager 서비스를 가리킨다
ActivityManagerService.startService
public ComponentName startService(IApplicationThread caller, Intent service,
String resolvedType, int userId) {
enforceNotIsolatedCaller("startService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
if (DEBUG_SERVICE)
Slog.v(TAG, "startService: " + service + " type=" + resolvedType);
synchronized(this) {
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
ComponentName res = mServices.startServiceLocked(caller, service,
resolvedType, callingPid, callingUid, userId);
Binder.restoreCallingIdentity(origId);
return res;
}
}
final ActiveServices mServices;
Active Services 분석startServiceLocked, 이 방법은 비교적 길기 때문에, 여기에 마지막 관건적인 코드를 붙인다
ComponentName startServiceLocked(IApplicationThread caller,
...
return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}
ComponentName startServiceInnerLocked(ServiceMap smap, Intent service,
ServiceRecord r, boolean callerFg, boolean addToStarting) {
ProcessStats.ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
}
r.callStart = false;
synchronized (r.stats.getBatteryStats()) {
r.stats.startRunningLocked();
}
String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false);
if (error != null) {
return new ComponentName("!!", error);
}
if (r.startRequested && addToStarting) {
boolean first = smap.mStartingBackground.size() == 0;
smap.mStartingBackground.add(r);
r.startingBgTimeout = SystemClock.uptimeMillis() + BG_START_TIMEOUT;
if (DEBUG_DELAYED_SERVICE) {
RuntimeException here = new RuntimeException("here");
here.fillInStackTrace();
Slog.v(TAG, "Starting background (first=" + first + "): " + r, here);
} else if (DEBUG_DELAYED_STARTS) {
Slog.v(TAG, "Starting background (first=" + first + "): " + r);
}
if (first) {
smap.rescheduleDelayedStarts();
}
} else if (callerFg) {
smap.ensureNotStartingBackground(r);
}
return r.name;
}
startServiceInnerLocked가 또 bringUpServiceLocked로 호출됨private final String bringUpServiceLocked(ServiceRecord r,
int intentFlags, boolean execInFg, boolean whileRestarting) {
//Slog.i(TAG, "Bring up service:");
//r.dump(" ");
if (r.app != null && r.app.thread != null) {
sendServiceArgsLocked(r, execInFg, false);
return null;
}
...
if (!isolated) {
app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false);
if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid
+ " app=" + app);
if (app != null && app.thread != null) {
try {
app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
realStartServiceLocked(r, app, execInFg);
return null;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting service " + r.shortName, e);
}
// If a dead object exception was thrown -- fall through to
// restart the application.
}
}
...
return null;
}
bringUpServiceLocked는 주로 realStartServiceLocked를 호출합니다
private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg) throws RemoteException {
if (app.thread == null) {
throw new RemoteException();
}
if (DEBUG_MU)
Slog.v(TAG_MU, "realStartServiceLocked, ServiceRecord.uid = " + r.appInfo.uid
+ ", ProcessRecord.uid = " + app.uid);
r.app = app;
r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
app.services.add(r);
bumpServiceExecutingLocked(r, execInFg, "create");
mAm.updateLruProcessLocked(app, false, null);
mAm.updateOomAdjLocked();
boolean created = false;
try {
String nameTerm;
int lastPeriod = r.shortName.lastIndexOf('.');
nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName;
if (LOG_SERVICE_START_STOP) {
EventLogTags.writeAmCreateService(
r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid);
}
synchronized (r.stats.getBatteryStats()) {
r.stats.startLaunchedLocked();
}
mAm.ensurePackageDexOpt(r.serviceInfo.packageName);
app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
app.thread.scheduleCreateService(r, r.serviceInfo,
mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
app.repProcState);
r.postNotification();
created = true;
} catch (DeadObjectException e) {
Slog.w(TAG, "Application dead when creating service " + r);
mAm.appDiedLocked(app);
} finally {
if (!created) {
app.services.remove(r);
r.app = null;
scheduleServiceRestartLocked(r, false);
return;
}
}
requestServiceBindingsLocked(r, execInFg);
updateServiceClientActivitiesLocked(app, null, true);
// If the service is in the started state, and there are no
// pending arguments, then fake up one so its onStartCommand() will
// be called.
if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) {
r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(),
null, null));
}
sendServiceArgsLocked(r, execInFg, true);
if (r.delayed) {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r);
getServiceMap(r.userId).mDelayedStartList.remove(r);
r.delayed = false;
}
if (r.delayedStop) {
// Oh and hey we've already been asked to stop!
r.delayedStop = false;
if (r.startRequested) {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG, "Applying delayed stop (from start): " + r);
stopServiceLocked(r);
}
}
}
그중에 앱.thread.scheduleCreate Service는 IPC에서 클라이언트 시작 서비스를 호출하고 H 클래스의 실례가 메시지를 보내고 처리합니다
ActivityThread.handleCreateService
private void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to instantiate service " + data.info.name
+ ": " + e.toString(), e);
}
}
try {
if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
context.setOuterContext(service);
Application app = packageInfo.makeApplication(false, mInstrumentation);
service.attach(context, this, data.info.name, data.token, app,
ActivityManagerNative.getDefault());
service.onCreate();
mServices.put(data.token, service);
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, 0, 0, 0);
} catch (RemoteException e) {
// nothing to do.
}
} catch (Exception e) {
if (!mInstrumentation.onException(service, e)) {
throw new RuntimeException(
"Unable to create service " + data.info.name
+ ": " + e.toString(), e);
}
}
}
이 방법은 비교적 중요하므로handleCreate 서비스는 주로 다음과 같은 몇 가지 일을 하였다.
1. ClassLoader를 통해 서비스 인스턴스 로드
2. Application 객체를 만들고 onCreate 메서드를 호출합니다.
3. ContextImpl 객체를 만들고 Service의attach 방법으로 연결(Activity와 유사)
4. 마지막으로 Service의 onCreate 방법을 호출하고 서비스 대상ActivityThread의 mServies 목록에
realStartServiceLocked는 위의 방법 이외에sendServiceArgsLocked도 호출합니다
sendServiceArgsLocked
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
boolean oomAdjusted) {
final int N = r.pendingStarts.size();
if (N == 0) {
return;
}
while (r.pendingStarts.size() > 0) {
try {
ServiceRecord.StartItem si = r.pendingStarts.remove(0);
if (DEBUG_SERVICE) Slog.v(TAG, "Sending arguments to: "
+ r + " " + r.intent + " args=" + si.intent);
if (si.intent == null && N > 1) {
// If somehow we got a dummy null intent in the middle,
// then skip it. DO NOT skip a null intent when it is
// the only one in the list -- this is to support the
// onStartCommand(null) case.
continue;
}
si.deliveredTime = SystemClock.uptimeMillis();
r.deliveredStarts.add(si);
si.deliveryCount++;
if (si.neededGrants != null) {
mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants,
si.getUriPermissionsLocked());
}
bumpServiceExecutingLocked(r, execInFg, "start");
if (!oomAdjusted) {
oomAdjusted = true;
mAm.updateOomAdjLocked(r.app);
}
int flags = 0;
if (si.deliveryCount > 1) {
flags |= Service.START_FLAG_RETRY;
}
if (si.doneExecutingCount > 0) {
flags |= Service.START_FLAG_REDELIVERY;
}
r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
} catch (RemoteException e) {
// Remote process gone... we'll let the normal cleanup take
// care of this.
if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while scheduling start: " + r);
break;
} catch (Exception e) {
Slog.w(TAG, "Unexpected exception", e);
break;
}
}
}
r.app.thread.scheduleServiceArgs 이것은 또 하나의 크로스 프로세스 호출입니다. AMS는 ApplicationThread를 통해 프로세스를 알립니다
ActivityThread.ApplicationThread.scheduleServiceArgs
public final void scheduleServiceArgs(IBinder token, boolean taskRemoved, int startId,
int flags ,Intent args) {
ServiceArgsData s = new ServiceArgsData();
s.token = token;
s.taskRemoved = taskRemoved;
s.startId = startId;
s.flags = flags;
s.args = args;
sendMessage(H.SERVICE_ARGS, s);
}
사실상 H 이handler 실례를 통해 메시지를 보냈어요. private void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
private void sendMessage(int what, Object obj, int arg1) {
sendMessage(what, obj, arg1, 0, false);
}
private void sendMessage(int what, Object obj, int arg1, int arg2) {
sendMessage(what, obj, arg1, arg2, false);
}
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
if (DEBUG_MESSAGES) Slog.v(
TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
+ ": " + arg1 + " / " + obj);
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}
이어서 Activity Thread.H.handleMessage case SERVICE_ARGS:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStart");
handleServiceArgs((ServiceArgsData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
private void handleServiceArgs(ServiceArgsData data) {
Service s = mServices.get(data.token);
if (s != null) {
try {
if (data.args != null) {
data.args.setExtrasClassLoader(s.getClassLoader());
data.args.prepareToEnterProcess();
}
int res;
if (!data.taskRemoved) {
res = s.onStartCommand(data.args, data.flags, data.startId);
} else {
s.onTaskRemoved(data.args);
res = Service.START_TASK_REMOVED_COMPLETE;
}
QueuedWork.waitToFinish();
try {
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, 1, data.startId, res);
} catch (RemoteException e) {
// nothing to do.
}
ensureJitEnabled();
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
throw new RuntimeException(
"Unable to start service " + s
+ " with " + data.args + ": " + e.toString(), e);
}
}
}
}
그 중에서 s.onStartCommand는 서비스의 onStartCommand 방법을 호출했다.
이 Start Service에 대한 분석이 진행 중입니다.
2. bindService 상세 정보
Start Service와 마찬가지로 시작도 Activity입니다.bindService->ContextWrapper.bindService->ContextImpl.bindServiceCommon
private boolean bindServiceCommon(Intent service, ServiceConnection conn, int flags,
UserHandle user) {
IServiceConnection sd;
if (conn == null) {
throw new IllegalArgumentException("connection is null");
}
if (mPackageInfo != null) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
mMainThread.getHandler(), flags);
} else {
throw new RuntimeException("Not supported in system context");
}
validateServiceIntent(service);
try {
IBinder token = getActivityToken();
if (token == null && (flags&BIND_AUTO_CREATE) == 0 && mPackageInfo != null
&& mPackageInfo.getApplicationInfo().targetSdkVersion
< android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
flags |= BIND_WAIVE_PRIORITY;
}
service.prepareToLeaveProcess();
int res = ActivityManagerNative.getDefault().bindService(
mMainThread.getApplicationThread(), getActivityToken(),
service, service.resolveTypeIfNeeded(getContentResolver()),
sd, flags, user.getIdentifier());
if (res < 0) {
throw new SecurityException(
"Not allowed to bind to service " + service);
}
return res != 0;
} catch (RemoteException e) {
return false;
}
}
sd = mPackageInfo.get 서비스 디스패치는 클라이언트가 먼저 서비스 연결 형식의conn에서 IService 연결 형식의 sd로 바뀌었고 IService 연결 형식은 실제 인터페이스입니다
여기 mPackage Info는 Loaded Apk 타입입니다. 이 함수가 도대체 어떤 신마동동을 했는지 보세요.
public final IServiceConnection getServiceDispatcher(ServiceConnection c,
Context context, Handler handler, int flags) {
synchronized (mServices) {
LoadedApk.ServiceDispatcher sd = null;
ArrayMap map = mServices.get(context);
if (map != null) {
sd = map.get(c);
}
if (sd == null) {
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);
}
return sd.getIServiceConnection();
}
}
클라이언트의 서비스 연결 대상을 사실상 서비스 디스패치로 전환했다.InnerConnection 객체왜 돌려야 하는지, 이유는 매우 간단하다. 왜냐하면 서비스의 귀속은 크로스 프로세스의 필수적인 Binder를 빌려야 하기 때문이다. 서비스디스패치.InnerConnection 객체가 이러한 역할을 수행합니다.ServiceDispatcher는 두 객체를 연결합니다.이어서 bind 서비스 Common 뒤에 있는 코드를 보면 위에서 start 서비스 분석을 한 경험이 있으면Activity Manager Native를 쉽게 알 수 있습니다.getDefault().bindService 는 IPC 호출이며 AMS를 직접 가리킵니다.bindService
public int bindService(IApplicationThread caller, IBinder token,
Intent service, String resolvedType,
IServiceConnection connection, int flags, int userId) {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
throw new IllegalArgumentException("File descriptors passed in Intent");
}
synchronized(this) {
return mServices.bindServiceLocked(caller, token, service, resolvedType,
connection, flags, userId);
}
}
ActiveServices.bindServiceLocked-> bringUpServiceLocked->realStartServiceLocked real Start Service Locked는 start Service 분석으로 돌아왔습니다. start Service와 달리 bind Service는Active Services의 Request Service Binding Locked 방법을 호출합니다.
ActiveServices.requestServiceBindingLocked
private final boolean requestServiceBindingLocked(ServiceRecord r,
IntentBindRecord i, boolean execInFg, boolean rebind) {
if (r.app == null || r.app.thread == null) {
// If service is not currently running, can't yet bind.
return false;
}
if ((!i.requested || rebind) && i.apps.size() > 0) {
try {
bumpServiceExecutingLocked(r, execInFg, "bind");
r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE);
r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind,
r.app.repProcState);
if (!rebind) {
i.requested = true;
}
i.hasBound = true;
i.doRebind = false;
} catch (RemoteException e) {
if (DEBUG_SERVICE) Slog.v(TAG, "Crashed while binding " + r);
return false;
}
}
return true;
}
ActivityThread를 분석합니다.ApplicationThread.sheduleBindService public final void scheduleBindService(IBinder token, Intent intent,
boolean rebind, int processState) {
updateProcessState(processState, false);
BindServiceData s = new BindServiceData();
s.token = token;
s.intent = intent;
s.rebind = rebind;
if (DEBUG_SERVICE)
Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
+ Binder.getCallingUid() + " pid=" + Binder.getCallingPid());
sendMessage(H.BIND_SERVICE, s);
}
더 이상 익숙할 수 없으니 직접 처리법을 보시오private void handleBindService(BindServiceData data) {
Service s = mServices.get(data.token);
if (DEBUG_SERVICE)
Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind);
if (s != null) {
try {
data.intent.setExtrasClassLoader(s.getClassLoader());
data.intent.prepareToEnterProcess();
try {
if (!data.rebind) {
IBinder binder = s.onBind(data.intent);
ActivityManagerNative.getDefault().publishService(
data.token, data.intent, binder);
} else {
s.onRebind(data.intent);
ActivityManagerNative.getDefault().serviceDoneExecuting(
data.token, 0, 0, 0);
}
ensureJitEnabled();
} catch (RemoteException ex) {
}
} catch (Exception e) {
if (!mInstrumentation.onException(s, e)) {
throw new RuntimeException(
"Unable to bind to service " + s
+ " with " + data.intent + ": " + e.toString(), e);
}
}
}
}
클라이언트 프로세스가 다시 IPC에서 AMS의publish 서비스를 호출하는 방법 public void publishService(IBinder token, Intent intent, IBinder service) {
// Refuse possible leaked file descriptors
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");
}
mServices.publishServiceLocked((ServiceRecord)token, intent, service);
}
}
ActiveServices.publishServiceLocked
void publishServiceLocked(ServiceRecord r, Intent intent, IBinder service) {
...
try {
c.conn.connected(r.name, service);
} catch (Exception e) {
Slog.w(TAG, "Failure sending service " + r.name +
" to connection " + c.conn.asBinder() +
" (in " + c.binding.client.processName + ")", e);
}
...
}
이 방법은 매우 길다. 여기서 가장 관건적인 코드를 캡처했다. 그 중에서 c의 유형은 Connectionrecord이고 c.conn의 유형은 서비스 디스패치이다.InnerConnection 객체 private static class InnerConnection extends IServiceConnection.Stub {
final WeakReference mDispatcher;
InnerConnection(LoadedApk.ServiceDispatcher sd) {
mDispatcher = new WeakReference(sd);
}
public void connected(ComponentName name, IBinder service) throws RemoteException {
LoadedApk.ServiceDispatcher sd = mDispatcher.get();
if (sd != null) {
sd.connected(name, service);
}
}
}
LoadedApk에서ServiceDispatcher.InnerConnection의 정의는 InnerConnection을 볼 수 있다.connected->ServiceDispacher.connected public void connected(ComponentName name, IBinder service) {
if (mActivityThread != null) {
mActivityThread.post(new RunConnection(name, service, 0));
} else {
doConnected(name, service);
}
}
여기 서비스 디스패치의 mActivity Thread는handler이고Activity Thread의 H클래스에 대응한다. 이렇게 RunConnection이 H.post를 거쳐 메인 라인에서 운행하면 서비스 연결의 방법은 메인 라인에서 리셋되는 것이다.이어서 RunConnection의 정의를 살펴보겠습니다.private final class RunConnection implements Runnable {
RunConnection(ComponentName name, IBinder service, int command) {
mName = name;
mService = service;
mCommand = command;
}
public void run() {
if (mCommand == 0) {
doConnected(mName, mService);
} else if (mCommand == 1) {
doDeath(mName, mService);
}
}
final ComponentName mName;
final IBinder mService;
final int mCommand;
}
ServiceDispatcher.doConnected public void doConnected(ComponentName name, IBinder service) {
ServiceDispatcher.ConnectionInfo old;
ServiceDispatcher.ConnectionInfo info;
synchronized (this) {
if (mForgotten) {
// We unbound before receiving the connection; ignore
// any connection received.
return;
}
old = mActiveConnections.get(name);
if (old != null && old.binder == service) {
// Huh, already have this one. Oh well!
return;
}
if (service != null) {
// A new service is being connected... set it all up.
mDied = false;
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) {
// This service was dead before we got it... just
// don't do anything with it.
mActiveConnections.remove(name);
return;
}
} else {
// The named service is being disconnected... clean up.
mActiveConnections.remove(name);
}
if (old != null) {
old.binder.unlinkToDeath(old.deathMonitor, 0);
}
}
// If there was an old service, it is not disconnected.
if (old != null) {
mConnection.onServiceDisconnected(name);
}
// If there is a new service, it is now connected.
if (service != null) {
mConnection.onServiceConnected(name, service);
}
}
ServiceConnected의 onServiceConnected가 실행된 후bindService의 분석도 일단락되었다.이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Bitrise에서 배포 어플리케이션 설정 테스트하기이 글은 Bitrise 광고 달력의 23일째 글입니다. 자체 또는 당사 등에서 Bitrise 구축 서비스를 사용합니다. 그나저나 며칠 전 Bitrise User Group Meetup #3에서 아래 슬라이드를 발표했...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.