BindService啟動的Service為何在Activity銷燬時自動解綁(Service銷燬時也是一樣處理)
以bindService啟動服務後,直接退出Activity,日誌會輸出:
ActivityThread: Activity com.example.administrator.layoutmanager.MainActivity has leaked ServiceConnection [email protected]atwasoriginallyboundhereandroid.app.ServiceConnectionLeaked:Activitycom.example.administrator.layoutmanager.MainActivityhasleakedServiceConnectioncom.example.administrator.layoutmanager.MainActivity
[email protected] 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) { // Keep this in sync with DevicePolicyManager.bindDeviceAdminServiceAsUser. 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);
1
裡面發生了什麼
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; i < rmap.size(); i++) {
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; i < smap.size(); i++) {
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退出時服務也被解綁。
其餘變數的用處可以自行參閱原始碼。
---------------------
作者:風捲雲飛會天黑
來源:CSDN
原文:https://blog.csdn.net/castledrv/article/details/80311502