以bindService启动服务后,直接退出Activity,日志会输出:
ActivityThread: Activity com.example.administrator.layoutmanager.MainActivity has leaked ServiceConnection com.example.administrator.layoutmanager.MainActivity1 @ 52816718 t h a t w a s o r i g i n a l l y b o u n d h e r e a n d r o i d . a p p . S e r v i c e C o n n e c t i o n L e a k e d : A c t i v i t y c o m . e x a m p l e . a d m i n i s t r a t o r . l a y o u t m a n a g e r . M a i n A c t i v i t y h a s l e a k e d S e r v i c e C o n n e c t i o n c o m . e x a m p l e . a d m i n i s t r a t o r . l a y o u t m a n a g e r . M a i n A c t i v i t y 1 @ 52816718 t h a t w a s o r i g i n a l l y b o u n d h e r e a n d r o i d . a p p . S e r v i c e C o n n e c t i o n L e a k e d : A c t i v i t y c o m . e x a m p l e . a d m i n i s t r a t o r . l a y o u t m a n a g e r . M a i n A c t i v i t y h a s l e a k e d S e r v i c e C o n n e c t i o n c o m . e x a m p l e . a d m i n i s t r a t o r . l a y o u t m a n a g e r . M a i n A c t i v i t y 1@52816718 that was originally bound here
紧接着就是我们服务的解绑与销毁
MyService: call onUnbind…
call onDestroy…
既然我们没去主动解绑,系统会帮我们解绑,那么它一定缓存了服务的connection。
所以我们先看下bindService的流程,看看在哪里保存了我们的Service信息,直接到ContextImpl中找bindService方法
@Override
public boolean bindService (Intent service, ServiceConnection conn,
int flags) {
warnIfCallingFromSystemProcess();
return bindServiceCommon(service, conn, flags, mMainThread.getHandler(),
Process.myUserHandle());
}
bindServiceCommon方法:
private boolean bindServiceCommon (Intent service, ServiceConnection conn, int flags, Handler
handler, UserHandle user) {
IServiceConnection sd;
if (conn == null ) {
throw new IllegalArgumentException("connection is null" );
}
if (mPackageInfo != null ) {
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler, 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(this );
int res = ActivityManager.getService().bindService(
mMainThread.getApplicationThread(), getActivityToken(), service,
service.resolveTypeIfNeeded(getContentResolver()),
sd, flags, 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();
}
}
代码很少,也不难理解,我们只管 看看 ServiceConnection 在哪里用到了 便知道 在哪里保存了。只有一行代码将其传了进去:
IServiceConnection sd;
sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler , flags);
我们继续点进去 发现点不进去,来看看这个mPackageInfo:
final @NonNull LoadedApk mPackageInfo ;
这个类最上面还有两个异常:
一个是IntentReceiver没有反注册,一个是ServiceConnection没有解绑。一切都答案都在这个类里了。
final class IntentReceiverLeaked extends AndroidRuntimeException {
public IntentReceiverLeaked(String msg) {
super (msg);
}
}
final class ServiceConnectionLeaked extends AndroidRuntimeException {
public ServiceConnectionLeaked(String msg) {
super (msg);
}
}
LoadedApk类开头有一句注释:
/**
* Local state maintained about a currently loaded .apk.
* @hide
*/
当前加载的apk的本地状态维护
来看看里面有些什么,emmm 应有尽有…
static final String TAG = "LoadedApk" ;
static final boolean DEBUG = false ;
private final ActivityThread mActivityThread;
final String mPackageName;
private ApplicationInfo mApplicationInfo;
private String mAppDir;
private String mResDir;
private String[] mOverlayDirs;
private String[] mSharedLibraries;
private String mDataDir;
private String mLibDir;
private File mDataDirFile;
private File mDeviceProtectedDataDirFile;
private File mCredentialProtectedDataDirFile;
private final ClassLoader mBaseClassLoader;
private final boolean mSecurityViolation;
private final boolean mIncludeCode;
private final boolean mRegisterPackage;
private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
/** WARNING: This may change. Don't hold external references to it. */
Resources mResources;
private ClassLoader mClassLoader;
private Application mApplication;
private String[] mSplitNames;
private String[] mSplitAppDirs;
private String[] mSplitResDirs;
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers
= new ArrayMap<>();
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers
= new ArrayMap<>();
private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices
= new ArrayMap<>();
private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices
= new ArrayMap<>();
我们接着 bindService的流程走,看看
mPackageInfo.getServiceDispatcher(conn, getOuterContext(), handler , flags);
里面发生了什么
public final IServiceConnection getServiceDispatcher(ServiceConnection c,
Context context, Handler handler, int flags) {
synchronized (mServices) {
LoadedApk. ServiceDispatcher sd = null ;
ArrayMap< ServiceConnection, LoadedApk. ServiceDispatcher> map = mServices. get(context);
if (map != null ) {
if (DEBUG) Slog. d(TAG , "Returning existing dispatcher " + sd + " for conn " + c);
sd = map . get(c);
}
if (sd == null ) {
sd = new ServiceDispatcher(c, context, handler, flags);
if (DEBUG) Slog. d(TAG , "Creating new dispatcher " + sd + " for conn " + c);
if (map == null ) {
map = new ArrayMap<> ();
mServices. put(context, map );
}
map . put(c, sd);
} else {
sd. validate(context, handler);
}
return sd. getIServiceConnection();
}
}
代码中很清楚的的交代了, map.put(c, sd); 将ServiceConnection 存到了mServices当中。
然后看Activity在销毁时的处理,这里我就直接说代码位置,大家可以实际 去研究下Activity相关流程。
ActivityThread 的 handleDestroyActivity 中有一段代码:
if (c instanceof ContextImpl) {
((ContextImpl) c).scheduleFinalCleanup(
r.activity.getClass().getName(), "Activity" );
}
final void scheduleFinalCleanup(String who, String what) {
mMainThread.scheduleContextCleanup(this , who, what);
}
final void scheduleContextCleanup(ContextImpl context, String who,
String what) {
ContextCleanupInfo cci = new ContextCleanupInfo()
cci.context = context
cci.who = who
cci.what = what
sendMessage(H.CLEAN _UP_CONTEXT, cci)
}
//这个消息
case CLEAN_UP_CONTEXT:
ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj
cci.context .performFinalCleanup (cci.who , cci.what )
break
//最后 来到
final void performFinalCleanup(String who, String what) {
//Log.i (TAG, "Cleanup up context: " + this)
mPackageInfo.removeContextRegistrations (getOuterContext(), who, what)
}
mPackageInfo熟悉吗?绕了一圈 又来到了 LoadApk。我们来看removeContextRegistrations
public void removeContextRegistrations(Context context,
String who, String what) {
final boolean reportRegistrationLeaks = StrictMode.vmRegistrationLeaksEnabled ()
synchronized (mReceivers) {
ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher > rmap =
mReceivers.remove (context)
if (rmap != null) {
for (int i = 0
LoadedApk.ReceiverDispatcher rd = rmap.valueAt (i)
IntentReceiverLeaked leak = new IntentReceiverLeaked(
what + " " + who + " has leaked IntentReceiver "
+ rd.getIntentReceiver () + " that was " +
"originally registered here. Are you missing a " +
"call to unregisterReceiver()?" )
leak.setStackTrace (rd.getLocation ().getStackTrace ())
Slog.e (ActivityThread.TAG , leak.getMessage (), leak)
if (reportRegistrationLeaks) {
StrictMode.onIntentReceiverLeaked (leak)
}
try {
ActivityManager.getService ().unregisterReceiver (
rd.getIIntentReceiver ())
} catch (RemoteException e) {
throw e.rethrowFromSystemServer ()
}
}
}
mUnregisteredReceivers.remove (context)
}
synchronized (mServices) {
//Slog.i (TAG, "Receiver registrations: " + mReceivers)
ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher > smap =
mServices.remove (context)
if (smap != null) {
for (int i = 0
LoadedApk.ServiceDispatcher sd = smap.valueAt (i)
ServiceConnectionLeaked leak = new ServiceConnectionLeaked(
what + " " + who + " has leaked ServiceConnection "
+ sd.getServiceConnection () + " that was originally bound here" )
leak.setStackTrace (sd.getLocation ().getStackTrace ())
Slog.e (ActivityThread.TAG , leak.getMessage (), leak)
if (reportRegistrationLeaks) {
StrictMode.onServiceConnectionLeaked (leak)
}
try {
ActivityManager.getService ().unbindService (
sd.getIServiceConnection ())
} catch (RemoteException e) {
throw e.rethrowFromSystemServer ()
}
sd.doForget ()
}
}
mUnboundServices.remove (context)
//Slog.i (TAG, "Service registrations: " + mServices)
}
}
很明显 这里去 帮我们解绑 我们没有解绑的Receiver和Service,并输出异常。
到这里就基本结束了,现在来组织下语言:
- 为什么bindService可以跟Activity生命周期联动?
答: 因为bindService时LoadApk将ServiceConnection用map保存了起来,当Activity被destroy时会执行removeContextRegistrations来清除 该context的相关注册。所以Activity退出时服务也被解绑。
其余变量的用处可以自行参阅源码。