微信号:jszj2014215

介绍:主要分享移动互联网的相关产品和资讯,关注你将学习到更多,在互联网的当下你会赚更多的钱...

Android内存泄漏:谨慎使用getSystemService

2016-11-16 08:15 点这里

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相关的服务已经优先进行了处理

@Overridepublic 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服务

@Overridepublic 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时要慎重考虑这样的问题。

 

来自:http://droidyue.com/blog/2016/11/14/be-careful-using-getsystemservice/


 
Android技术之家 更多文章 减少APK的大小,Android官方这样说 热修复框架HotFix源码解析 巧用Android图片资源,打造更精致的APP 让你的studio 的Gradle编译上高速 一个2年安卓开发者的一些忠告
猜您喜欢 “人人访谈”更名“开讲啦”,第七期于6月26日晚约定你 MySQL分页性能优化指南 毕业了,去大公司实习还是去创业公司实习好? 设计师的工作台该是什么样? 舍弗勒集团与IBM签署Watson物联网合作协议,迎接全新工业时代