Android內存泄漏:謹慎使用getSystemService



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時要慎重考慮這樣的問題。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章