Android内存泄漏:谨慎使用getSystemService

原创 2017年01月03日 12:28:22



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


版权声明:本文为博主原创文章,未经博主允许不得转载。

SystemService 学习笔记之ConnectivityManager

ConnectivityManager功能:监视网络连接状态 通过context.getSystemService(Context.CONNECTIVITY_SERVICE);方法获取, 需要...
  • txj8612
  • txj8612
  • 2013年03月01日 18:18
  • 1981

从源码解析部分系统服务引起的内存泄露

写在开头有时候系统也会坑我们,嘿嘿。那些系统服务引起的内存泄露(举例)系统服务其实就是那些你通过getSystemService得到的那些东西嘿嘿。举例讲解:你得到一个ConnectivityMana...
  • say_from_wen
  • say_from_wen
  • 2017年08月04日 13:32
  • 434

android如何监听粘贴板内容

android如何监听粘贴板内容 最近项目做监听粘贴板弹窗的功能小记一下。其实思路很简单,写一个服务在后台跑,通过ClipboardManager服务来监听粘贴板。废话绍少说上代码: ~~~jav...
  • u012436608
  • u012436608
  • 2016年10月24日 18:48
  • 2379

Android进阶之路 - 复制粘贴的实现

初始 Xml效果 效果实现后的效果: 关键方法 : //获取剪贴板,并把内容设置在剪切板 ClipboardManager cbm=(ClipboardManager)get...
  • qq_20451879
  • qq_20451879
  • 2018年01月05日 17:58
  • 29

使用ConnectivityManager的内存泄漏隐患

Android里面内存泄漏问题最突出的就是Activity的泄漏,而泄漏的根源大多在于单例的使用,也就是一个静态实例持有了Activity的引用。静态变量的生命周期与应用(Application)是相...
  • idaretobe
  • idaretobe
  • 2016年07月23日 16:56
  • 781

避免Activity内存泄露

在android中context可以作很多操作,但是最主要的功能是加载和访问资源。在android中有两种context,一种是 application context,一种是activity con...
  • OyangYujun
  • OyangYujun
  • 2014年11月19日 17:01
  • 2078

android开发 -- 复制文本内容到系统剪贴板(自由复制)

直接上代码:(对应的类:android.content.ClipboardManager) //获取剪贴板管理器: Clipboard...
  • qq_22078107
  • qq_22078107
  • 2016年12月03日 21:42
  • 9284

android 复制文本粘贴

今天做到复制文本,记录一下,主要是使用官方方法;/** * 实现文本复制功能 * add by wangqianzhou * @param content */ public sta...
  • daoxiaomianzi
  • daoxiaomianzi
  • 2016年11月01日 14:00
  • 135

Activity中使用handle存在内存泄漏的隐患

在Android开发中,经常会在Activity中使用handler来进行线程间通信,使主线程能够实时更新UI。       但是在Android Studio中,发现使用handler时会有黄色的...
  • u012551993
  • u012551993
  • 2016年07月22日 23:47
  • 1927

android 实现剪贴板的粘贴复制

复制字符串到剪贴板管理器 String text = "abcdefg";ClipboardManager cmb = (ClipboardManager) context .getSystemSer...
  • uniquemei
  • uniquemei
  • 2016年10月15日 16:53
  • 2292
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android内存泄漏:谨慎使用getSystemService
举报原因:
原因补充:

(最多只允许输入30个字)