Android中有很多服務,比如PowerManager,AlarmManager,NotificationManager等,通常使用起來也很方便,就是使用Context.getSystemService方法來獲得。
一次在公司開發項目開發中,突然LeakCanary彈出了一個內存泄漏的通知欄,不好,內存泄漏發生了。原因竟是和getSystemService有關。
爲了排除干擾因素,我們使用一個簡單的示例代碼
public class MainActivity extends AppCompatActivity {
private static PowerManager powerManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
}
}
當退出MainActivity時,得到了LeakCanary的內存泄漏報告。如下圖。
奇怪了,爲什麼PowerManager會持有Activity的實例呢,按照理解,PowerManager應該是持有Application的Context對象的。
因此,我們有必要對PowerManager的源碼分析一下
1.PowerManager會持有一個Context實例,具體使用Activity還是Application的Context取決於調用者。
final Context mContext;
final IPowerManager mService;
final Handler mHandler;
/**
* {@hide}
*/
public PowerManager(Context context, IPowerManager service, Handler handler) {
mContext = context;
mService = service;
mHandler = handler;
}
2.負責緩存服務的實現在ContextImpl.java文件中
// The system service cache for the system services that are cached per-ContextImpl.
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
而Activity通過ContextImpl提供的setOuterContext方法設置mOuterContext
final void setOuterContext(Context context) {
mOuterContext = context;
}
因此Activity與ContextImpl的關係如下圖
SystemServiceRegistry.java中獲取PowerManager的實現。
registerService(Context.POWER_SERVICE, PowerManager.class,
new CachedServiceFetcher<PowerManager>() {
@Override
public PowerManager createService(ContextImpl ctx) {
IBinder b = ServiceManager.getService(Context.POWER_SERVICE);
IPowerManager service = IPowerManager.Stub.asInterface(b);
if (service == null) {
Log.wtf(TAG, "Failed to get power manager service.");
}
return new PowerManager(ctx.getOuterContext(),
service, ctx.mMainThread.getHandler());
}});
創建具體的服務的實現爲core/java/android/app/SystemServiceRegistry.java
如何解決
不使用靜態持有PowerManager
因爲static是一個很容易和內存泄漏產生關聯的因素
- static變量與類的生命週期相同
- 類的生命週期等同於類加載器
- 類加載器通常和進程的生命週期一致
所以通過去除static可以保證變量週期和Activity實例相同。這樣就不會產生內存泄漏問題。
使用ApplicationContext
除了上面的方法之外,傳入Application的Context而不是Activity Context也可以解決問題。
PowerManager powerManager = (PowerManager)getApplicationContext().getSystemService(Context.POWER_SERVICE);
是不是都要使用Application Context?
然而並非如此
以Activity爲例,一些和UI相關的服務已經優先進行了處理
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
ContextThemeWrapper也優先處理了LayoutManager服務
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
那到底改用哪個Context
- 如果服務和UI相關,則用Activity
- 如果是類似ALARM_SERVICE,CONNECTIVITY_SERVICE建議有限選用Application Context
- 如果出現出現了內存泄漏,排除問題,可以考慮使用Application Context
所以,當我們再次使用getSystemService時要慎重考慮這樣的問題。