首页 > 系统相关 >[译]Android防止内存的八种方法(下)

[译]Android防止内存的八种方法(下)

时间:2022-12-09 18:00:52浏览次数:56  
标签:八种 void private static 内存 Override new Android 引用


在上一篇​​Android内存泄漏的八种可能(上)​​中,我们讨论了八种容易发生内存泄漏的代码。其中,尤其严重的是泄漏​​Activity​​对象,因为它占用了大量系统内存。不管内存泄漏的代码表现形式如何,其核心问题在于:

在Activity生命周期之外仍持有其引用。

幸运的是,一旦泄漏发生且被定位到了,修复方法是相当简单的。

Static Actitivities

这种泄漏

private static MainActivity activity;

void setStaticActivity() {
activity = this;
}

private static MainActivity activity;

void setStaticActivity() {
activity = this;
}

构造静态变量持有​​Activity​​​对象很容易造成内存泄漏,因为静态变量是全局存在的,所以当​​MainActivity​​生命周期结束时,引用仍被持有。这种写法开发者是有理由来使用的,所以我们需要正确的释放引用让垃圾回收机制在它被销毁的同时将其回收。

Android提供了特殊的​​Set​​​集合​​https://developer.android.com/reference/java/lang/ref/package-summary.html#classes​​​允许开发者控制引用的“强度”。​​Activity​​对象泄漏是由于需要被销毁时,仍然被强引用着,只要强引用存在就无法被回收。

可以用弱引用代替强引用。
​​​https://developer.android.com/reference/java/lang/ref/WeakReference.html​​.

弱引用不会阻止对象的内存释放,所以即使有弱引用的存在,该对象也可以被回收。

private static WeakReference<MainActivity> activityReference;

void setStaticActivity() {
activityReference = new WeakReference<MainActivity>(this);
}

private static WeakReference<MainActivity> activityReference;

void setStaticActivity() {
activityReference = new WeakReference<MainActivity>(this);
}

Static Views

静态变量持有View

private static View view;

void setStaticView() {
view = findViewById(R.id.sv_button);
}

private static View view;

void setStaticView() {
view = findViewById(R.id.sv_button);
}

由于​​View​​​持有其宿主​​Activity​​​的引用,导致的问题与​​Activity​​​一样严重。弱引用是个有效的解决方法,然而还有另一种方法是在生命周期结束时清除引用,​​Activity#onDestory()​​方法就很适合把引用置空。

private static View view;

@Override
public void onDestroy() {
super.onDestroy();
if (view != null) {
unsetStaticView();
}
}

void unsetStaticView() {
view = null;
}

private static View view;

@Override
public void onDestroy() {
super.onDestroy();
if (view != null) {
unsetStaticView();
}
}

void unsetStaticView() {
view = null;
}

Inner Class

这种泄漏

private static Object inner;

void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}

private static Object inner;

void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}

与上述两种情况相似,开发者必须注意用非静态内部类,因为​​非静态内部类​​持有外部类的隐式引用,容易导致意料之外的泄漏。然而内部类可以访问外部类的私有变量,只要我们注意引用的生命周期,就可以避免意外的发生。

避免静态变量

这样持有内部类的成员变量是可以的。

private Object inner;

void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}

private Object inner;

void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();
}

Anonymous Classes

前面我们看到的都是持有全局生命周期的静态成员变量引起的,直接或间接通过链式引用​​Activity​​​导致的泄漏。这次我们用​​AsyncTask​

void startAsyncTask() {
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}.execute();
}

void startAsyncTask() {
new AsyncTask<Void, Void, Void>() {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}.execute();
}

​Handler​

void createHandler() {
new Handler() {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}.postDelayed(new Runnable() {
@Override public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}

void createHandler() {
new Handler() {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}.postDelayed(new Runnable() {
@Override public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}

​Thread​

void scheduleTimer() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}

void scheduleTimer() {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
while(true);
}
}, Long.MAX_VALUE >> 1);
}

全部都是因为​​匿名类​​​导致的。匿名类是特殊的内部类——写法更为简洁。当需要一次性特殊的子类时,Java提供的语法糖能让表达式最少化。这种很赞很偷懒的写法容易导致泄漏。正如使用内部类一样,只要不跨越生命周期,内部类是完全没问题的。但是,这些类是用于产生后台线程的,这些Java线程是全局的,而且持有创建者的引用(即匿名类的引用),而匿名类又持有外部类的引用。线程是可能长时间运行的,所以一直持有​​Activity​​​的引用导致当销毁时无法回收。
这次我们不能通过移除静态成员变量解决,因为线程是于应用生命周期相关的。为了避免泄漏,我们必须舍弃简洁偷懒的写法,把子类声明为静态内部类。

静态内部类不持有外部类的引用,打破了链式引用。

所以对于​​AsyncTask​

private static class NimbleTask extends AsyncTask<Void, Void, Void> {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}

void startAsyncTask() {
new NimbleTask().execute();
}

private static class NimbleTask extends AsyncTask<Void, Void, Void> {
@Override protected Void doInBackground(Void... params) {
while(true);
}
}

void startAsyncTask() {
new NimbleTask().execute();
}

​Handler​

private static class NimbleHandler extends Handler {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}

private static class NimbleRunnable implements Runnable {
@Override public void run() {
while(true);
}
}

void createHandler() {
new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE >> 1);
}

private static class NimbleHandler extends Handler {
@Override public void handleMessage(Message message) {
super.handleMessage(message);
}
}

private static class NimbleRunnable implements Runnable {
@Override public void run() {
while(true);
}
}

void createHandler() {
new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE >> 1);
}

​TimerTask​

private static class NimbleTimerTask extends TimerTask {
@Override public void run() {
while(true);
}
}

void scheduleTimer() {
new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE >> 1);
}

private static class NimbleTimerTask extends TimerTask {
@Override public void run() {
while(true);
}
}

void scheduleTimer() {
new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE >> 1);
}

但是,如果你坚持使用匿名类,只要在生命周期结束时中断线程就可以。

private Thread thread;

@Override
public void onDestroy() {
super.onDestroy();
if (thread != null) {
thread.interrupt();
}
}

void spawnThread() {
thread = new Thread() {
@Override public void run() {
while (!isInterrupted()) {
}
}
}
thread.start();
}

private Thread thread;

@Override
public void onDestroy() {
super.onDestroy();
if (thread != null) {
thread.interrupt();
}
}

void spawnThread() {
thread = new Thread() {
@Override public void run() {
while (!isInterrupted()) {
}
}
}
thread.start();
}

Sensor Manager

这种泄漏

void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

void registerListener() {
SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

使用Android系统服务不当容易导致泄漏,为了​​Activity​​​与服务交互,我们把​​Activity​​​作为监听器,引用链在传递事件和回调中形成了。只要​​Activity​​维持注册监听状态,引用就会一直持有,内存就不会被释放。

在Activity结束时注销监听器

private SensorManager sensorManager;
private Sensor sensor;

@Override
public void onDestroy() {
super.onDestroy();
if (sensor != null) {
unregisterListener();
}
}

void unregisterListener() {
sensorManager.unregisterListener(this, sensor);
}

private SensorManager sensorManager;
private Sensor sensor;

@Override
public void onDestroy() {
super.onDestroy();
if (sensor != null) {
unregisterListener();
}
}

void unregisterListener() {
sensorManager.unregisterListener(this, sensor);
}

总结

​Activity​​泄漏的案例我们已经都走过一遍了,其他都大同小异。建议日后遇到类似的情况时,就使用相应的解决方法。内存泄漏只要发生过一次,通过详细的检查,很容易解决并防范于未然。



作者:豆沙包67



标签:八种,void,private,static,内存,Override,new,Android,引用
From: https://blog.51cto.com/u_15907753/5926347

相关文章

  • Android中的EditText默认时不弹出软键盘的方法
    在做项目过程中,父Activity中用ViewPager中的子Activity EditText默认弹出软键盘。这是想屏蔽软键盘应该从 父Activity中处理。处理子Activity达不到效果。......
  • [译]Android内存泄漏的八种可能(上)
    ​​原文EightWaysYourAndroidAppCanLeakMemor​​Java是垃圾回收语言的一种,其优点是开发者无需特意管理内存分配,降低了应用由于局部故障(segmentationfault)导致......
  • Android的架构设计
    AS历史版本下载地址https://developer.android.google.cn/studio/archiveAndroid的架构设计干Android也有一点的年头,特此记录一下自己理解的App架构设计。......
  • JavaScript:变量:声明和赋值变量时,内存结构是什么样的?
    这里只是大概画出内存结构的模型图,方便理解当我们声明变量和赋值变量时,到底在干嘛。如上图所示,a赋值一个对象{},b赋值字符串hello;于是内存里划了三个区域给我们,一个存储......
  • android 网络可用否,以及类型
    在Android平台上开发基于网络的应用,必然需要去判断当前的网络连接情况。下面的代码,作为例子,详细说明了对于当前网络情况的判断。先看一个自己定义的应用类。viewplaincop......
  • android 进程管理
    系统会对进程的重要性进行评估,并将重要性以“oom_adj”这个数值表示出来,赋予各个进程;(系统会根据“oom_adj”来判断需要结束哪些进程,一般来说,“oom_adj”的值越大,该进程被系......
  • android webView 文字、图片分别加载。乱码问题
     1.加载url前,设置图片阻塞1.webSettings.setBlockNetworkImage(true);2.加载完毕后,关闭图片阻塞1.publicvoidonPageFinished(WebViewview,Stringurl){2.......
  • Android Bitmap和Drawable
    一、相关概念1、Drawable就是一个可画的对象,其可能是一张位图(BitmapDrawable),也可能是一个图形(ShapeDrawable),还有可能是一个图层(LayerDrawable),我们根据画图的需求,创建相应的......
  • Android类库打包方法探究
    众所周知,Android应用使用ADT打包成apk,apk中包含了运行程序所需要的一切,包括:class、asset、res、AndroidManifest.xml等。而对于类库项目(libraryproject),ADT生成的jar......
  • android打开系统联系人界面
    在android应用程序的开发中,经常要实现的一个功能是调用系统自带的联系人界面,做到选择人之后,取得相应的名称和号码,并返回。android2.0前后实现方式上也有一定的差别:主要是2.0......