Android中OOM 的产生
当Android系统启动一个应用时即会创建一个虚拟机实例,同时向Android系统申请所需内存,然后把内存分为两部分,一部分为栈空间,存储一些全局引用和静态变量等值,该空间的分配与回收由系统机制决定,垃圾回收不作用在这块区域;另一部分为堆空间,里面存储是对象的实例,垃圾回收主要作用在这部分,回收的一个主要策略是检测堆中的对象在栈空间有无对应的引用。如果没有引用指向它,则会被优先回收,如果有引用指向则不会被回收。一旦剩余堆内存空间不够申请新对象时就会产生OutOfMemoryError异常。
Android中的虚拟机(Dalvik or ART)对每个应用可使用的最大内存空间做了限制,每台设备出厂之前厂家就对单个虚拟机实例可使用的最大内存进行了限定。这些信息储存在手机中 /system/build.prop配置文件中。
获取Nexus5X相关信息如下
bullhead:/ $ cat /system/build.prop
...
dalvik.vm.heapstartsize=8m 为一个应用初始分配的堆大小,越大意味着应用第一次启动时越流畅,但也意味着内存耗用越快。
dalvik.vm.heapgrowthlimit=192m 单个应用可使用的最大内存堆大小。
dalvik.vm.heapsize=512m 表示应用在manifest中配置android:largeHeap="true"时可使用的最大内存堆大小。
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m
获取内存配置
Log.e(TAG, "max memory = " + Runtime.getRuntime().maxMemory());
Log.e(TAG, "free memory = " + Runtime.getRuntime().freeMemory());
Log.e(TAG, "total memory = " + Runtime.getRuntime().totalMemory());
ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
Log.e(TAG, "memoryClass = " + Integer.toString(am.getMemoryClass()));
Log.e(TAG, "largememoryClass = " + Integer.toString(am.getLargeMemoryClass()));
Runtime.getRuntime().maxMemory()
这个参数对应到build.prop中
的信息就是在未设置largeHeap为true时会返回heapgrowthlimit
的大小,而设置了largeHeap为true后,则返回heapsize大小。单位为Bytes。
getMemoryClass
所获得的大小不受largeHeap配置影响,永远是heapgrowthlimit
中大小。而getLargeMemoryClass
则为heapsize
大小,两者单位都为M。
Nexus5X manifest中配置android:largeHeap="true"
时数据如下,可以把这些数据与build.prop
中的数据对应起来
E/DebugYoga: max memory = 536870912
E/DebugYoga: free memory = 229684
E/DebugYoga: total memory = 9332092
E/DebugYoga: memoryClass = 192
E/DebugYoga: largememoryClass = 512
情况分析
实际开发过程中OOM最终都由两种情况产生。1.申请大量的内存导致OOM(常见情况加载大图/多图),2.内存泄漏导致可用内存越来越少(常见情况Context泄露)。
编码时我们会注意这些点,可是难免会有疏漏。项目中可以借助leakcanary
库,当有内存泄漏时即可发出通知。leakcanary
集成只集成debug模式,发布时去除可以减小APK大小。
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4'
public class DebugYoga extends Yoga {
public static final String TAG = "DebugYoga";
@Override
public void onCreate() {
LeakCanary.install(this);
}
}
内存泄漏时通知
资源下载时引起的内存泄漏,ProjectManager静态对象引用Activity对象,导致Activity关闭时无法回收。触发代码ProjectManager.getInstance().downloadOpenAdvertSource()
EasyHttp.get(YogaHttpConfig.OPEN_ADVERT).params("action", 7 + "").execute(getLifecycleTransformer(), new YogaSimpleCallBack<List<OpenAdvertResponseResult>>() {
@Override
public void onSuccess(List<OpenAdvertResponseResult> openAdvertResponseResults) {
// 判定广告集合非空且size大于0
if (openAdvertResponseResults != null && openAdvertResponseResults.size() > 0) {
// 获取mOpenAdvertResultBean
mOpenAdvertResultBean = openAdvertResponseResults.get(0);
// 判定mOpenAdvertResultBean不为null
if (mOpenAdvertResultBean != null) {
ProjectManager.getInstance().downloadOpenAdvertSource(AdvertActivity.this, mOpenAdvertResultBean.getImage(), mOpenAdvertResultBean.getAds_type());
}
}
}
});
解决方案:采用BehaviorSubject
在Activity关闭时,取消回掉关系。
mOpenAdvertSubject
.compose(this.<OpenAdvertResponseResult>getLifecycleTransformer())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<OpenAdvertResponseResult>() {
@Override
public void accept(OpenAdvertResponseResult result) throws Exception {
File file = new File(result.sourceFilePath);
dealDownloadSuccess(result, file, Uri.fromFile(file));
}
});
ConfigUtil.downloadOpenAdvertSource(mOpenAdvertResultBean, mOpenAdvertSubject);
静态引用Context对象引起内存泄漏,静态对象引用Context对象,静态对象生命周期长于Context对象生命周期,导致Context对象无法回收
private PublicDBManager(Context context) {
mContext = context;
initSqliteDB();
}
Context mContext;
public static PublicDBManager getInstence(Context context) {
if (mForumesDBManager == null) {
mForumesDBManager = new PublicDBManager(context);
}
mForumesDBManager.mContext = context;
return mForumesDBManager;
}
解决方案:采用context.getApplicationContext()
替换context
对象
private PublicDBManager(Context context) {
mContext = context.getApplicationContext();
initSqliteDB();
}
定位时Activity对象引起内存泄漏,LocationManager对象生命周期长于Activity对象生命周期,导致Activity对象无法回收。
public void getLocations(final Activity activity) {
// 声明LocationManager对象
String contextService = Context.LOCATION_SERVICE;
// 通过系统服务,取得LocationManager对象
LocationManager loctionManager = (LocationManager) activity.getSystemService(contextService);
// 监听位置变化,10秒一次,距离10米以上
loctionManager.requestLocationUpdates(provider, 10000, 10, new LocationListener() {
// 当位置变化时触发
@Override
public void onLocationChanged(Location location) {
// 使用新的location更新TextView显示
updateWithNewLocation(location, activity);
}
});
}
解决方案:若无需回掉至Activity,采用context.getApplicationContext()
替换Activity
对象,若需要回掉,采用BehaviorSubject在Activity关闭时,取消回掉关系。
public static void getLocations() {
// 声明LocationManager对象
String contextService = Context.LOCATION_SERVICE;
// 通过系统服务,取得LocationManager对象
LocationManager loctionManager = (LocationManager) Yoga.getInstance().getSystemService(contextService);
// 监听位置变化,10秒一次,距离10米以上
loctionManager.requestLocationUpdates(provider, 10000, 10, new LocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
// 当位置变化时触发
@Override
public void onLocationChanged(Location location) {
// 使用新的location更新TextView显示
updateWithNewLocation(location);
}
});
}
静态Activity对象引起内存泄漏,静态对象会一直存在整个应用生命周期,创建后无法回收。
public class FrameworkActivity extends TabActivity{
/** FrameworkActivity实例对象 */
public static FrameworkActivity mFrameInstance;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mFrameInstance = this;
}
解决方案:禁止此种写法。FrameworkActivity
页面采用静态Activity对象的原因有两个,1.TabActivity
对象和子Activity对象交互。2,其他Activity对象和TabActivity
对象交互。
-
TabActivity
对象和子Activity对象交互,采用Activity activity = getParent()
获取到TabActivity
对象直接调用方法即可public void gotoYouZanHome() { Activity activity = getParent(); if (activity instanceof FrameworkActivity) { ((FrameworkActivity) activity).setCurrentTab(2); } }
-
其他Activity对象和
TabActivity
对象交互,采用Intent对象
和onNewIntent(Intent intent)
进行交互Intent intent = new Intent(getContext(), FrameworkActivity.class); intent.setAction(FrameworkActivity.ACTION_SWITCH_TAB); intent.putExtra(ConstServer.POSITION, 3); startActivity(intent); protected void onNewIntent(Intent intent) { super.onNewIntent(intent); mEnterFrameIndex = intent.getIntExtra(ConstServer.POSITION, 0); if (ACTION_SWITCH_TAB.equals(intent.getAction())) { // 设置当前Tab setCurrentTab(mEnterFrameIndex); } }
总结
1,借助leakcanary
库可以快速定位到发生泄漏的位置。
2,避免任何生命周期长于Activity的对象引用Activity对象,如必须绑定关系,需在Activity关闭时解除绑定关系
3,熟悉各个组件交互方式,禁止静态Activity对象写法
4,采用MVP模式避免网络引起的内存泄漏